Redis实现分布式锁及Redisson
Redis实现分布式锁及Redisson
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
在分布式系统中,常常需要协调他们的动作,若不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。
例子:商品秒杀超卖
1. 无锁
这是一个订单库存的例子,库存stock为1,这里很容易会发现容易造成多次下单成功的错误。
2. 加同步锁
这里在单机情况下确实能够满足不出现超卖的问题。
缺点:如果作Nginx进行负载均衡+分布式集群部署,依然会出现超卖问题。
- 原因是同步锁
synchronized
是JVM级别的,每台服务器在并发情况下,只能锁住一个线程。
所以,如何处理这种分布式的情况呢?
3. Redis or Zookeeper
由于系统已经使用到了Redis,为了系统的轻量避免冗余引入新组件,选择通过Redis来进行实现分布式锁。
Redis实现分布式锁
1. SETNX和SET NX命令
[Windows版Docker安装Redis教程(保姆级),适合开发环境快速提供Redis服务_windows docker 安装redis-CSDN博客](https://blog.csdn.net/BXD19931010/article/details/135065606?ops_request_misc=&request_id=&biz_id=102&utm_term=windows docker安装redis&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-135065606.142^v100^pc_search_result_base8&spm=1018.2226.3001.4187)
1 | 创建容器并执行 |
Redis
实现分布式锁的核心便在于SETNX
命令,它是SET if Not eXists
的缩写,如果键不存在,则将键设置为给定值,在这种情况下,它等于SET;当键已存在时,不执行任何操作;成功时返回1,失败返回0
但setnx
不能同时完成expire
设置失效时长,不能保证setnx
和expire
的原子性。我们可以使用set
命令完成setnx
和expire
的操作,并且这种操作是原子操作。
下面是set
命令的可选项:
1 | set key value [EX seconds] [PX milliseconds] [NX|XX] |
使用示例:
两次插入相同键不同值,第一次返回成功,第二次返回失败:
设置过期时间(十秒):
2. 通过Redis的SETNX实现分布式锁
命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法
1 | 给lock设置了过期时间为60000毫秒(也可以用ex 6000,单位就变成了秒),当用NX再次赋值,则返回nil,不能重入操作 |
如果setnx 返回ok 说明拿到了锁;如果setnx 返回 nil,说明拿锁失败,被其他线程占用。
换成客户端服务器则是如下:
- 客户端执行以上的命令:
- 如果服务器返回 OK ,那么这个客户端获得锁。
- 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
问题一:为什么需要PX/XX设置超时时间?
答:如果第一个set的进程A不讲道理/突然宕机,锁永远释放不了,导致系统中其他机器的其他线程谁也拿不到锁。
问题二:设置了超时时间还有什么问题吗?
- 如果第一个set的进程A又不讲道理,业务步骤时间超过设置的超时时间,那么就会导致其他进程拿到锁(趁虚而入),导致超卖问题。
- 同时,等进程A回来了,回手就是把其他进程的锁删了/释放其他线程的锁,就会更加趁虚而入导致超卖。
解决办法:
- 加长锁的过期时间,并添加子线程每隔10秒确认主线程是否在线,如果在线则将过期时间重新设置(给锁续命)。
- 给锁加上UUID(锁与线程的唯一性),这样就不会释放别人的锁了。
以上解决方案要确保代码的正确性、健壮性还是比较麻烦的,在此引入Redisson
工具。
Redisson
Redisson原理
这里的原理就是上面提到的那两点。
问题及解决方案
因为Redis
是满足AP
(高可用+分区容错),当Redis
采用集群(主从节点),这里的加锁只会往Redis
的一个节点去加锁,比如对主节点加锁了,这时主节点会去从节点那进行锁状态的同步。倘若这时主节点挂掉了,从节点没有有效同步,依然会发生线程不安全的情况。
解决方案:
采用
redlock
(红锁)实现:对Redis
的所有节点都进行加锁后,才返回响应。