Redis分布式锁
本次参考自redis的官方文档,主要作为自己学习的一个记录,并能够整理和精化其内容,希望大家指正。
分布式锁
分布式锁在很多高并发的场景中使用,比如电商中的秒杀活动、限量的优惠券的抢购等等,这样的一个场景特点就是访问量极具增长。尽管系统使用了限流、异步、排队的方式优化,但是整体还是没有达到效果,为了避免逻辑错误,这些系统往往都会使用到锁,比如一共有九十九张百元优惠券,但是抢券的人数达到了五万多人,这样的场景显然使用到分布式锁,每当有一个进入券系统并拿到了锁,那么其他人就只能等待系统将券减一,并释放掉锁。
因此一般一个安全的分布式锁应该具备以下特征:
- 互斥性。互斥是锁的基本特征,同一时刻只能被一个线程持有,执行临界区操作。
- 超时释放。通过超时释放,避免死锁
- 可重入性。一个线程在持有锁的情况下可以再次请求加锁。防止锁没操作完就提前释放。
- 高性能和高可用。加锁和释放锁的过程尽量开销低。
其中实现的方式有很多,一般为:
分布式锁类型 | 特征 |
---|---|
Memecached分布式锁 | 利用其add命令,该命令是原子操作,只有在key不存在的情况下,才能add成功,也就意味线程得到了锁 |
Zookeeper分布式锁 | 利用其顺序临时节点,来实现分布式锁和等待队列。该框架专门为分布式应用提供方案 |
Chubby锁 | Google实现的粗粒度分布式锁服务,通过sequencer机制解决了请求延迟造成的锁失效问题 |
Redis分布式锁 | 基于Redis单机实现的分布式锁,其方式和memecached很像,利用redis的SERNX命令,实现了原子操作,只有在key不存在的情况下,才能set成功。 |
Redis分布式锁演变
上面我们也看到了通过redis实现的分布式锁,具体的实现细节可以通过Redsync.go或者Redisson查看。
其实实现redis分布式锁很简单,就是在redis创建一个key,这个key设置一个失效时间(TTL),以保证锁最后能释放,确保不会产生死锁。当客户端释放资源的时候,就会删除掉这个key。
虽然表面上看效果不错,但是还是有个问题:这个架构中存在一个严重的单点失败问题,如果redis挂掉了,在增加slave作为主节点的过程中,有其他线程来获取slave节点的锁,就会出现安全失效的情况。
因此,在尝试克服上述单实例设置的限制之前,让我们使用原子锁的命令
SET resource_name my_random_value NX PX 30000
这个命令仅在不存在key的时候才能被执行成功(NX选项),并且这个key有一个30秒的自动失效时间(PX属性)。这个key有个随机值“my_random_value“,这个值在所有的客户端必须是唯一的,所有同一个key的获取者这个值都不能一样。
value值必须是随机的主要原因是为了更安全的释放锁,释放锁的时候使用脚本告诉redis:只有key存在并且存储的值和我指定的一样才能删除成功。使用这个方式释放锁主要是为了避免别的客户端获取成功的锁。
Redlock算法
在redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。我们在五台机器或者五台虚拟机上运行这些实例,这样就能保证他们不会同时宕机。
为了取得锁,客户端应该执行以下操作:
- 获取当前的Unix时间,以毫秒为单位
- 依次尝试从N个实例,使用相同的key和随机值来获取锁。其中redis设置锁的时候,超时时间应该小于锁的失效时间。这样就避免了服务器端挂掉了,客户端不会死死的等待结果。如果服务器没有响应,客户端会尝试其他redis实例。
- 客户端使用当前时间剪去第一步获取到的时间就是锁使用的时间。当且仅当大多数的redis节点都取得锁,并且使用的时间小于锁失效时间,锁才算获取成功。
- 如果取得了锁,key的真正有效时间=有效时间-获取锁使用的时间
- 如果获取锁失败了,没有在至少N/2+1个redis实例取得锁或者取锁时间已经超过有效时间。则客户端应该在所有的redis实例上进行解锁。
总结
本次的文章编写,主要是在梳理系统的内容中,查看到了redis锁的使用,便展开进行了了解,这也算大致对其原理有了些了解,下面就准备去看源码了。奥力给!!!