码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【SimpleFunction系列二】渐进式理解Redis分布式锁


    文章目录

      • 渐进式理解Redis分布式锁
          • 1. 分布式锁需要满足的条件
          • 2. 如何实现Redis分布式锁?
            • 2.1 如何使用Redis加锁❓
            • 2.2 加锁就需要解锁,使用Redis解锁❗️
            • 2.3 为了保证容错性,需要设置锁的超时时间❗️
            • 2.4 即第一版的分布式锁伪代码为:⁉️
            • 2.5 问题1:加锁和设置过期时间是非原子操作❗️
            • 2.6 如何解决死锁的问题呢❓
            • 2.7 问题2:锁到期,任务未完成❗️
            • 2.8 如何解决释放其他线程锁的问题❓
            • 2.9 如果解决两个线程同时获取到锁的问题❓
          • 3. 下一篇Redisson分布式锁

    渐进式理解Redis分布式锁

    并发场景下,由于修改和保存数据的过程不是原子性的,部分操作可能会丢失,在单服务中我们常用本地锁来避免并发带来的问题。但是本地锁无法在多服务器之间生效。

    1. 分布式锁需要满足的条件

    • 互斥性:任意时刻,只能有一个客户端获取锁。
    • 同一性:锁只能被持有该锁的客户端删除。
    • 可重入性:持有锁的客户端可继续对该锁加锁,实现锁的续租。
    • 容错性:持有锁的客户端下线,到期释放锁,防止死锁。

    2. 如何实现Redis分布式锁?

    2.1 如何使用Redis加锁❓

    最直白的做法:SETNX

    SETNX is short for “SET if Not eXists”,即设置KEY如果不存在的话,value我们可以暂定设置1。

    SETNX lockName 1
    
    • 1

    返回1说明key不存在设置成功,即获取到了锁,返回0则加锁失败。

    2.2 加锁就需要解锁,使用Redis解锁❗️

    删除命令:DEL

    DEL lockName
    
    • 1

    删除了该key,此时其他线程就可以通过SETNX获取锁了。

    2.3 为了保证容错性,需要设置锁的超时时间❗️

    设置key的过期时间:EXPIRE

    EXPIRE lockName 20
    
    • 1

    为key设置一个超时时间,以保证即使锁没有被显示的释放时,在到达过期时间后也能自动释放锁,防止死锁的产生。

    2.4 即第一版的分布式锁伪代码为:⁉️
    if(setnx(key,1) == 1){
        expire(key,30)
        try {
            work....
        } finally {
            del(key)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2.5 问题1:加锁和设置过期时间是非原子操作❗️

    在极端情况下,当线程执行完SETNX还未执行EXPIRE时服务挂掉。

    此时该锁既不会被显示的解锁,也不会自动过期,其他线程再也无法获取到该锁了,game over。

    2.6 如何解决死锁的问题呢❓

    SET命令加锁

    SET lockName 1 EX 30
    
    • 1

    SETNX命令是不支持传入超时时间的,不过幸好Redis2.6.12以后为SET指令增加了可选参数EX、PX属性,这样加锁和设置超时时间就是原子操作了。

    2.7 问题2:锁到期,任务未完成❗️

    回忆一下我们实现的锁机制,如果锁到期了任务未完成将产生两个严重问题。

    请添加图片描述

    1. 将其他线程的锁释放(不满足同一性)。
    2. 其他线程提前获取到了锁,即本不应该同时执行的任务同事执行(不满足互斥性)。
    2.8 如何解决释放其他线程锁的问题❓

    解决这个问题,我们只需要在删除之前验证key对应的value是不是自己的线程。

    我们可以把线程ID作为key对应的value,在删除之前验证一下锁是不是自己的锁。

    伪代码:

    加锁:
    String threadId = Thread.currentThread().getId()
    set(key,threadId ,30,EX)
    解锁:
    if(threadId .equals(redisClient.get(key))){
        del(key)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里,判断锁和删除锁是两个独立操作,不是原子操作。

    我们可以使用lua脚本来实现:

    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    
    • 1

    这样,判断和删除过程就是原子操作了。

    2.9 如果解决两个线程同时获取到锁的问题❓

    上面我们解决了释放非自己锁的问题,但是AB两个线程同时执行任务也是不完美的。

    我们可以让获得锁的线程开启一个守护线程,用来给快到期的锁续期。

    请添加图片描述

    3. 下一篇Redisson分布式锁

    Redis分布式锁在生产中使用自然不需要我们自己去实现每一个细节,Redis分布式锁在java中的解决方案官方推荐就会Redisson

    【Distributed Locks with Redis】

  • 相关阅读:
    【深度学习】不同虚拟环境安装不同Pytorch版本
    竞赛选题 深度学习的水果识别 opencv python
    设计模式(八)——装饰者模式
    C++中的各种函数及用法(3)
    22【数据库连接池】
    给出32位有符号整数,将这个整数翻转
    深度学习第四课——卷积神经网络(week 2)
    STM32F1与STM32CubeIDE综合实例-MPU6050数据3D可视化(基于Python)
    BigCodeBench: 继 HumanEval 之后的新一代代码生成测试基准
    独立站店铺应该如何精细化运营
  • 原文地址:https://blog.csdn.net/qq_38723677/article/details/126132697
  • 最新文章
  • 沪漂五周年了:我越来越迷茫了
    Agentic Skill Routing 实战:别再把所有 Skill 塞进 AI Agent 上下文
    MySQL-Seconds_behind_master的精度误差
    [MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱省时间
    AI的至暗历史:从万众期待到被政府撤资,AI的两次死亡徘徊
    Agent OS :五种驯服不确定性的范式
    PortSwigger SQL注入LAB11
    数据库即时编译JIT
    [Begin]AI Learn Data Day 0
    深度学习进阶(二十七)现代 LLM 的核心架构设计其二:SwiGLU
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
小工具 小游戏
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1

京公网安备 11010502049817号