分布式锁

2020-08-15  本文已影响0人  YDDMAX_Y

分布式锁需要解决下面问题

  1. 互斥性
  2. 续命(比如想延长redis锁的加锁时间)
  3. 锁释放
  4. HA一致性(比如redis主备切换导致锁丢失不互斥了)
    下面列出几种分布式锁的实现和对比。
方案 互斥性 锁释放 HA一致性 续命 其他优点 其他缺点
关系型数据库 很好 很好(事务保证) 很好需要配置数据库在完成主备同步之后才返回上层sql成功 不需要续命(不需要像KV方案那样设置expireTime) 互斥性和锁释放指标很好 因为加锁期间整个事务都要维持会造成事务时间很长,暂用宝贵连接
KV存储 一般(如果获得锁之后因为load高或者网络差等造成业务处理时间超过expireTime会造成别的请求可以再次获得锁,造成锁不互斥) 一般(像应用程序宕机等极端情况需要等待expireTime才能释放锁) 主备同步完成之后才返回生成成功,对于redis可以使用wait 延长expire 互斥性和锁释放相对其他两种方案稍差 互斥性和锁释放在可接受范围内,而且性能很好
ZK 很好 很好(会话保证) ZAB协议保证 不需要续命(不需要像KV方案那样设置expireTime) 互斥性和锁释放指标很好 ZK的性能较低

数据库锁(有多种方案,这里只考虑在一个事务里方案)

  1. 加锁
    开启事务并insert
  2. 解锁
    rollback

KV数据库锁(以redis举例)

参考:https://help.aliyun.com/document_detail/146758.html

  1. 加锁
    SET resource_1 random_value NX EX 5
  2. 续命
    在Redis中通常需要用Lua脚本来实现自锁自解:
    在Redis中可使用如下Lua脚本来实现续租:
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("expire",KEYS[1], ARGV[2])
else
    return 0
end
  1. 解锁
    在Redis中通常需要用Lua脚本来实现自锁自解:
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
  1. HA一致性
    Redis的WAIT命令会阻塞当前客户端,直到这条命令之前的所有写入命令都成功从master同步到指定数量的replica,命令中可以设置单位为毫秒的等待超时时间。在云Redis版中使用WAIT命令提高分布式锁一致性的示例如下:
SET resource_1 random_value NX EX 5
WAIT 1 5000

使用以上代码,客户端在加锁后会等待数据成功同步到replica才继续进行其它操作,最大等待时间为5000毫秒。执行WAIT命令后如果返回结果是1则表示同步成功,无需担心数据不一致。相比红锁,这种实现方法极大地降低了成本。

需要注意的是:

WAIT只会阻塞发送它的客户端,不影响其它客户端。
WAIT返回正确的值表示设置的锁成功同步到了replica,但如果在正常返回前发生高可用切换,数据还是可能丢失,此时WAIT只能用来提示同步可能失败,无法保证数据不丢失。您可以在WAIT返回异常值后重新加锁或者进行数据校验。
解锁不一定需要使用WAIT,因为锁只要存在就能保持互斥,延迟删除不会导致逻辑问题。

ZK锁

每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。
判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。
当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。

上一篇下一篇

猜你喜欢

热点阅读