千家信息网

怎么使用redis实现分布式锁

发表于:2024-10-19 作者:千家信息网编辑
千家信息网最后更新 2024年10月19日,小编给大家分享一下怎么使用redis实现分布式锁,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1.加锁最简单的方法是使用s
千家信息网最后更新 2024年10月19日怎么使用redis实现分布式锁

小编给大家分享一下怎么使用redis实现分布式锁,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

1.加锁

最简单的方法是使用setnx命令。key是锁的唯一标识,按业务来决定命名。比如想要给一种商品的秒杀活动加锁,可以给key命名为 "lock_hot_商品ID" 。而value设置成什么呢?我们可以姑且设置成1。加锁的伪代码如下:

setnx(key,1)

当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁;当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败。

2.解锁

有加锁就得有解锁。当得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行del指令,伪代码如下:

del(key)

释放锁之后,其他线程就可以继续执行setnx命令来获得锁。

3.锁超时

锁超时是什么意思呢?如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程再也别想进来。
所以,setnx的key必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。setnx不支持超时参数,所以需要额外的指令,

伪代码如下:

expire(key, 30)

4.综合分析

我们分布式锁实现的第一版伪代码如下:

if(setnx(key,1) == 1){    expire(key,30)    try {        do something ......    } finally {        del(key)    }}
  • 上面的伪代码中,存在着三个致命问题:

1. setnx和expire的非原子性

设想一个极端场景,当某线程执行setnx,成功得到了锁:

setnx刚执行成功,还未来得及执行expire指令,节点1 Duang的一声挂掉了。

这样一来,这把锁就没有设置过期时间,变得"长生不老",别的线程再也无法获得锁了。
怎么解决呢?setnx指令本身是不支持传入超时时间的,但是Redis 2.6.12以上版本为set指令增加了可选参数,伪代码如下:

set(key,1,30,NX)
  • 这样就可以取代setnx指令。

2. del 导致误删

又是一个极端场景,假如某线程成功得到了锁,并且设置的超时时间是30秒。

如果某些原因导致线程A执行的很慢很慢,过了30秒都没执行完,这时候锁过期自动释放,线程B得到了锁。

随后,线程A执行完了任务,线程A接着执行del指令来释放锁。但这时候线程B还没执行完,线程A实际上删除的是线程B加的锁。

怎么避免这种情况呢?可以在del释放锁之前做一个判断,验证当前的锁是不是自己加的锁。
至于具体的实现,可以在加锁的时候把当前的线程ID当做value,并在删除之前验证key对应的value是不是自己线程的ID。
加锁:

String threadId = Thread.currentThread().getId()set(key,threadId ,30,NX)

    解锁:

    if(threadId .equals(redisClient.get(key))){    del(key)}

      但是,这样做又隐含了一个新的问题,判断和释放锁是两个独立操作,不是原子性。
      我们都是追求极致的程序员,所以这一块要用Lua脚本来实现:

      String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));

        这样一来,验证和删除过程就是原子操作了。

        3. 出现并发的可能性

        还是刚才第二点所描述的场景,虽然我们避免了线程A误删掉key的情况,但是同一时间有A,B两个线程在访问代码块,仍然是不完美的。
        怎么办呢?我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁"续航"。

        当过去了29秒,线程A还没执行完,这时候守护线程会执行expire指令,为这把锁"续命"20秒。守护线程从第29秒开始执行,每20秒执行一次。

        当线程A执行完任务,会显式关掉守护线程。

        另一种情况,如果节点1 忽然断电,由于线程A和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续命,也就自动释放了。

        以上是"怎么使用redis实现分布式锁"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

        线程 指令 代码 时间 成功 任务 分布式 原子 场景 情况 篇文章 验证 这样一来 一来 两个 内容 参数 命令 商品 时候 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 服务器开机响 数据库中最后一行 铁路网络安全红线管理包括 应用软件开发大赛中文官方版 线性代数对软件开发的重要性 数据库 授权查询最大值 该行为违反网络安全法微信封号 移动it网络技术岗位职责 中国网络安全企业强2019 数据库系统工程师的面试题目 深圳应用软件开发中心 在线文档用什么做数据库 北印网络安全 菏泽蚁巢网络技术有限公司 重庆交通大学网络安全学院专业 电脑直连服务器管理口 centos创建数据库 达内学软件开发培训课程 我的世界创建服务器 大学智慧芽数据库 哈利波特一个服务器才能一起玩吗 互联网时代 网络安全问题 黑客与网络技术的关系 分组交换网络技术特点 冀中能源网络安全防护 上海凝德网络技术有限公司 蚌埠oa管理软件开发费用 查看系统中所有数据库 福田区网络技术转移代理价钱 移动网络技术百科
        0