Redis的分布式锁分析

系列文章目录

Java项目对接redis,客户端是选Redisson、Lettuce还是Jedis?


在这里插入图片描述

开发同学都知道,在分布式系统中,为了保证多个机器并发操作的数据一致性,常常需要使用分布式锁机制。常用的比如数据库锁,ZK锁,Reis锁等等,今天我们就探讨下Redis分布式锁,说说他的实现原理和应用场景,并介绍如何正确使用分布式锁来解决并发问题

📕作者简介:战斧,从事金融IT行业,有着多年一线开发、架构经验;爱好广泛,乐于分享,致力于创作更多高质量内容
📗本文收录于 Redis专栏 专栏,有需要者,可直接订阅专栏实时获取更新
📘高质量专栏 云原生、RabbitMQ、Spring全家桶 等仍在更新,欢迎指导
📙Zookeeper Redis kafka docker netty等诸多框架,以及架构与分布式专题即将上线,敬请期待


一、什么是分布式锁?

其实分布式锁并不是特指某个组件,而是对某些组件的用法,能够起到锁的作用。与我们之前讲的单体应用内部的锁,功能都是一致的。只不过在单体应用中,竞争锁的是各个线程,而在分布式场景下的锁,竞争锁的就是各个分布式应用了,如下:
在这里插入图片描述

所以说,分布式锁是一种机制,用于协调分布式系统中多个节点对共享资源的访问,保证资源的一致性和并发操作的正确性

二、Redis分布式锁的几种实现

一般我们利用Redis来作为分布式锁,有两种使用方式,一种是基于SETNX的简单锁,这种锁只需要一个redis实例就好,简单易学。另一种则是更高可用的RedLock机制,但这种方案要求至少要有一个Redis集群,成本更高,使用也更复杂。

1. 简单分布式锁

Redis提供了一系列的原子操作指令,例如SETNX(SET if Not eXists - 如果不存在,则设置)可以实现原子的锁获取操作,保证在并发情况下只有一个客户端能够成功获取锁。

基于SETNX命令实现的简单分布式锁:使用SETNX命令设置一个键值对,当键不存在时,设置成功并获取锁;当键存在时,表示锁已经被其他节点获取

// 成功设置返回1
setnx zhanfu aa
1
// 设置失败返回0
setnx zhanfu bb
0// 删除锁
del zhanfu
1

基于此,抢锁使用setnx, 释放锁使用del。这样就完成了一个基本的锁功能。

但是我们不难发现如果我们最后忘记释放锁,或者释放锁的过程出现问题,那将导致这个锁一直存在。因此合理的操作应该是以NX参数去使用SET命令

// ex 20 指的是20秒后自动过期,这个值要结合业务来设定,至少要保证业务能做完
set zhanfu aa ex 20 nx

这里SET命令设置锁过期时间,这样我们就可以通过设置合理的过期时间来避免锁无法释放的问题。当然这样一来,又可能出现A业务执行时间过长,导致锁被自动删除后,再被B获取到,而A执行完业务代码上又执行删除锁,导致误删的问题,所以一般上锁的值都是一个独有的uuid,而删除则是在Lua脚本里先判断锁是否仍由自己占有,再进行删除

if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])
elsereturn 0
end

2. Redlock

但是我们很明显能注意到,Redis的简单锁基于单实例Redis,在面对Redis集群时,Redis实例间一旦数据不一致,就很难处理,或者单实例宕机了,那就无法使用了,那么多实例的场景下该怎么办呢?

在这里插入图片描述

所以,Redlock是一种针对多实例的分布式锁算法,原理基于Quorum机制,即多数派机制,他的核心思想是通过在多个节点上创建相同的锁,并使用Redis的原子操作来保证原子性,看最后上锁的成功的节点数是否超过总节点数的1/2

Quorum机制的核心在于定义三个参数:N、W、R。其中,N表示副本的总数,W表示成功写入操作所需的副本数量,R表示成功读取操作所需的副本数量(设计时需保证W + R > N)。当进行写入操作时,只要W个副本成功写入,操作即被认为成功。读取操作时,只要R个副本返回数据,即可保证读取到的是最新的数据。
比方说一共有7个副本,一次成功更新了5个,那么至少需要读取3个副本的数据,才可以保证读到了最新的数据。

