基于redis实现分布式锁(多实例redis+RedLock算法)

本文介绍了如何基于Redis实现高可用的分布式锁——Redlock。讨论了Redlock的基本原理,包括互斥性、避免死锁和容错性要求。详细阐述了Redlock算法,并通过代码实现展示了在不同并发和实例数量下的测试结果,强调了在多线程环境下使用Redlock的注意事项。最后,作者对Redlock的复杂性和实用性提出了个人看法,推荐使用Zookeeper作为更优的分布式锁解决方案。
摘要由CSDN通过智能技术生成

  在前面的文章中,已经实现单实例redis分布式锁,但这种实现是基于单个redis服务,若redis服务不可用,显然所有客户端无法加锁,该实现还未到高可用的水平,因此需要进一步提升分布式的锁的逻辑,好在redis官方提供了相应的权威描述并称之为Redlock,具体参考文章:DLM,这个锁的算法实现了多redis实例(各个redis是相互独立的,没有主从、集群模式)的情况,实现了真正高可用分布式锁。

高可用的分布式锁要求:

1)Mutual exclusion,互斥性,任何时刻只能有一个client获取锁

2)Deadlock free,死锁也必须能释放,即使锁定资源的redis服务器崩溃或者分区,仍然能释放锁

3)Fault tolerance;只要多数互相独立的redis节点,这里不是指主从模式两个节点或者集群模式,(一半以上)在使用,client才可以进行加锁,而且加锁成功的redis服务数量超过半数,且锁的有效时长还未过期,才认为加锁成功,否则加锁失败

Redlock算法说明

  首先需理解时钟漂移clock drift概念:服务器时钟偏离绝对参考时钟的差值,例如在分布式系统中,有5台服务器,所有服务器时钟在初始情况下都设置成相同的时间(服务器上没有设置ntp同步)例如都为2019-08-01 10:00:00,随着时间的流逝,例如经过1年后,再“观察”这5台服务器的时间,服务器之间的时间对比,将有可能出现一定的快慢差异:

Server1显示一年后的时间:2020-08-01 10:00:01

Server2显示一年后的时间:2020-08-01 10:00:02

Server3显示一年后的时间:2020-08-01 10:00:02

Server4显示一年后的时间:2020-08-01 09:59:58

Server5显示一年后的时间:2020-08-01 10:00:01

那么由这5台服务器组成的分布式系统,在外侧观察,时钟漂移为=2020-08-01 10:00:02减去2020-08-01 09:59:58=4秒,当然这是累计一年的时钟漂移时长,于是可以计算每秒的时间漂移刻度=4/(3600*24*365),该刻度时长极小完全可以忽略不计,这是redis官方提供这个概念,让分布式锁的redis实现看起来更高级。

redlock加锁流程,假设客户端A按顺序分别在5个完全独立的redis实例作为加锁,如图所示:
在这里插入图片描述

1)客户端A在redis01加锁操作前,获取当前时间戳T1

2)客户端A使用相同的key和uuid按顺序在5个redis上set key加锁和设定键的过期时长(有效时长),因为set key操作需要一定时间,因此在set过期时长时,需要set大于加锁所消耗的时长,否则客户端A还未在超过半数redis实例加锁成功前,前面redis set的key就已经先失效了,

错误设置:TTL为1s,例如客户端A在redis01加锁耗时为0.1秒、在redis02加锁耗时为0.5秒,但在redis03加锁耗时为1秒,此时redis01、redis02的key已失效,导致客户端A没能在超过半数(3个)的redis实例上加锁成功

正确设置:TTL为5s,例如客户端A在redis01加锁耗时为0.1秒、在redis02加锁耗时为0.5秒,redis03加锁耗时为1秒,此时redis01、redis02、redis03 key还未失效,客户端A成功在超过半数(3个)的redis实例上加锁,但此时客户端A还不能严格意义上成功获得了分布式锁,还需要进行第3步骤的判断

3)客户端A完成在多个redis实例上加锁后,此刻,锁真正有效时间不是一开始设置TTL的10秒,而是由以下得出:

在5个redis上加锁完后所消耗的时长:set_lock_cost=T5-T1=4s

实际锁的最小有效时长:min_validity=TTL-set_lock_cost-时钟漂移耗时

实际锁的最小有效时长=10s-4s-1s=5s,也就是说客户端A虽然在redis服务器设置有效时长为10s,但扣除一系列的加锁操作耗时后,“redis服务端”留给客户端A的实际有效时长为5秒。如果客户端A能在这5秒内完成任务,且按顺序释放锁,那么客户端A完成了一个完整流程的分布式锁条件的任务。

4)如果客户端A超时等原因无法获得超过半数(3)个以上,则必须解锁所有redis实例,否则影响其他进程加锁

RedLock代码实现:

  前一篇文章中已经实现的单服务的redis分布式锁,基于该基础上,实现redlock并不复杂,

import time,datetime
import uuid
import random
import redis
import threading


class RedLockException(Exception):
    pass


class RedLock(object):
    def __init__(self, locker_key, connection_conf_list=None,
                 retry_times=3,
                 retry_interval=200,
                 ttl=5000,
                 clock_drift=500):
        self.locker_key = locker_key
        self.retry_times = retry_times
        self.retry_interval = retry_interval
        self.global_ttl = ttl
        self.clock_drift = clock_drift
        self.locker_id = None
        self.is_get_lock = False
        if not connection_conf_list:
            connection_conf_list = [{
   
                'host': '192.168.100.5',
                'port': 6379,
                'db': 0,
                'socket_connect_timeout':1
            }]

        self.all_redis_nodes = [redis.StrictRedis(**each_conf) for each_conf in connection_conf_list]
        self.majority_nodes = len(self.all_redis_nodes) // 2 + 1

    def _release_single_lock(self, node):
        """
        在redis服务端执行原生lua脚本,只能删除加锁者自己的id,而且是原子删除
        :return:
        """
        lua_script = """
        if redis.call("get",KEYS[1]) == ARGV[1] then
            return redis.call("del",KEYS[1])
        else
            return 0
        end
        """
        try:
            lua_func = node.register_script(lua_script)
            lua_func(keys=[self.locker_key], args=[self.locker_id])
        except(redis.exceptions.ConnectionError, redis.exceptions.TimeoutError):
            pass

    def _acqu
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值