您的位置 首页 php

redis构建分布式锁

分布式系统要访问共享资源,为了避免并发访问资源带来错误,我们为共享资源添加一把锁,让各个访问互斥,保证并发访问的安全性,这就是使用分布式锁的原因。

一个高效分布式锁的基础:

  1. 一致性:互斥,不管任何时候,只有一个客户端能持有同一个锁。
  2. 分区可容忍性:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。
  3. 可用性:只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。

常见的redis部署有单实例和 redis sentinel集群方式,下面看下这2种部署场景,实现分布式锁的方式:

1,单redis实例

用setnx方法实现

 <?php
        $redis->multi();
        $redis->setNX($key, $value);
        $redis->expire($key, $ttl);
        $redis->exec();
?>  

但当sentnx成功,expire失败,就会产生死锁,这时需要借助lua脚本(保证原子性)实现

 local key   = KEYS[1]
local value = KEYS[2]
local ttl   = KEYS[3]

local ok = redis.call('setnx', key, value)
 
if ok == 1 then
  redis.call('expire', key, ttl)
end
 
return ok  

从 2.6.12 起,s et包含了setex功能,set key value NX PX 毫秒就能实现加锁并设置过期时间的功能,在日常开发中,我们大多数采用了此种实现方式来实现分布式锁,锁的value采用随机数,可由uuid+客户端id生成,防止客户端误删除其它客户端建立的锁。

此方案大多数情况可行,但有风险点

1,redis未开启备份,当设置了锁,redis宕机重启其它客户端可重新获取锁,redis官方建议锁TTL时间后重启,这种方案锁安全但是影响系统不可用。

2, AOF持久化采用每秒执行一次fsync,这种持久化最多会丢失2秒数据,和上述1情况有类似的问题。

3, 如果我们想在Redis重启的任何情况下都保证锁的安全,我们必须开启fsync=always的配置,但是大大影响了系统的性能。

如果能够接受1和2此种锁失效的风险,就可以使用此方案。

2,redis sentinel集群

实现锁和单实例场景类似,也是使用set key value NX PX 毫秒方法,此方法在集群下仍有风险:

集群都是多实例, 如果客户端一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。客户端二就可以获取同个key的锁,但客户端一也已经拿到锁了,锁的安全性就没了。

这种故障时间少且正好发生在切换,假如业务能接受这种故障,也可使用此方案。

综上2种redis部署场景,set key value NX PX 毫秒这种加锁方式都有不足的地方, 为了解决这个问题,Redis作者 antirez提出一种高级的分布式锁算法:Redlock。

RedLock算法思想,不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁, n/2+1 ,必须在大多数redis节点上都成功创建锁,才能算这个整体的RedLock加锁成功,避免说仅仅在一个redis实例上加锁而带来的问题,当其中一台redis宕机重启后,也需要在TTL时间后重启,防止其它客户端重复获取锁,RedLock算法具体实现和 redisson方案 可网上搜索。

此算法满足分布式锁的要求,并且弥补了单实例下分布式锁的不足,单实例下宕机情况下在TTL情况下重启其实也能满足锁的需要,但是这段时间内redis服务不可用,影响系统可用性,此算法只要n/2+1能获取到锁,就认为锁获取成功,提高了锁获取的容错性。

RedLock方案相比普通的Redis分布式锁方案 可靠性 确实大大提升。但是,任何事情都具有两面性,因为我们的业务一般只需要一个Redis Cluster,或者一个Sentinel,但是这两者都不能承载RedLock的落地。如果你想要使用RedLock方案,还需要专门搭建一套环境。所以,如果不是对分布式锁可靠性有极高的要求(比如金融场景),不太建议使用RedLock方案。

文章来源:智云一二三科技

文章标题:redis构建分布式锁

文章地址:https://www.zhihuclub.com/151875.shtml

关于作者: 智云科技

热门文章

网站地图