而对于Redis集群而言,写操作则是要超过一半的实例成功才行,具体步骤如下:

  1. 获取当前时间戳。
  2. 依次向多个Redis节点发送 SETNX 命令,尝试在每个节点上创建一个带有过期时间(比如10秒)的锁。锁的键是一个唯一标识符,值是当前节点的标识符。
  3. 获取锁的操作还有一个超时时间,它要远小于锁的过期时间,一般是几十毫秒量级。客户端在向某个Redis节点获取锁失败以后,不管是超时还是真的失败了,应该立即尝试下一个Redis节点。
  4. 计算整个获取锁的过程总共消耗了多长时间,计算方法是用当前时间减去第1步记录的时间,计算成功获取锁的节点数(Quorum)。如果成功获取锁的节点数小于半数,则认为获取锁失败,返回错误。
  5. 如果成功获取锁的节点数大于等于半数,且获取锁的时间小于锁的过期时间,则认为获取锁成功。
  6. 如果获取锁成功,则返回锁的标识符和过期时间。如果获取锁失败,则在所有节点上通过LUA执行DEL命令,释放已创建的锁。
    在这里插入图片描述

通过以上步骤,Redlock 算法可以比较可靠地在Redis集群里实现分布式锁。

三、Redis 锁的问题

1. 互斥失效

但是 Redlock 尽管在集群里实现了分布式锁,可不难看出,他的设计上仍有不少异常情况难以处理(有些问题单例Redis上也有,所以统称为Redis锁问题)。也曾引发起广泛的讨论,比如下面这种场景

在这里插入图片描述

  • 客户端1在获得锁
  • 客户端1之后发生了很长时间的GC pause,在此期间,它获得的锁过期了。
  • 而客户端2获得了锁。
  • 客户端1从GC pause中恢复过来的时候,它不知道自己持有的锁已经过期了
  • 客户端1, 客户端2都认为自己获得了锁,进而都去操作共享数据了。

而这种场景下,原作者的意思是在对共享资源进行操作前,都进行一个版本校验来解决,也即带上一个token,然后通过CAS来确保操作共享资源时,自己的token是最新的。但问题是,如果共享资源可以提供原子性的CAS的能力,自己就具备了互斥能力,似乎就没必要使用分布式锁了。

2. 时钟偏移

另外一个显著的问题是, Redlock 需要依赖系统时间来计算锁的过期时间和判断锁的有效性。如果发生了时钟偏移(不同节点之间的时间存在较大的偏差),可能会导致锁的有效性错误判断。

尤其是针对一种时钟跳跃的情况,就是某些节点的时钟突然产生了较大的变动,将直接影响到Redis的锁过期的问题,

对于这些问题,分布式系统专家Martin Kleppmann 的意见是:如果是为了效率(efficiency)而使用分布式锁,允许锁的偶尔失效,那么使用单Redis节点的锁方案就足够了,简单而且效率高。Redlock则是个过重的实现(heavyweight)。
如果是为了正确性(correctness)在很严肃的场合使用分布式锁,那么不要使用Redlock。它不是建立在异步模型上的一个足够强的算法,它对于系统模型的假设中包含很多危险的成分(对于timing)。而且,它没有一个机制能够提供fencing token。

也正由于体验不佳,我们前面提到过的Redission 客户端已经将 Redlock的使用废弃了.

四、与其他分布式锁相比

除了Redis,常用的分布式锁还有 ZooKeeper数据库,严格的说分布式锁并不是某个组件,而是一个机制与用法,所以很多组件都能当作分布式锁来用,我们可以从这几个角度来考量:

