当前位置: 首页 > Linux

面试官问我redis锁怎么实现?我一口气告诉他3个方法!

时间:2023-04-06 22:19:39 Linux

文章每周持续更新。您的“三连冠”是对我最大的肯定。可以在微信搜索公众号“后端技术学堂”立即阅读(一般一两篇文章比博客更早更新)。春节期间,每个人都喜欢在家里抢红包。抢红包这个服务看似很简单,但实际上需要做好这项服务,尤其是跟钱有关的服务,是不允许出错的。想一想,每个红包的数量都是真金白银,这对服务的健壮性要求非常高,其中包含了很多后台服务的技术细节。今天说说其中一个技术细节,在我的另一篇文章《Linux后台开发C++学习路线技巧未展开》中也提到过,高并发服务编程中的redis分布式锁。下面介绍redis实现的三种分布式锁,并对比说明各自的特点。Redis单实例分布式锁实现一:SETNX实现分布式锁setnx用法参考redis官方文档语法SETNXkeyvalue设置key值为value,如果key不存在,此时相当于SET命令.当密钥存在时,什么也不做。SETNX是“SETifNoteXists”的缩写。返回值:1如果key设置成功,0如果key设置失败,锁步SETNXlock.foo如果客户端获取到锁,SETNX返回1,表示锁被成功的。如果SETNX返回0,则该密钥已被另一个客户端锁定。按照上一步,SETNX返回0,锁定失败。此时调用GETlock.foo获取时间戳,检查锁是否过期:如果没有过期,休眠一会再试。如果已过期,则可以获取锁。具体是:调用GETSETlock.foo根据当前时间设置新的过期时间。注意:这里设置的时候,因为SETNX和GETSET之间有一个窗口期,这段时间锁可能已经被其他客户端抢走了,所以这里需要判断GETSET的返回值,它的返回值为旧时间beforeSETStamp:如果旧的时间戳已经过期,说明加锁成功。如果旧的时间戳还没有过期(说明被其他客户端抢走了,设置了时间戳),说明加锁失败,需要等待重试。解锁步骤解锁比较简单,GETlock.foo的时间戳判断是否过期,过期后调用DEL删除lock.foo即可。实现方式二:SET实现的分布式锁的用法参考官方文档语法SETkeyvalue[EXseconds|PXmilliseconds][NX|XX]设置keykey为指定的“string”值。如果键已经包含一个值,这个操作将简单地覆盖原始值,忽略原始类型。设置命令执行成功后,之前设置的过期时间将失效。从2.6.12版本开始,redis在SET命令中增加了一系列选项:EXseconds–设置指定的过期时间,单位为秒。PXmilliseconds–设置指定的过期时间,单位为毫秒。NX–只设置key如果有notalreadyexist.XX–只有当key已经存在时才设置key。EXseconds–设置key的过期时间,单位秒PXmilliseconds–设置key的过期时间,单位毫秒existXX的值——只有当key存在时才会设置key的值Version>=6.0KEEPTTL--保留key之前的有效时间TTL加锁步骤一条命令可以加锁:SETresource_namemy_random_valueNXPX30000该命令会仅当密钥不存在时才设置密钥(NX选项),过期时间为30000毫秒(PX选项)。键设置为值“myrandomvalue”。此值在所有客户端和所有锁定请求中必须是唯一的。该命令只有在key对应的key没有resource_name时才会生效(NX选项的作用)。同时设置30000毫秒的超时时间,成功将其值设置为my_random_value,这是所有redis客户端锁请求中全局唯一的随机值。在解锁步骤中,需要保证my_random_value与加锁时一致。下面的Lua脚本可以完成ifredis.call("get",KEYS[1])==ARGV[1]thenreturnredis.call("del",KEYS[1])elsereturn0end这个Lua脚本在执行的时候你想传入前面的my_random_value作为ARGV[1]的值,传入resource_name作为KEYS[1]的值。释放锁实际上包括三个步骤:'GET'、判断和'DEL',使用Lua脚本来实现这三个步骤的原子性。Redis集群分布式锁实现三:Redlock的前两种分布式锁实现是针对单个redismaster实例,既不是互为备份的slave节点,也不是多master集群。如果是redis集群,每个redismaster节点都是独立存储的。在这种场景下,使用前面两种加锁策略存在锁的安全问题。例如下面的场景:Client1从Master那里获取锁。Master挂了,存储锁的key还没有同步到Slave。Slave升级为Master。Client2从新的Master那里获取到同一个资源对应的锁。因此客户端1和客户端2同时持有同一个资源的锁。锁的安全性被打破了。针对这种多redis服务实例的场景,redis的作者antirez设计了Redlock(DistributedlockswithRedis)算法,我们接下来会介绍。加锁步骤集群加锁的大致思路是尽量加锁所有节点。当超过一半的节点被锁定时,则表示锁定成功。集群部署你的数据可能存储在任意一个redis服务节点上。一旦锁定,必须保证集群中的任何一个节点都被锁定,否则锁定的意义就失去了。具体:获取当前时间(毫秒)。依次对N个Redis节点执行获取锁的操作。这个获取操作和前面基于单个Redis节点的锁获取过程是一样的,包括随机字符串my_random_value和过期时间(比如PX30000,就是锁的有效时间)。为了保证算法在某个Redis节点不可用时能够继续运行,锁的获取操作还有一个超时时间(timeout),这个时间远短于锁的有效时间(几十毫秒量级)).客户端从某个Redis节点获取锁失败后,应该立即尝试下一个Redis节点。这里的故障应该包括任何类型的故障,比如Redis节点不可用,或者Redis节点上的锁已经被其他客户端持有(注:Redlock原文只提到Redis节点不可用,但应该还包括其他失败案例)。计算整个获取锁的过程总共需要多长时间。计算方法是用当前时间减去第1步记录的时间。如果客户端成功从大多数Redis节点(>=N/2+1)获取到锁,并且获取锁花费的总时间不超过锁有效时间(lockvaliditytime),则客户端认为最终获取了锁定成功;否则,认为最终的锁获取失败。如果最终获取锁成功,则需要重新计算锁的有效时间,等于原锁的有效时间减去第3步计算的获取锁所消耗的时间。如果最终获取锁失败(可能是因为获取锁的Redis节点数小于N/2+1,或者整个获取锁的过程耗时比锁的初始有效时间长),客户端应该立即向所有的节点发送请求Redis节点发起释放锁的操作(即前面介绍的RedisLua脚本)。解锁步骤客户端向所有Redis节点发起释放锁的操作,不管这些节点当时获取锁是否成功。算法实现上面描述的算法已经用各种语言实现了。Redlock-rb(Ruby实现)。还有一个Redlock-rb的分支,它添加了一个gem以便于分发,也许更多/php-redis-lock(用于锁的PHP库)Redsync(Go实现).Redisson(Java实现).Redis::DistLock(Perl实现).Redlock-cpp(C++实现).Redlock-cs(C#/.NET实现).RedLock.net(C#/.NET实现)。包括异步和锁定扩展支持。ScarletLock(具有可配置数据存储的C#.NET实现)Redlock4Net(C#.NET实现)node-redlock(NodeJS实现)。包括对锁扩展的支持。比如我使用C++实现源码创建一个分布式锁管理类CRedLockCRedLock*dlm=newCRedLock();dlm->AddServerUrl("127.0.0.1",5005);dlm->AddServerUrl("127.0.0.1",5006);dlm->AddServerUrl("127.0.0.1",5007);锁定并设置超时CLockmy_lock;布尔标志=dlm->Lock("my_resource_name",1000,my_lock);锁定并保持直到释放CLockmy_lock;boolflag=dlm->ContinueLock("my_resource_name",1000,my_lock);my_resource_name是锁定标志;1000是锁的有效期,单位毫秒,锁失败返回false,锁成功返回Lock结构如下。时钟类{public:intm_validityTime;=>9897.3020019531//当前锁可以存活的时间,单位毫秒sdsm_resource;=>my_resource_name//待锁定的资源名称sdsm_val;=>53771bfa1e775//锁定资源的进程的随机名称};解锁dlm->Unlock(my_lock);总结总结起来,有3种实现方式。在单redis实例场景下,分布式锁实现1和实现2都可以。实现2更简洁,推荐使用实现2。实现3也可以,但是实现3有点复杂繁琐。在多redis实例场景下,推荐使用实现三最安全,但实现三并不完美。还有讨论该算法的缺陷(节点宕机同步延迟,时间同步假设),需要根据自己的业务场景灵活选择或使用。自定义您自己的分布式锁。参考分布式锁与Redis如何做分布式锁基于Redis的分布式锁安全吗?最后,感谢您的阅读。这篇文章的目的是分享你对知识的理解。如果文章中有明显的错误,请指出。让我们通过讨论共同学习。.原创不易,看到这里动动手指,大家的“三连”就是对我不断创作的最大支持。你可以在微信搜索公众号“后端技术学院”,回复“资讯”,里面有我为你准备的各种编程学习资料。文章每周持续更新,我们下期再见!