分布式锁的本质
就是在所有进程都能访问到的一个地方,设置一一个锁资源, 让这些进程都来竞争锁资源。数据库、zookeeper, Redis。
通常对于分布式锁,会要求响应快、性能高、与业务无关。
Redis实现分布式锁
- SETNX key value: 当key不存在时,就将key设置为value,并返回1。如果key存在,就返回0。
- EXPIRE key locktime: 设置key的有效时长。
- DEL key: 删除。
- GETSET key value: 先GET, 再SET, 先返回key对应的值,如果没有就返回空。然后再将key设 置成value。
简单的分布式锁
SETNX加锁, DEL解锁。
问题: 如果获取到锁的进程执行失败,他就永远不会主动解锁,那这个锁就被锁死了。
解决方法: 给锁设置过期时长。
问题: SETNX 和EXPIRE并不是原子性的,所以获取到锁的进程有可能还没有执行EXPIRE指令,就挂了,这时锁还是会被锁死。
解决方法: 将锁的内容设置为过期时间(客户端时间+过期时长),SETNX获取锁失败时,拿这个时间跟当前时间比对,如果是过期的锁,就先删除锁,再重新上锁。
问题: 在高并发场景下,会产生多个进程同时拿到锁的情况。
解决方法: SETNX失败后,获取锁上的时间戳,然后用GETSET, 将自己的过期时间更新上去,并获取旧值。如果这个旧值,跟之前获得的时间戳是不一致的,就表示这个锁已经被其他进程占用了,自己就要放弃竞争锁。
1 | public boolean tryLock(RedisConnection conn){ |
分析一下:
上面各种优化的根本问题在于SETNX和EXPIRE两个指令 无法保证原子性。Redis2.6提供了 直接执行Lua脚本的方式,通过Lua脚本来保证原子性。redission。