基于单Redis节点的分布式锁

来源:互联网 发布:摄像头阅卷软件 编辑:程序博客网 时间:2024/06/05 11:33

场景:某个业务接口,因为做了比较复杂的数据库查询并且调用量很大,所以加了缓存,并设定缓存过期后刷新。问题是缓存过期的瞬间,大量并发请求会穿透缓存直接查询数据库,造成雪崩效应,如果有锁机制,那么就可以控制只有一个请求去更新缓存,其它的请求视情况要么等待,要么使用过期的缓存。

下面以PHPRedis 扩展为例,实现一段演示代码:

<?php$redis = new Redis();$cache = new Cache();$random = microtime(true);$ttl = 3000;$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));if($ok){    $cache->update();    if($redis->get($key) == $random)    {        $redis->del($key);    }}$data = $cache->get();?>

问题一:为什么设置过期时间?


当一个客户端获取锁成功之后,假如它崩溃了,那么它就会一直持有这个锁,而其它客户端永远无法获得锁了,缓存就永远无法更新了。


问题二:网上不少时间设置锁的过期时间用了以下代码,存在什么问题?


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

这段代码是有问题的:当多个请求到达时,虽然只有一个请求的setnx可以成功,但是任何一个请求的expire却都可以成功,如此就意味着即便获取不到锁,也可以刷新过期时间,如果请求比较密集的话,那么过期时间会一直被刷新,导致锁一直有效。其实Redis从2.6.12起,SET涵盖了SETEX的功能,并且SET本身已经包含了设置过期时间的功能,所以我们只需要用set设置锁的过期时间就好了。


问题三:为什么要设置一个随机数?


因为它保证了一个客户端释放的锁必须是自己持有的那个锁。设想一下,如果一个请求更新缓存的时间比锁的有效期还要长,导致在缓存更新过程中,锁就失效了,此时另一个请求会获取锁,如果不加以判断直接删除锁,就会出现误删除其它请求创建的锁的情况。


问题四:假如Redis节点宕机了该怎么办?


假如Redis节点宕机了,那么所有客户端就都无法获得锁了,服务变得不可用。为了提高可用性,我们可以给这个Redis节点挂一个Slave,当Master节点不可用的时候,系统自动切到Slave上。但由于Redis的主从复制是异步的,客户端1从Master获取了锁,此时Master宕机了,存储锁的key还没有来得及同步到Slave上,Slave升级为Master,然后客户端2从新的Master获取到了对应同一个资源的锁。针对这个问题,antirez设计了Redlock算法,基于篇幅较长,有兴趣的自己可以研究一下,加深对分布式锁的理解。

1 0