分散ロックの深さ分析をRedisの

転送https://www.jianshu.com/p/d0caf5d99e56

ロックは、主に(setnxコマンド・インタプリタは、次を参照してくださいsetnxコマンドのRedisのに基づいているのRedisを達成分散http://doc.redisfans.com/string/setnx.html)、我々はsetnxでコマンドを見て:

 
Redisの-setnx.png

 

1、Redisの基本的分散ロック

ロックコマンドをRedisの:

SETNX resource_name my_random_value PX 30000

これは、このキーの値が存在しないだけで、キー(NXオプションの役割)、タイムアウトが設定されている(PXアクションオプション)000ミリ秒キーの値が「my_random_value」に設定されている場合にのみ、コマンドセットの役割があります。この値は、ロックの内部を取得するすべてのクライアント要求で一意である必要があります。

SETNX値は唯一のリリースがロックを取得し、他のクライアントの誤って削除を防ぐために、安全ロックを確保することである残っています。たとえば、クライアントがロックを取得するには、アクションを長時間ブロックされた、タイムアウト後にロックを自動解除し、クライアント側と、その後は他のクライアントを取得することであったこの事実を削除しようロック。キャラクターの署名 "のランダムな文字列で、各クライアントは、それぞれのロックのみロックを取得できるようにすることを確実にするために、この値をチェックすることにより、ロックの他のクライアントを削除するには、クライアント原因の簡単なコマンドDELを使用することが可能であるので、クライアントが削除されました。

この値をチェックする必要ロックが解除され、ロックを解除する必要がされたとき、あなたは原子性を確保する必要が両方あるので、我々はLUAスクリプトによってアトミック操作を実現するためのサポートがアトミック、Luaのスクリプトを実行するのRedis。コードは以下の通りであります:

if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end 

2、ビジネスロジックの実行時間は、ロックを保持すると同時に、ロックのタイムアウト制限の問題を引き起こす2つのクライアントを超えます

ロックとそのロックのタイムアウト制限を超えて、あまりにも長いロックの実行を解除する間の論理た場合、問題が発生します。最初のスレッドがロックの有効期限が切れて保持しているこの時間は、ロジック重要な領域が行われていないので、先に第二のスレッドのこの時間は、厳密に逐次実行することはできませんコードのクリティカルセクションで、その結果、再びこのロックを保持しています。

不难发现正常情况下锁操作完后都会被手动释放,常见的解决方案是调大锁的超时时间,之后若再出现超时带来的并发问题,人工介入修正数据。这也不是一个完美的方案,因为但业务逻辑执行时间是不可控的,所以还是可能出现超时,当前线程的逻辑没有执行完,其它线程乘虚而入。并且如果锁超时时间设置过长,当持有锁的客户端宕机,释放锁就得依靠redis的超时时间,这将导致业务在一个超时时间周期内不可用。

基本上,如果在执行计算期间发现锁快要超时了,客户端可以给redis服务实例发送一个Lua脚本让redis服务端延长锁的时间,只要这个锁的key还存在而且值还等于客户端设置的那个值。 客户端应当只有在失效时间内无法延长锁时再去重新获取锁(基本上这个和获取锁的算法是差不多的)。

当锁超时时间快到期且逻辑未执行完,延长锁超时时间的伪代码:

if  redis.call("get",KEYS[1]) == ARGV[1] then redis.call("set",KEYS[1],ex=3000) else getDLock();//重新获取锁 

3、redis的单点故障主从切换带来的两个客户端同时持有锁的问题

生产中redis一般是主从模式,主节点挂掉时,从节点会取而代之,客户端上却并没有明显感知。原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。然后从节点变成了主节点,这个新的节点内部没有这个锁,所以当另一个客户端过来请求加锁时,立即就批准了。这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生。

不过这种不安全也仅仅是在主从发生 failover 的情况下才会产生,而且持续时间极短,业务系统多数情况下可以容忍。

4、RedLock算法

如果你很在乎高可用性,希望挂了一台 redis 完全不受影响,可以考虑 redlock。 Redlock 算法是由Antirez 发明的,它的流程比较复杂,不过已经有了很多开源的 library 做了良好的封装,用户可以拿来即用,比如 redlock-py。

import redlock

addrs = [{
  "host": "localhost", "port": 6379, "db": 0 }, { "host": "localhost", "port": 6479, "db": 0 }, { "host": "localhost", "port": 6579, "db": 0 }] dlm = redlock.Redlock(addrs) success = dlm.lock("user-lck-laoqian", 5000) if success: print 'lock success' dlm.unlock('user-lck-laoqian') else: print 'lock failed' 

RedLock算法的核心原理:

使用N个完全独立、没有主从关系的Redis master节点以保证他们大多数情况下都不会同时宕机,N一般为奇数。一个客户端需要做如下操作来获取锁:

1.获取当前时间(单位是毫秒)。
2.轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。
3.客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁((N/2) +1),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
4.如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
5.如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

5、知识扩展

5.1为什么lua脚本结合redis命令可以实现原子性

Redis 提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题。Redis 为这样的用户场景提供了 lua 脚本支持,用户可以向服务器发送 lua 脚本来执行自定义动作,获取脚本的响应数据。Redis 服务器会单线程原子性执行 lua 脚本,保证 lua 脚本在处理的过程中不会被任意其它请求打断。


 
redis-lua交互--.png

5.2 redis 可重入分布式锁

要实现可重入锁,方法很简单,当加锁失败时判断锁的值是不是跟当前线程设置值相同,伪代码如下:

if setnx == 0 
      if get(key) == my_random_value //重入 else //不可重入 else //获取了锁,等价于可重入 

参考文档:



著者:ホー・スアンhexuanの
リンクします。https://www.jianshu.com/p/d0caf5d99e56
出典:ジェーンの本は
、著者が著作権を保有しています。著者は認可商業転載してください接触、非商用の転載は、ソースを明記してください。

おすすめ

転載: www.cnblogs.com/KQNLL/p/12004650.html