python基于redis优化分布式锁

关爱redis

描述

基于redis的分布式锁,最近看到分布式锁都是一级分布式锁,而且未拿到锁需要休眠几秒钟继续撞redis,心疼redis三秒钟,本方案采用二级分布式锁,一级锁拿不到就等二级锁,这种不需要几毫秒撞击一次redis,对于超高并发能大大减轻redis的负荷,同时分配锁还能实现时序功能。

安装

pip3 install -r requirements.txt

分布式锁设计
  1. 分布式锁设计两层,第一层是需要竞争的,第二层是需要等待前一个持有者释放,第二层可以友好获取锁
  2. 一级锁使用key-value的常规竞争模式
  3. 第二层是为了防止惊群,采用list队列形式,大家都等待list被填充一个值,被填充表示前面一个持有者释放了锁
  4. 如果两次获取锁都失败了,那么需要再次获取一级锁,这是为了持有者过期,未将持有权向下传递
    z
工作步骤
  1. 首先获取锁需要提供一个key和value,相同的key获取锁是需要竞争的,其次value具有唯一性,这个用于在释放的时候分析持有锁是否超时被其他人重新获取
  2. 如果某个任务获取一级锁,它可以继续执行任务,未获得一级锁的任务,可以等待二级锁,二级锁不需要频繁的撞击redis,而是通过list的blpop特性阻塞等待获取
  3. 如果一级和二级都未获取到锁,最后需要一次兜底处理,再次获取一级锁,目的是防止前面的持有者已经过期,但未释放锁
  4. 互斥任务处理完毕需要释放锁,释放锁首先要对一级锁和二级锁进行从新设定过期时间,防止时间间隙产生两个持有者,然后获取一级锁的值和自己之前设定的是否一致,如果一致说明自己持有权有效,如果不一致说明持有权过期,被其他的任务夺取
  5. 如果释放的时候持有权有效则向二级锁添加一条自己value记录,这样等待二级锁的任务就可以获取到消息,如果持有权无效则无需做任何处理
  6. 当某个任务通过二级锁获取到值后,需要立即修改一级锁的值为自己的值,表示自己拥有所有权,那么释放任务时同上
  7. 在修改一级锁、等待二级锁、释放锁前后需要同步一级锁与二级锁的时间,这样保证一级锁和二级锁生命周期一致,当没有继承者之后会自动删除
引用
  1. 以上的逻辑都已经封装在locker.py和async_locker.py中,直接引用该文件即可
# 阻塞式
from locker import Locker
locker = Locker(redis_cli, 'key_name', 'UUID000')
if locker.lock(): 
    do_something()
    locker.release_lock()
else: print('fail')

# 协程异步
from async_locker import AioLocker
locker = AioLocker(redis_cli, 'key_name', 'UUID000')
if await locker.lock(): 
    await do_something()
    await locker.release_lock()
else: print('fail')
  1. 核心代码
from datetime import datetime, timedelta
import redis
from math import ceil


class Locker:

    def __init__(self, cli: redis.Redis, lock_name, lock_value, time_out=2):
        self.lock_name, self.lock_value, self.list_key = lock_name, lock_value, f'{lock_name}_list'
        self.cli, self.time_out = cli, ceil(time_out)
        self.is_locker = False

    # 加锁
    def lock(self):
        if self._lock_key(): return self.is_locker
        if self._lock_list(): return self.is_locker
        self._lock_key()
        return self.is_locker

    # 通过key获取一级锁
    def _lock_key(self):
        if self.cli.set(self.lock_name, self.lock_value, ex=self.time_out, nx=True):
            self.is_locker = True
        return self.is_locker

    # 通过队列获取二级锁
    def _lock_list(self):
        self._set_expire()
        list_top = self.cli.blpop(self.list_key, self.time_out)
        if list_top is not None:
            self.is_locker = True
            self._set_new_value()
        return self.is_locker

    # 重新设置值和过期时间
    def _set_new_value(self):
        self.cli.set(self.lock_name, self.lock_value)
        self._set_expire()

    # 重新修改过期
    def _set_expire(self):
        at = datetime.now() + timedelta(seconds=self.time_out)
        self.cli.expireat(self.lock_name, at)
        self.cli.expireat(self.list_key, at)

    # 释放锁
    def release_lock(self):
        self._set_expire()
        if self.cli.get(self.lock_name) == self.lock_value:
            self.cli.lpush(self.list_key, self.lock_value)
            self._set_expire()

仓库地址:https://gitee.com/favouriter/care4redis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值