一文带你搞懂Redis分布式锁
作者:指北君 发布时间:2021-09-26 12:56:14
1、分布式锁简介
分布式锁是控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
业界流行的分布式锁实现,一般有这3种方式:
基于数据库实现的分布式锁
基于Redis实现的分布式锁
基于Zookeeper实现的分布式锁
这里主要介绍如何通过 Redis 来实现分布式锁。在介绍 Redis 分布式锁之前,我们首先介绍一下实现Redis 分布式锁的关键命令。
2、setnx
setnx key value
Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
设置成功,返回 1 。设置失败,返回 0 。
PS:Redis 官方是不推荐基于 setnx 命令来实现分布式锁的,因为会存在很多问题,
①、单点问题。比如:
1、客户端A 从master拿到锁lock01
2、master正要把lock01同步(Redis的主从同步通常是异步的)给slave时,突然宕机了,导致lock01没同步给slave
3、主从切换,slave节点被晋级为master节点
4、客户端B到master拿lock01照样能拿到。这样必将导致同一把锁被多人使用。
②、锁的高级用法,比如读写锁、可重入锁等等,setnx 都比较难实现。
这里先介绍基于 sentnx 实现的分布式锁,后面会介绍官方推荐的基于 redisson 来实现分布式锁。
3、Redis-分布式锁-阶段1
接到上文,查询 * 分类数据,如果我们部署了多个商品服务,然后多个线程同时去获取 * 分类数据,如果不加分布式锁,就会导致,每一个部署的商品服务第一次查询都会走 DB。
public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、获取分布式锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if(lock){
// true 表示加锁成功,执行相关业务
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
4、Redis-分布式锁-阶段2
设置锁自动过期
public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、获取分布式锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if(lock){
// true 表示加锁成功,执行相关业务
// 设置过期时间
stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
5、Redis-分布式锁-阶段3
setnx 命令和过期时间保证原子性。
public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、获取分布式锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111",30,TimeUnit.SECONDS);
if(lock){
// true 表示加锁成功,执行相关业务
// 设置过期时间
//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
6、Redis-分布式锁-阶段4
保证删除的是自己的锁。
public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、获取分布式锁
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
if(lock){
// true 表示加锁成功,执行相关业务
// 设置过期时间
//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
String lockValue = stringRedisTemplate.opsForValue().get("lock");
if(uuid.equals(lockValue)){
stringRedisTemplate.delete("lock");
}
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
7、Redis-分布式锁-阶段5
通过Lua脚本保证删除锁和判断锁两个操作原子性
public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock(){
// 一、获取分布式锁
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
if (lock) {
System.out.println("获取分布式锁成功...");
Map<String, List<Catelog2Vo>> dataFromDb = null;
try {
//加锁成功...执行业务
dataFromDb = getDataFromDb();
} finally {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//删除锁
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
//先去redis查询下保证当前的锁是自己的
//获取值对比,对比成功删除=原子性 lua脚本解锁
// String lockValue = stringRedisTemplate.opsForValue().get("lock");
// if (uuid.equals(lockValue)) {
// //删除我自己的锁
// stringRedisTemplate.delete("lock");
// }
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
这也是分布式锁的最终模式,需要保证两个点:加锁【设置锁+过期时间】和删除锁【判断+删除】原子性。
来源:https://mp.weixin.qq.com/s/6_YsYLSFSV9rxjGacbDQ5w


猜你喜欢
- Talk is cheap, show me the code!先来看代码:public class TestEval {public st
- 本文实例讲述了C#实现汉字转拼音或转拼音首字母的方法。分享给大家供大家参考。具体实现方法如下:/// <summary>///
- 支付宝上有一个咻一咻的功能,就是点击图片后四周有水波纹的这种效果,今天也写一个类似的功能。效果如下所示:思路:就是几个圆的半径不断在变大,这
- 本文实例为大家分享了Android CameraManager类的具体代码,供大家参考,具体内容如下先看代码: private
- Spring开启注解AOP的支持放置的位置放在springmvc的aop,需要在springmvc的配置文件中写开启aop,而不是sprin
- mybatis 传入null值解决前端传入两个值,如果其中一个为null时,很多时候我们都很困惑,明明传入的是null,为啥mybatis
- 注解注解定义Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 语言中的类、方法、变
- 一、编辑框EditText编辑框用于接收键盘输入的文字,由文本视图派生而来,除了TextView已有的各种属性和方法,EditText还支持
- 最近做项目,需要设置用户的生日,所以做这样一个功能。开始发觉自带的DatePicker 很是不好用。上代码:<DatePicker &
- 学习java的人都知道spring,springMVC,mybatis等框架,像structs2这样的框架是基于servlet以外实现的,而
- jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS
- Kotlin Flow在开发中的常用场景使用大家了解了 Flow 的创建与接收流程,了解 SharedFlow 创建的几种方式,各个参数的用
- 本次内容主要介绍基于Ehcache 3.0来快速实现Spring Boot应用程序的数据缓存功能。在Spring Boot应用程序中,我们可
- 1. 前言Compose 具有超强的兼容性,兼容现有的所有代码,Compose 能够与现有 View 体系并存,可实现渐进式替换。这就很有意
- app的启动方式: 1.)冷启动 当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启
- 这几天恰好和朋友谈起了递归,忽然发现不少朋友对于“尾递归”的概念比较模糊,网上搜索一番也没有发现讲解地完整详细的资料,于是写了这么一篇文章,
- 实现 bean 初始化、摧毁方法的配置与处理spring支持我们自定义 bean 的初始化方法和摧毁方法。配置方式可以通过 xml 的 in
- 一、相关概念1.1 Jenkins概念:Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目,无论用的是什么平台。这是一个免费
- 前言想象一下生活中哪些是和线程沾边的?饭店炒菜就是一个很好的例子首先客人要吃菜,前提是厨师要炒好,也就是说,厨师不炒好的话客人是没有饭菜的。
- CountDownTimerCountDownTimer 是android 自带的一个倒计时类,使用这个类可以很简单的实现 倒计时功能Cou