性能可靠性功能特性使用场景
Redis是一个内存数据库,因此读写速度非常快,适用于高并发场景通过复制和持久化来确保数据的可靠性,即使发生故障,也可以通过故障转移来保证服务的可用性提供了丰富的数据结构和功能,如发布/订阅、事务、Lua脚本适用于高并发的场景,如秒杀、抢购等需要快速响应的业务
ZooKeeper通过主节点选举和写操作的持久化,保证了数据的一致性,但相对于Redis来说,性能较差使用ZAB协议来提供高可用的分布式一致性。它的选举机制和写操作持久化确保了数据的一致性和可靠性提供了分布式锁之外的更多功能,如配置管理、分布式队列等。它还可以用于分布式协调和命名服务适用于需要强一致性和可靠性的分布式场景,如分布式事务、分布式锁
数据库数据库的性能受到硬件和数据库引擎的影响,通常不如Redis和ZooKeeper数据库的可靠性取决于数据库引擎的复制和故障恢复机制主要用于数据存储和查询,通常没有专门的分布式协调功能适用于对数据一致性要求较高的场景,如金融交易系统、电子商务等

一般来讲,大部分场景下,我们选用分布式锁,最重要的考量还是一致性,这直接影响这个锁的正确性。然后才是性能及其他指标。所以如果从这个角度来考虑,

  • 如果是一些非关键的业务,使用单点Redis 的 setNx,又或者集群环境,使用RedLock是可以的。前者简单易用,后者可靠性更好一点
  • 如果是关键业务,尽管性能稍逊,但仍建议使用 ZK 或其他能保证一致性的组件,这些更适合作为分布式锁的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/15731.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

柯桥生活英语口语学习“面坨了”英语怎么表达?

“面坨了”英语怎么表达? 要想搞清楚这个表达,首先,我们要搞明白“坨”是啥意思? 所谓“坨”就是指,面条在汤里泡太久,从而变涨,黏糊凝固在一起的状态。 有一个词汇,很适合用来表达这…

鸿蒙NEXT应用示例:切换图片动画

【引言】 在鸿蒙NEXT应用开发中,实现图片切换动画是一项常见的需求。本文将介绍如何使用鸿蒙应用框架中的组件和动画功能,实现不同类型的图片切换动画效果。 【环境准备】 电脑系统:windows 10 开发工具:DevEco Studio NEXT B…

UAC2.0 speaker——speaker 数据传输

文章目录 麦克风数据传输准备音频数据抓包原始数据频谱分析(FFT)应用麦克风数据传输 上一节中实现了 USB 麦克风设备 本节主要介绍 MCU 麦克风的数据如何传输给上位机。 准备音频数据 MCU 端发送 48KHZ, 16bit 单声道的正弦波数据,正弦波数据的生成参考 音频——C语言生…

【多语言】每种语言打印helloworld,编译为exe会占多大空间

文章目录 背景c语言 53KBc 53KBgo 1.8Mdart 4.6Mpython未测试nodejs未测试rust未测试java未测试cmd || bash || powershell 未测试other 背景 各个版本的helloworld,纯属闲的, 环境如下: - win10 - mingw: gcc8.1.0 - go1.21 - dart3.5.4c语言 53KB gcc main.c -…

Android12的ANR解析

0. 参考: ANR分析 深入理解 Android ANR 触发原理以及信息收集过程 1.ANR的触发分类: ANR分为4类: InputDispatchTimeout:输入事件分发超时5s,包括按键和触摸事件。BroadcastTimeout:比如前台广播在10s内未执行完成&#xff0…

2022-2023全国高校计算机能力挑战赛区域赛python组编程题

mi目录 2022 1. 2. 1. 使用 format() 方法 2. 使用 f-string(Python 3.6 及以上) 2023 1. 2. 3. 4 闽农大宝玲楼 2022 1. 1.某动物研究员给动物园的动物们定了一个园区幸福值,其中园区幸福值的计算为一个园区内“所有动物的活动时…

函数的栈帧

前言: 1.请使用vs2013调试,我使用vs2019被恶心到了,封装严重,不利于观察。 2.函数栈帧:函数就是程序,程序就需要空间来运行,所以我们要为他分配空间,分配的空间用ebp esp维护&…

机器学习基础04

