1.Redis怎样用它来加锁和解锁,锁超时风险。数据库的乐观并发控制,通过版本号来解决。
2.数据库用 CAS,就没必要分布式锁
3.分布式锁服务能用来做同步,数据库锁不能
一、Redis 分布式锁
1、避免死锁
(1)对资源加锁:SET resource_name my_random_value NX PX 30000
1)SET NX 命令:只会在 key 不存在的时候给 key 赋值,
2)PX 命令:通知 Redis 保存这个 key30000ms(存活时间,称作锁过期时间,超过这个时间时,锁将自动释放)。没在时间内完成,其他客户端获得锁,引起争用问题。
(3)my_random_value :全局唯一值。随机数释放锁时保证安全性。
原理:某个 key 不存在,才能设置成功。多个进程设置同一个 key,一个成功,其它失败。
2、解锁:
key 对应value 一致,删除 key。这样释放锁是避免释放其他 Client 申请锁。
(2)不区分Client问题:
1.Client A 获得锁。
2.释放锁请求给 Redis 时被阻塞,没有及时到达 Redis。
3.超时,Redis 认为锁到期,释放锁。
4.B申请到,A 解锁请求到达,将B锁定key 解锁。
5.C 获得,B C 同时持有锁。
(3)关于 value 的生成,
(1)官方:/dev/urandom 中取 20 个 byte 随机数。
(2)更简单: RC4 加密算法在 /dev/urandom 中得到一个种子(Seed),生成伪随机流;
(3)简单:使用时间戳 + 客户端编号生成随机数。 (安全性较差一些,大多数的场景足够安全 )
2、分布式锁服务的一个问题
避免Client占锁不放,Redis超时后把其释放掉。
(1)Client A 取锁,B等A完成。
(2)A 被外部阻塞调用,或CPU被进程吃满,或Full GC,A 超时,怕死锁,锁释放
(3)B 获得锁且更新
(4)A 缓过来去更新。B 更新被冲掉。数据出错。HBase 就曾经遇到
解决:引入 fence(栅栏)。乐观锁机制,需要递增版本号排它。下图
写时带上自己版本号。数据库服务需要保存数据版本号,检查请求
如用 ZooKeeper 做锁服务,用 zxid 或 znode 版本号来做 fence版本号。
二、从乐观锁到 CAS
如数据库保留版本号,用数据库锁不更方便了吗?
数据版本(Version)记录机制,数据库表增加version字段。读时将 version 字段读出,更新一次 version 加一。
提交更新时,当前版本与第一次version 比对。相等更新,否则过期
SQL:UPDATE table_name SET xxx = #{xxx}, version=version+1 where version =#{version};乐观锁常用实现方式。版本号,或fence token不需要分布式锁。
fence token 玩法,数据库用 timestamp 时间截。更新提交时,和更新前时间戳对比,一致则 OK,否则冲突。
更新库存: 库存数量(stock)查出,更新时,查一下是否是上次读出。不是失败,重新再来。
SELECT stock FROM tb_product where product_id=#{product_id};
UPDATE tb_product SET stock=stock-#{num} WHERE product_id=#{product_id} AND stock=#{stock};
三、分布式锁服务能用来做同步,数据库锁不能
计算机汇编指令中原子操作 CAS(Compare And Swap),大量无锁数据结构都用到。(关于 CAS 可以CoolShell上写无锁队列实现 )。
从分布式锁服务到乐观锁,再到 CAS,还需要分布式锁服务吗?
不需更新,只同步或是互斥不同机器上线程,Redis 分布式锁就有意义。
CAS (无锁方式)更新数据,不需要使用分布式锁服务,
分布式锁设计的重点:
集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。分布式互斥:某个事时,去服务上请求标识。请求到操作,完成后把标识还回去,别进程可请求。
死锁问题,ZooKeeper 靠自身sessionTimeout 删除节点。
RedLock高可用:非阻塞方式锁服务。考虑锁可重入性。
Redis 也不错,ZooKeeper变通方式, Apache 有Curator封装了各种分布式锁玩法。
在哪些场景下会用到锁?有没有用到数据库锁?是OCC,还是悲观锁?如果是悲观锁的话,又是怎样避免死锁的?