redis分布式锁和看门狗的实现

redis分布式锁和看门狗的实现

  • 分布式应用进行逻辑处理时经常会遇到并发问题。
  • 比如一个操作要修改用户的状态,修改状态需要先读出用户的状态,在内存里进行修 改,改完了再存回去。如果这样的操作同时进行了,就会出现并发问题,因为读取和保存状 态这两个操作不是原子的。(Wiki 解释:所谓原子操作是指不会被线程调度机制打断的操 作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 线程切换。)

在这里插入图片描述

演示不使用分布式锁

  • 以下均使用python代码进行演示

逻辑:开启多线程模拟并发存储数据,在最后阶段取出数据与redis中存储的数据进行比对

import threading
import redis as _redis
redis = _redis.Redis(host='localhost', port=6379, decode_responses=True, db=11)


def storage_redis(value):
    redis.set('age', value)
    

if __name__ == '__main__':
    for i in [1,2,3,4,5]:
        t = threading.Thread(target=storage_redis, args=(i,))
        t.start()
    print('--------------------')
    print(redis.get('age'))
  • 启动脚本运行,结果如下图

在这里插入图片描述

  • 看一看,是不是直接裂开🙃,数据存的和取的结果根本不一样

像这种应该办,这个时候就需要用到分布式锁了,类似于MySQL中悲观锁与乐观锁

redis分布式锁实现

  • 分布式锁本质上要实现的目标就是在 Redis 里面占一个“茅坑”,当别的进程也要来占 时,发现已经有人蹲在那里了,就只好放弃或者稍后再试。

  • 占坑一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占坑。先来先占, 用 完了,再调用 del 指令释放茅坑。

  • 但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样 就会陷入死锁,锁永远得不到释放

  • 于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也 可以保证 5 秒之后锁会自动释放。

  • 但是以上逻辑还有问题。如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因 为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。

  • 这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可 以一起执行就不会出现问题。也许你会想到用 Redis 事务来解决。但是这里不行,因为 expire 是依赖于 setnx 的执行结果的,如果 setnx 没抢到锁,expire 是不应该执行的。事务里没有 ifelse 分支逻辑,事务的特点是一口气执行,要么全部执行要么一个都不执行。

  • 为了治理这个乱象,Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底解决了分布式锁的乱象。从此以后所有的第三方分布式锁 library 可以休息了。 > set lock:codehole true ex 5 nx OK … do something critical … > del lock:codehole 上面这个指令就是 setnx 和 expire 组合在一起的原子指令,它就是分布式锁的奥义所在

import threading
import time
import uuid
import redis as _redis
from redis import WatchError
from threading import Timer
redis = _redis.Redis(host='localhost', port=6379, decode_responses=True, db=11)

class DistributedLocks(object):
    """
    Redis分布式锁实现类
    """
    lock_timeout = None
    lock_name = None
    def __init__(self,coon,lock_name=None , acquire_timeout=3 , lock_timeout=3, **kwargs):
        """
        coon --> redis的连接
        lock_name --> 锁的名称
        acquire_timeout -->  获取锁的超时时间(秒)
        lock_timeout -->  锁的超时时间(秒)
        """
        self.coon = coon
        if not lock_name:
            lock_name = 'default'
        self.lock_name = f'lock:{
     lock_name}'
        self.rem_ark = str(uuid.uuid4())
        self.lock_timeout = lock_timeout
        self.end = time.time() + acquire_timeout
        DistributedLocks.lock_timeout = self.lock_timeout
        DistributedLocks.lock_name = self.lock_name

    def _lock(self) -> bool:
        """
        加锁
        """
        while time.time() <= self.end:
            # 如果锁不存在,设置锁并给锁加上超时时间
            if self.coon.set(self.lock_name,self.rem_ark,ex=self.lock_timeout,nx=True):
                return True
            time.sleep(0.001)
        return False

    def _unlock(self) -> bool:
        """
        释放锁 方法一事务
        """
        # 使用redis中的事务pipeline
        pipe = self.coon.pipeline(True)
        while True:
            try:
                # 通过watch监视锁名称,如果锁名称一旦改变,就抛出WatchError
                pipe.watch(self.lock_name)
                rem_rak = self.coon.get(self.lock_name)
                if rem_rak and rem_rak == self.rem_ark:
                    pipe.multi()
                    pipe
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值