redis集群的一致性保证分布式锁(什么是redis分布式锁)

本文目录
什么是redis分布式锁
分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。
举个不太恰当的例子:(推荐学习:Redis视频教程)
假设共享的资源就是一个房子,里面有各种书,分布式系统就是要进屋看书的人,分布式锁就是保证这个房子只有一个门并且一次只有一个人可以进,而且门只有一把钥匙。然后许多人要去看书,可以,排队,第一个人拿着钥匙把门打开进屋看书并且把门锁上,然后第二个人没有钥匙,那就等着,等第一个出来,然后你在拿着钥匙进去,然后就是以此类推
实现原理
互斥性
保证同一时间只有一个客户端可以拿到锁,也就是可以对共享资源进行操作
安全性
只有加锁的服务才能有解锁权限,也就是不能让a加的锁,bcd都可以解锁,如果都能解锁那分布式锁就没啥意义了
可能出现的情况就是a去查询发现持有锁,就在准备解锁,这时候忽然a持有的锁过期了,然后b去获得锁,因为a锁过期,b拿到锁,这时候a继续执行第二步进行解锁如果不加校验,就将b持有的锁就给删除了
避免死锁
出现死锁就会导致后续的任何服务都拿不到锁,不能再对共享资源进行任何操作了
保证加锁与解锁操作是原子性操作
这个其实属于是实现分布式锁的问题,假设a用redis实现分布式锁
假设加锁操作,操作步骤分为两步:
1,设置key set(key,value)2,给key设置过期时间
假设现在a刚实现set后,程序崩了就导致了没给key设置过期时间就导致key一直存在就发生了死锁
如何实现分布式锁
实现分布式锁的方式有很多,只要满足上述条件的都可以实现分布式锁,比如数据库,redis,zookeeper,在这里就先讲一下如何使用redis实现分布式锁
分布式锁实现的关键是在分布式的应用服务器外,搭建一个存储服务器,存储锁信息,这时候我们很容易就想到了Redis。首先我们要搭建一个Redis服务器,用Redis服务器来存储锁信息。
使用redis实现分布式锁
使用redis命令 set key value NX EX max-lock-time 实现加锁
使用redis命令 EVAL 实现解锁
在实现的时候要注意的几个关键点:
1、锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
2、同一时刻只能有一个线程获取到锁。
几个要用到的redis命令:
setnx(key, value):“set if not exits”,若该key-value不存在,则成功加入缓存并且返回1,否则返回0。
get(key):获得key对应的value值,若不存在则返回nil。
getset(key, value):先获取key对应的value值,若不存在则返回nil,然后将旧的value更新为新的value。
expire(key, seconds):设置key-value的有效期为seconds秒。
更多Redis相关技术文章,请访问Redis数据库使用入门教程栏目进行学习!
如何使用redis实现分布式锁
使用Redis实现分布式锁
redis特性介绍
1、支持丰富的数据类型,如String、List、Map、Set、ZSet等。
2、支持数据持久化,RDB和AOF两种方式
3、支持集群工作模式,分区容错性强
4、单线程,顺序处理命令
5、支持事务
6、支持发布与订阅
Redis实现分布式锁使用了SETNX命令:
SETNX key value
将key的值设为value ,当且仅当key不存在。
若给定的key已经存在,则SETNX不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
可用版本:》= 1.0.0时间复杂度:O(1)返回值:
设置成功,返回 1 。
设置失败,返回 0 。
redis》 EXISTS job # job 不存在
(integer) 0
redis》 SETNX job "programmer" # job 设置成功
(integer) 1
redis》 SETNX job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0
redis》 GET job # 没有被覆盖
"programmer"首先,我们需要封装一个公共的Redis访问工具类。该类需要注入RedisTemplate实例和ValueOperations实例,使用ValueOperations实例是因为Redis实现的分布式锁使用了最简单的String类型。另外,我们需要封装3个方法,分别是setIfObsent (String key, String value)、 expire (String key, long timeout, TimeUnit unit) 、delete (String key) ,分别对应Redis的SETNX、expire、del命令。以下是Redis访问工具类的具体实现:
@Component
public class RedisDao {
@Autowired
private RedisTemplate redisTemplate;
@Resource(name="redisTemplate")
private ValueOperations《Object, Object》 valOpsObj;
/**
* 如果key不存在,就存储一个key-value,相当于SETNX命令
* @param key 键
* @param value 值,可以为空
* @return
*/
public boolean setIfObsent (String key, String value) {
return valOpsObj.setIfAbsent(key, value);
}
/**
* 为key设置失效时间
* @param key 键
* @param timeout 时间大小
* @param unit 时间单位
*/
public boolean expire (String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 删除key
* @param key 键
*/
public void delete (String key) {
redisTemplate.delete(key);
}
}完成了Redis访问工具类的实现,现在需要考虑的是如何去模拟竞争分布式锁。因为Redis本身就是支持分布式集群的,所以只需要模拟出多线程处理业务场景。这里采用线程池来模拟,以下是测试类的具体实现:
@RestController
@RequestMapping("test")
public class TestController {
private static final Logger LOG = LoggerFactory.getLogger(TestController.class); //日志对象
@Autowired
private RedisDao redisDao;
//定义的分布式锁key
private static final String LOCK_KEY = "MyTestLock";
@RequestMapping(value={"testRedisLock"}, method=RequestMethod.GET)
public void testRedisLock () {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i 《 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
//获取分布式锁
boolean flag = redisDao.setIfObsent(LOCK_KEY, "lock");
if (flag) {
LOG.info(Thread.currentThread().getName() + ":获取Redis分布式锁成功");
//获取锁成功后设置失效时间
redisDao.expire(LOCK_KEY, 2, TimeUnit.SECONDS);
try {
LOG.info(Thread.currentThread().getName() + ":处理业务开始");
Thread.sleep(1000); //睡眠1000ms模拟处理业务
LOG.info(Thread.currentThread().getName() + ":处理业务结束");
//处理业务完成后删除锁
redisDao.delete(LOCK_KEY);
} catch (InterruptedException e) {
LOG.error("处理业务异常:", e);
}
} else {
LOG.info(Thread.currentThread().getName() + ":获取Redis分布式锁失败");
}
}
});
}
}
}通过上面这段代码,可能会产生以下几个疑问:
线程如果获取分布式锁失败,为什么不尝试重新获取锁?
线程获取分布式锁成功后,设置了锁的失效时间,这个失效时间长短如何确定?
线程业务处理结束后,为什么要做删除锁的操作?
针对这几个疑问,我们可以来讨论下。
第一,Redis的SETNX命令,如果key已经存在,则不会做任何操作,所以SETNX实现的分布式锁并不是可重入锁。当然,也可以自己通过代码实现重试n次或者直至获取到分布式锁为止。但是,这不能保证竞争的公平性,某个线程会因为一直等待锁而阻塞。因此,Redis实现的分布式锁更适用于对共享资源一写多读的场景。
第二,分布式锁必须设置失效时间,而且失效时间必须大于业务处理所需的时间(保证数据一致性)。所以,在测试阶段尽可能准确的预测出业务正常处理所需的时间,设置失效时间是防止因为业务处理过程的某些原因导致死锁的情况。
第三,业务处理结束,必须要做删除锁的操作。
上面设置分布式锁和为锁设置失效时间是通过两个操作步骤完成的,更合理的方式应该是把设置分布式锁和为锁设置失效时间通过一个操作完成。要么都成功,要么都失败。实现代码如下:
/**
* Redis访问工具类
*/
@Component
public class RedisDao {
private static Logger logger = LoggerFactory.getLogger(RedisDao.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 设置分布式锁
* @param key 键
* @param value 值
* @param timeout 失效时间
* @return
*/
public boolean setDistributeLock (String key, String value, long timeout) {
RedisConnection connection = null;
boolean flag = false;
try {
//获取一个连接
connection = stringRedisTemplate.getConnectionFactory().getConnection();
//设置分布式锁的同时为锁设置失效时间
connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(timeout), RedisStringCommands.SetOption.SET_IF_ABSENT);
flag = true;
} catch (Exception e) {
logger.error("set automic lock error:", e);
} finally {
//使用后关闭连接
connection.close();
}
return flag;
}
/**
* 查询key的失效时间
* @param key 键
* @param timeUnit 时间单位
* @return
*/
public long ttl (String key, TimeUnit timeUnit) {
return stringRedisTemplate.getExpire(key, timeUnit);
}
}
/**
* 单元测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class Demo1ApplicationTests {
private static final Logger LOG = LoggerFactory.getLogger(Demo1ApplicationTests.class);
@Autowired
private RedisDao redisDao;
@Test
public void testDistributeLock () {
String key = "MyDistributeLock";
//设置分布式锁,失效时间20s
boolean result = redisDao.setDistributeLock(key, "1", 20);
if (result) {
LOG.info("设置分布式锁成功");
long ttl = redisDao.ttl(key, TimeUnit.SECONDS);
LOG.info("{}距离失效还有{}s", key, ttl);
}
}
}运行单元测试类,结果如下:
2019-05-15 13:07:10.827 - 设置分布式锁成功
2019-05-15 13:07:10.838 - MyDistributeLock距离失效还有19s更多Redis相关知识,请访问Redis使用教程栏目!
Redisson实现分布式锁原理
如图所示啊,石杉大佬画的redisson分布式锁原理。
大概总结下,保证我们的key落到一个集群里,并且加锁操作是基于lua脚本的原子性操作,对于锁延迟由watch dog控制。
***隐藏网址***
如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master slave实例。但是这个过程中一旦发生redis master宕机,主备切换,redis slave变为了redis master。
接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。
此时就会 导致多个客户端对一个分布式锁完成了加锁。
这时系统在业务语义上一定会出现问题,导致各种脏数据的产生。
所以这个就是redis cluster,或者是redis master-slave架构的主从异步复制导致的redis分布式锁的最大缺陷:在redis master实例宕机的时候,可能导致多个客户端同时完成加锁。
如果主动结构redis架构模式下,我们想保证完全一致,必须重写加锁的逻辑了, 保证必须mater和slave同时加锁成功,我们整个加锁才是成功的 。
上面的2是对于单个主从结构我们可以这样干,如果假设我们有多个相对独立的master,无slave呢?我们在其中一个master上加了:locked_with_key:,然后它挂了岂不是我们的数据会在剩下的结点会重新加锁成功?
redis引入了 红锁 的概念:用Redis中的多个master实例,来获取锁,只有 大多数 实例获取到了锁,才算是获取成功 。
具体的红锁算法分为以下五步:
以上步骤来自redis 分布式锁的解释,如下
”相同的key和随机值(随机值用于唯一关系客户端和key)在N个节点上请求锁“
这里的随机数是什么?
这对于 避免删除由另一个客户端创建的锁 很重要。例如,客户端可能会获取锁,执行某些操作时被阻塞的时间超过锁的有效时间(密钥将过期的时间),然后移除已被其他客户端获取的锁。使用 just DEL 是不安全的,因为客户端可能会删除另一个客户端的锁。使用上面的脚本,每个锁都用一个随机字符串“签名”,所以只有当它仍然是客户端试图移除它时设置的锁才会被移除。
这个随机字符串应该是什么?我们假设它是 20 个字节 /dev/urandom ,但您可以找到更便宜的方法使其对您的任务足够独特。例如,一个安全的选择是用 RC4 播种 /dev/urandom ,并从中生成一个伪随机流。一个更简单的解决方案是使用具有微秒精度的 UNIX 时间戳,将 时间戳与客户端 ID 连接起来。它并不安全,但对于大多数环境来说可能就足够了。
假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:
为了应对这一问题,提出了 延迟重启 (delayed restarts)的概念。
就是,一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,这段时间应该大于锁的有效时间(lock validity time)。
这样的话,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。
客户端1在获得锁之后发生了很长时间的GC pause,在此期间,它获得的锁过期了,而客户端2获得了锁。
当客户端1从GC pause中恢复过来的时候,它不知道自己持有的锁已经过期了,它依然向共享资源( 比如 一个存储服务)发起了写数据请求,
而这时锁实际上被客户端2持有,因此两个客户端的写请求就有可能冲突(锁的互斥作用失效了)。
如何解决这个问题呢?引入了 fencing token 的概念:
首先:RedLock根据随机字符串来作为单次锁服务的token,这就意味着对于资源而言,无法根据锁token来区分client持有的锁所获取的先后顺序。
fencing token可以理解成采用全局递增的序列替代随机字符串,即 有序token ,作为锁token来使用
流程:
假设有5个Redis节点A, B, C, D, E。
这个问题用Redis实现分布式锁暂时无解。而生产环境这种情况是存在的。
时钟跳跃是可以避免的,取决于基础设施和运维; 时钟跳跃是可以避免的,取决于基础设施和运维;
redis是保持的AP而非CP,如果要追求强一致性可以使用zookeeper分布式锁,但是zookeeper也不是完全没问题,在出现网络颜值,客户端与服务端失联情况的时候也依然可能会出现分布式的问题。
Redis怎么实现分布式锁
阿粉最近迷上了 Redis,为什么呢?感觉 Redis 确实功能很强大呀,一个基于内存的系统 Key-Value 存储的数据库,竟然有这么多的功能,而阿粉也要实实在在地把 Redis 来弄一下,毕竟面试的时候,Redis 可以说是一个非常不错的加分项。
为什么需要分布式锁?
目前很多的大型项目全部都是基于分布式的,而分布式场景中的数据一致性问题一直是一个不可忽视的问题,大家知道关于分布式的 CAP 理论么?
CAP 理论就是说任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。
而我们的系统最终满足的永远都是最终一致性,而这种最终一致性,有些时候有人会喜欢问关于分布式事务,而有些人则偏重在分布式锁上。
但是阿粉选择的就是使用缓存来实现分布式锁,也就是我们在项目中最经常使用的 Redis ,谈到 Redis,那真是可以用在太多地方了,比如说:
我们今天就来实现用 Redis 来实现分布式锁,并且要学会怎么使用。
1.准备使用 Jedis 的 jar 包,在项目中导入 jar 包。
jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); 这个加锁的姿势才是我们最需要了解的,不然你用的时候都不知道怎么使用。
key:加锁的键,实际上就是相当于一个唯一的标志位,不同的业务,你可以使用不同的标志位进行加锁。
requestId:这个东西实际上就是用来标识他是哪一个请求进行的加锁,因为在分布式锁中,我们要知道一件事,就是加锁的和解锁的,必须是同一个客户端才可以。
而且还有一种比较经典的就是 B 把 A 的锁给释放了,导致释放混乱,如果你不加相同的请求,A 线程处理业务,执行了加锁,锁的过期时间是5s, B线程尝试获取锁,如果 A 处理业务时间超过5s,这时候 A 就要开始释放锁,而B在这时候没有检测到这个锁,从而进行了加锁,这时候加锁的时候,A还没处理完对应业务,当他处理完了之后,再释放锁的话,要是就是直接把 B 刚加的锁释放了,要么就是压根都没办法释放锁。
SET_IF_NOT_EXIST:看字面意思,如果 key 不存在,我们进行Set操作,如果存在,啥都不干,也就不在进行加锁。
SET_WITH_EXPIRE_TIME:是否过期
expireTime:这是给 key 设置一个过期的时间,万一你这业务一直被锁着了,然后之后的业务想加锁,你直接给一直持有这个这个锁,不进行过期之后的释放,那岂不是要凉了。
上面的方法中 tryGetDistributedLock 这个方法也就是我们通常使用的加锁的方法。
大家看到这个 script 的时候,会感觉有点奇怪,实际上他就是一个 Lua 的脚本,而 Lua 脚本的意思也比较简单。
其实这时候就有些人说,直接 del 删除不行么?你试试你如果这么写的话,你们的领导会不会把你的腿给你打断。
这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,也就是说,这锁就算不是我加的,我都能开,这怎么能行呢?
在这里给大家放一段使用的代码,比较简单,但是可以直接用到你们的项目当中
我们先把这个实现方式实现了,然后我们再来说说大家最不愿意看的理论知识,毕竟这理论知识是你面试的时候经常会被问到的。
分布式CAP理论:
加州大学伯克利分校的 Eric Brewer 教授在 ACM PODC 会议上提出 CAP 猜想。2年后,麻省理工学院的 Seth Gilbert 和 Nancy Lynch 从理论上证明了 CAP。之后,CAP 理论正式成为分布式计算领域的公认定理。
也就是说,在二十年前的时候,CAP 理论只是个猜想。结果两年之后被证实了,于是,大家在考虑分布式的时候,就有根据来想了,不再是空想了。
什么是分布式的 CAP 理论 ?
一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项
这个和(Atomicity)不太一样,因为之前看有些人说,在 CAP 理论中的 A 和数据库事务中的 A 是一样的,单词都不一样,那能一样么?
Availability :分布式中的 A 表示的是可用性,也就是说服务一直可用,而且是正常响应时间。
而你在搭建分布式系统的时候,要保证每个节点都是稳定的,不然你的可用性就没有得到相对应的保证,也谈不上是什么分布式了。只能称之为一个伪分布式。
Consistency: 一致性
也就是说你的更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,这个如果你在使用 Redis 做数据展示的时候,很多面试官都会问你,那你们是怎么保证数据库和缓存的一致性的呢?
毕竟你只是读取的话,没什么问题,但是设计到更新的时候,不管是先写数据库,再删除缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。
所以如果你对这个很感兴趣,可以研究一下,比如说:
如果你能在面试的时候把这些都给面试官说清楚,至少感觉你应该能达到你自己的工资要求。
Partition tolerance:分区容错性
分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。
其实在 CAP 理论当中,我们是没有办法同时满足一致性、可用性和分区容错性这三个特性,所以有所取舍就可以了。
关于使用 Redis 分布式锁,大家学会了么?

更多文章:
c程序设计实训教程第二版(C程序设计(第三版)与C程序设计(第二版)有什么区别都是谭浩强的那个)
2025年10月17日 06:00
phpstorm怎么保存(phpstorm 怎么设置自动上传)
2025年12月11日 13:00
怎样用html制作一个简单的网页(怎样利用记事本和HTML制作一个简单的网页)
2026年4月21日 01:15
无法打开360安全卫士(360安全卫士打不开怎么办为什么360安全卫士打不开)
2026年8月20日 04:45
美国img模特经纪公司(有谁知道国际有名的模特经纪公司有那些)
2026年8月7日 17:15
excel2010函数教案(excel函数培训教程步骤讲解)
2026年7月19日 04:45
datagridview单元格值改变事件(C# 如何在dataGridView中直接修改单元格的值)
2025年12月28日 05:45
java字符串截取后两位(JAVA截取所有指定字符后面的字符串)
2025年12月5日 23:15
soap请求(soap协议和普通的post请求有什么区别呢)
2026年5月6日 22:15
















