zoukankan      html  css  js  c++  java
  • redis实现分布式锁——核心 setx+pipe watch监控key变化-事务

    如何设计一把分布式锁

    我们用 redis 来实现这把分布式的锁,redis 速度快、支持事务、可持久化的特点非常适合创建分布式锁。

    分布式环境中如何消除网络延迟对锁获取的影响

    锁,简单来说就是存于 redis 中一个唯一的 key。一般而言,redis 用 set 命令来完成一个 key 的设置(加锁),使用 get 命令获取 key 的信息(检查锁)。由于网络延迟的存在,简单的使用 setget 命令可能会带来如下问题:

    线程 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
  • 相关阅读:
    Python 函数知识点
    面向对象相关
    判断arg参数是否是可以被调用的
    利用U盘安装CentOS7系统
    简单模仿OpenGL中的栈的作用
    温故而知新我再一次学习库
    关于帧缓存的总结
    OGRE的相关工具和库
    OpenGL在Qt界面下的应用(helloworld)
    OpenGL加载Cg程序
  • 原文地址:https://www.cnblogs.com/bonelee/p/6430789.html
Copyright © 2011-2022 走看看