redis实现分布式锁——核心 setx+pipe watch监控key变化-事务
2017-02-22 20:22
337 查看
如何设计一把分布式锁
我们用 redis 来实现这把分布式的锁,redis 速度快、支持事务、可持久化的特点非常适合创建分布式锁。分布式环境中如何消除网络延迟对锁获取的影响
锁,简单来说就是存于 redis 中一个唯一的 key。一般而言,redis 用set命令来完成一个 key 的设置(加锁),使用
get命令获取 key 的信息(检查锁)。由于网络延迟的存在,简单的使用
set和
get命令可能会带来如下问题:
线程 A 检查锁是否存在(get)–>否–>加锁(set),在 A 发起加锁命令但是还没有加锁成功的时候,可能线程 B 已经完成了
set操作,锁被 B 获得,但是 A 也发起了加锁请求,由于
set命令并不检查 key 的存在,B 的锁很可能会被 A 的
set操作破坏。
幸运的是,redis 提供了另一个命令
setx: 当指定的 key 不存在时,设置 key 的值为指定 value,如果存在,不做任何操作,成功则返回 1,失败则返回 0。也就是只要命令返回成功,线程就能正确获得锁,不需要再做类似
get检查操作。
使用
setx可以消除网络延迟对锁设置的影响。
加锁的客户端发生 crash 导致锁不能被正确释放应该怎么处理?
加锁成功并操作完成时候,就需要加锁线程对锁进行释放,以让出资源的控制权。释放锁,简单来说就是删除 redis 中这个唯一的 key,但是一定要保证删除的这个 key 是该线程创建的,因而锁创建时必须携带执行线程的唯一特征以标示创建者的身份。如果加锁的线程出现异常 crash 了而不能及时删除锁,则会导致锁一直无法被正确释放,资源处于一直被占有,别的线程处于一直等待的状态。为了避免这样的情况发生,锁一定要在异常发生之后 可以自己释放,以让出资源的控制权,可以使用 redis 的超时机制来达到这个目的。超时时间视不同的业务场景而定,一般是最大允许等待时间。需要注意的是,只有在加锁成功之后才可以对 key 设置 TTL,否则很容易导致 key 被多个线程不断设置 TTL 而无法过期。
if CONN.setnx(lockname, identifier): CONN.expire(lockname, timeout)
加锁之后如何有效监测锁是否被篡改?
redis 提供了 pipeline 和事务操作来保证多个命令可以在一个事务内全部完成从而减少多次网络请求带来的开销,watch 命令又可以在事务开始执行之前对所要操作的 key 执行监测,从而保证了事务的完整性和一致性。因此,为了防止锁篡改,可以在加锁完成之后对锁进行 watch 操作,一旦锁发生变化,则终止事务,回滚操作。pipe = CONN.pipeline(True) pipe.watch(lock)
提供锁的宿主机( redis 服务器) crash 导致锁不能被正确建立和释放该如何处理?**
不论是通信故障或是服务器故障而导致的锁服务器无法响应,此时都会导致客户端加锁和释放锁的请求无法完成,因此一定要有相应的应急处理,以确保程序流程的完整体验,加强客户端的健壮性。比如相应的超时提示,异常告警等。哪些边界需要注意
1.只有锁正确释放才算是整个事务的完整结束,如果锁释放失败,比如被篡改、锁服务器异常等,不同的业务可以根据自己的需求进行变动和调整。2.设置 TTL 一定要是加锁成功之后,否则所有获取锁的客户端都会尝试 TTL 导致锁无法过期。
3.锁的过期时间也就是获取锁的客户端的最大等待时间,这个时间根据执行的事务能够容忍的最长时间为限
一个简单的 python 实现
import time
import redis
import logging
logger = logging.getLogger('service.redis_lock')
CONN = redis.Redis(host='localhost')
def acquire_lock(lockname, identifier, wait_time=20, timeout=15):
end = time.time() + wait_time
while end > time.time():
if CONN.setnx(lockname, identifier): CONN.expire(lockname, timeout) # set expire time
return identifier
time.sleep(0.001) #wait until the lock expired or release by some thread
return False
def release_lock(lockname, identifier):
pipe = CONN.pipeline(True)
try:
#watch lock once lock has been changed, break this transaction
pipe.watch(lockname)
#check if lock has been changed
if pipe.get(lockname) == identifier:
pipe.multi()
pipe.delete(lockname)
pipe.execute()
return True
pipe.unwatch() #execu when identifier not equal
except redis.exceptions.WatchError as e:
logger.error(e)
return False
except Exception as e:
logger.error(e)
return False
return False
if __name__ == '__main__':
print release_lock('h', 'a')
转自:https://gold.xitu.io/entry/57bae53f5bbb500063fedf31
相关文章推荐
- 利用redis实现分布式事务锁,解决高并发环境下库存扣减
- 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存
- 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存
- Jedis使用总结【pipeline】【分布式的id生成器】【分布式锁【watch】【multi】】【redis分布式】
- PHP:分布式事务及两阶段提交方案实现思路
- 使用Atomikos Transactions Essentials实现多数据源JTA分布式事务
- 分布式事务及其在OFBiz的实现
- .NET分布式事务处理总结【上】 - 实现分布式事务处理
- 用Redis实现分布式锁
- 使用Atomikos Transactions Essentials实现多数据源JTA分布式事务
- Jedis使用总结【pipeline】【分布式的id生成器】【分布式锁【watch】【multi】】【redis分布式】
- 通过Redis的Pub/Sub实现对服务器群的监控管理
- ADO.NET如何实现分布式事务处理
- 利用redis分布式锁的功能来实现定时器的分布式
- Redis实现分布式锁
- 为 Key-Value 数据库实现MVCC 事务
- 使用JOTM实现分布式事务管理(多数据源)
- C/S架构分布式系统客户端操作日志监控模块实现思路
- 【转载】分布式事务的两阶段提交---淘宝核心团队博客
- 分布式系统事务原子性的非阻塞实现