Redis分布式锁的简单实现分析

一 前言

再看交易相关的代码时,发现了采用了Redislock,整理一下这块的知识点。

二  业务现状:

常见的分布式锁是为了解决不同进程需要互斥地访问共享资源。常见的业务场景有“秒杀”。

对于账单来说,存在个订单多个账单的情况,而且不同的业务线字段不同,不能依靠数据库两个字段简单的唯一性索引约束。

对性能要求高,允许偶发的失败。


三 Redis实现分析

首先是分布式锁几个关键指标:
  1. 安全属性:互斥,不管任何时候,只有一个客户端能持有同一个锁。
  2. 效率属性A:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。
  3. 效率属性B:容错,只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。
Redis实现的基本原理:在Redis实例里创建一个键值,创建出来的键值一般都是有一个超时时间的(这个是Redis自带的超时特性),所以每个锁最终都会释放。
而当一个客户端想要释放锁时,它只需要删除这个键值即可。
注意点:
  • 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
 SET resource_name my_random_value NX PX 30000命令举例,实际使用过程中Java可用封装的Djedis来操作set
setnx是SET if Not eXists,只有这个key不存在的时候才会设置这个key的值。px就是超时时间。锁的value这个值必须在所有获取锁请求的客户端里保持唯一。 基本上这个随机值就是用来保证能安全地释放锁。
  • 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
  • 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
删除的时候djedis.get(key)获取锁的value,与传入的锁的value判断下是否相等。
可能存在问题:就是在超时的临界点,主动释放锁(删除)的时候,由于所过期被释放了,新的线程获取了锁,接着新锁也被释放了。
代码网上很多,就不在贴出来了。

四 其他实现方案:

除了刚才的问题,潜在风险还有Redis的单点风险,如果master节点宕机了,由于Redis是异步同步的,slave节点可能没有同步到刚才的信息。
所以造成多个客户端同时持有锁。
Redis还有一种ReadLock的方式来避免这种问题。它需要(N=5)个Redis节点

一个客户端需要做如下操作来获取锁:

1.获取当前时间(单位是毫秒)。

2.轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。

比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。

3.客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。

4.如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。

5.如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

可靠性是更高了,要求也高,性能肯定比单节点的redis锁要差(因为要5个节点去计算)。

更可靠的是zookeeper分布式锁。

对比下:zk锁的优点是不依靠超时时间释放锁,可靠性高,缺点:性能比不上redis锁,要频繁的创建节点,删除节点。

还是要结合业务来。





你可能感兴趣的:(并发,并发系列整理)