未加星标

基于分布式原理解析Redis分布式锁的实现

字体大小 | |
[数据库(综合) 所属分类 数据库(综合) | 发布者 店小二05 | 时间 2018 | 作者 红领巾 ] 0人收藏点击收藏
用Redis作为分布式锁是个轻量级的解决方案, 但很多同学在使用过程中并未弄明白其中的优劣, 知其然不知其所以然, 反而引入了一些那一排查的线上故障. 这里针对Redis分布式锁常用的几种方式, 从原理触发, 分析其适用场景及潜在缺陷 先回顾一下LESLIE LAMPORT大神在其1977年论文 Proving the Correctness of Multiprocess Programs 中对于分布式系统正确性的定义 Correctness == Safety and liveness Safety (安全性)
safety properties informally require that "something bad will never happen" in a distributed system or distributed algorithm Liveness (活性)
liveness properties refers to a set of properties of concurrent systems, that require a system to make progress despite the fact that its concurrently executing components ("processes") may have to "take turns" in critical sections , parts of the program that cannot be simultaneously run by multiple processes
知乎大神翻译了数学证明, 有兴趣的同学可以自取 对于使用分布式锁的正确性, 我们不妨如下界定 Safety
同一时间只有一个进程可以获得锁
没有获得锁的其他进程应该被正确的置为获取锁失败的状态 Liveness
获得锁的进程最终应该释放锁, 让其他进程可以再次尝试获取 下面从三个阶段来分析Reids作为分布式锁的用法 锁的获取; 锁的持有; 锁的释放; 先看锁的获取 // redis version < 2.6.12 SETNX lock_key lock_value EXPIRE lock_key ttl_with_seconds

// redis version >= 2.6.12 SET lock_key lock_value NX PX ttl_with_seconds Redis的SETNX可以保证只有第一次设置可以成功, 那么获取锁的Safety是可以保证的

但是在2.6.12之前SETNX和EXPIRE是两条命令, 这样会存在如下情况: Command Status SETNX lock_key lock_value 执行成功 EXPIRE lock_key ttl_with_seconds 发送失败&Client Crash 会导致lock_key无法正确释放, 从而不能满足Liveness

问题有了, 如何解决? Redis2.6.12之前版本可以采用lua脚本将命令一次提交, 保证操作原子性 升级到2.6.12之后版本

锁的持有比较复杂, 我们先来看锁的释放 野狐禅版本, golang示例 func AcquireLock(lock_key string, lock_value string, timeout uint32) bool { // SET lock_key lock_value NX PX ttl_with_seconds // return is_success } func ReleaseLock(lock_key string) { // DELETE lock_key } func Process(lock_key string, lock_value string, timeout uint32) { if AcquireLock(lock_key, lock_value, timeout) { // 无论业务逻辑执行是否成功, 一定释放锁 defer func() { ReleaseLock(lock_key) }() // do something // maybe process over lock TTL } }

看上去很完美的实现. But Really Good Job?


基于分布式原理解析Redis分布式锁的实现

惨案是如何发生的

这里完全没考虑如果业务执行超过TTL时间, 导致锁被自动释放的情况 >_< !!!!!!!!!!!! 来看个正规军版本

func ReleaseLock(lock_key string, lock_value string) bool { // 在释放锁的时候加入乐观锁校验, 并通过lua脚本保证原子性 // return_val = eval ( // if redis.call("get",lock_key) == lock_value then // return redis.call("del",lock_key) // else // return 0 // end // ) return return_val!=0 } func Process(lock_key string, lock_value string, timeout uint32) { if AcquireLock(lock_key, lock_value, timeout) { // 无论业务逻辑执行是否成功, 一定释放锁 defer func() { release_success := ReleaseLock(lock_key) if !release_success { // 如果锁释放失败, 说明锁超时, 其他人已经获取了锁, 需要根据业务决定是否rollback刚刚的操作 // maybe rollback?? } }() // do something // maybe process over lock TTL } }

最后需要注意的事情

由于使用了lock_value作为释放锁时的乐观校验, 那么lock_value的选择就需要一定的技巧

值的生成 优点 缺点 系统时间戳 程序实现简单 分布式环境下ntp时钟并不同步, 有概率碰撞 ID服务获取 全局保证唯一 引入了外部服务依赖, 降低健壮性 分布式ID算法 全局保证唯一 有一定技术门槛

这里推荐第三种方案, 可参考twitter的snowflake算法

顺便鄙视一下某些无良码农, 仅仅是对snowflake算法的位进行调整, 就人模狗样的跑出来说实现了nb的分布式ID算法>_<

本文数据库(综合)相关术语:系统安全软件

分页:12
转载请注明
本文标题:基于分布式原理解析Redis分布式锁的实现
本站链接:https://www.codesec.net/view/620744.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 数据库(综合) | 评论(0) | 阅读(10)