目录 1.朴素贝叶斯-分类 1.1贝叶斯分类理论 1.2条件概率 1.3全概率公式 1.4贝叶斯推断 1.5朴素贝叶斯推断 1.6拉普拉斯平滑系数 1.7API 2.决策树-分类 2.1决策树 2.2基于信息增益的决策树建立 2.2.1信息熵 2.2.2信息增益 2.2.3信息增益决策树建立步骤 2.3基于基…

如何解决IDE添加错误GitHub token后无法连接GitHub的问题

背景 当初学者首次使用IDE(IDEA、Xcode等)对GitHub仓库进行操作(push、fetch)时,会提示输入GitHub账户和token,如果这时候你一不小心输入了错误的token,之后你就叫天天不应叫地地不灵了&#xf…

PPT技巧:如何合并PPT文件?

在工作与学习中,PPT(PowerPoint)演示文稿已成为信息传递、项目汇报、教育培训等领域不可或缺的工具。随着任务的累积,我们往往会积累大量单独的PPT文件,每个文件可能包含特定章节、项目阶段或是不同主题的内容。为了更…

安全见闻1-5

涵盖了编程语言、软件程序类型、操作系统、网络通讯、硬件设备、web前后端、脚本语言、病毒种类、服务器程序、人工智能等基本知识,有助于全面了解计算机科学和网络技术的各个方面。 安全见闻1 1.编程语言简要概述 C语言:面向过程,适用于系统…

k-近邻算法(K-Nearest Neighbors, KNN)详解:机器学习中的经典算法

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

LeetCode297.二叉树的序列化和反序列化

题目要求 序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。 请设计一个算法来实现二叉树的序列化与反序…

蓝牙5.0模块助力闹钟升级,开启智能生活第一步

随着智能家居产业的快速发展,智能闹钟作为其中一个重要的品类,逐渐从单一的时间提醒功能演变为集音频播放、语音交互、智能控制等多种功能于一体的智能设备。而在这些功能的实现中,蓝牙音频模组扮演着核心角色。 1、蓝牙音频模组的功能概述 …

自己动手写Qt Creator插件

文章目录 前言一、环境准备1.先看自己的Qt Creator IDE的版本2.下载源码 二、使用步骤1.参考原本的插件2.编写自定义插件1.cmakelist增加一个模块2.同理,qbs文件也增加一个3.插件源码 三、效果总结 前言 就目前而言,Qt Creator这个IDE,插件比…

力扣经典面试题

1.本题的目标是判断字符串ransomNote是否由字符串magazine中的字符构成,且由magazine中的每个字符只能在ransomNote中使用一次 2.采用的方法是通过一个字典cahr_countl来统计magazine字符串中每个字符出现的次数 3.然后遍历ransomNote字符串,对于其中的…

Java开发人员从了学习ArkTs笔记(三)-数据结构与线程通信全解析

大家好,我是一名热爱Java开发的开发人员。目前,我正在学习ARKTS(Advanced Java Knowledge and Technology Stack),并将不断输出我的学习笔记。我将在这里分享我学习ARKTS的过程和心得,希望能够为其他开发人…

Java基础——预定义类/自定义类封装什么是Final类型

目录 预定义类——日历输出: 自定义类——在Java文件中: 什么是封装? 什么是final类型? 修饰变量: 修饰方法: 修饰类: 预定义类——日历输出: 例如:Math类、Date类…

spi 回环

///tx 极性0 (sclk信号线空闲时为低电平) /// 相位0 (在sclk信号线第一个跳变沿进行采样) timescale 1ns / 1ps//两个从机 8d01 8d02 module top(input clk ,input rst_n,input [7:0] addr ,input …

20241114软考架构-------软考案例16答案

每日打卡题案例16答案 16.【2017年真题】 难度:简单 阅读以下关于软件架构评估的叙述,在答题纸上回答问题1和问题2.(共25分) 【说明】 某单位为了建设健全的公路桥梁养护管理档案,拟开发一套公路桥梁在线管理系统。在系统的需求分析与架构设…