Redis系列:使用Redis实现分布式锁及相关问题

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

本篇内容包括:关于 Redis 与 分布式锁,Redis 分布式锁的问题及解决方式,Redis 中的 Lua 脚本 以及 Redis 中的 RedLock 算法!


文章目录

    • 一、关于 Redis 与 分布式锁
        • 1、关于分布式锁
        • 2、关于 Redis 实现分布式锁
    • 二、Redis 分布式锁的问题及解决方式
    • 三、Redis 中的 Lua 脚本
    • 四、Redis 中的 RedLock 算法
        • 1、Redis 中的 RedLock 算法
        • 2、 Redlock 算法的客户端的执行步骤


一、关于 Redis 与 分布式锁

1、关于分布式锁

在一个分布式系统中,当一个线程去读取数据并修改的时候,因为读取和更新保存不是一个原子操作,在并发时就很容易遇到并发问题,进而导致数据的不正确。这种场景很常见,比如电商秒杀活动,库存数量的更新就会遇到。如果是单机应用,直接使用本地锁就可以避免。如果是分布式应用,本地锁派不上用场,这时就需要引入分布式锁来解决。

一般来说,实现分布式锁的方式有以下几种:

  • 使用 MySQL,基于唯一索引。
  • 使用 ZooKeeper,基于临时有序节点。
  • 使用 Redis,基于 setnx 命令。

2、关于 Redis 实现分布式锁

Redis 实现分布式锁主要利用 Redis 的setnx 命令。setnx 是 SET if not exists(如果不存在,则 SET)的简写。

加锁:使用setnx key value命令,如果 key 不存在,设置 value(加锁成功)。如果已经存在 lock(也就是有客户端持有锁了),则设置失败(加锁失败)

解锁:使用 del 命令,通过删除键值释放锁。释放锁之后,其他客户端可以通过 setnx 命令进行加锁。

Key 的值可以根据业务设置,比如是用户中心使用的,可以命令为USER_REDIS_LOCK,value 可以使用 uuid 保证唯一,用于标识加锁的客户端。保证加锁和解锁都是同一个客户端。


二、Redis 分布式锁的问题及解决方式

首先,有一个致命问题,就是某个线程在获取锁之后由于某些异常因素(比如宕机)而不能正常的执行解锁操作,那么这个锁就永远释放不掉了。为此,我们可以为这个锁加上一个超时时间为此,我们可以为这个锁加上一个超时时间

  • 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value
  • 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value

然后,此时依然会有问题,某线程 A 获取了锁并且设置了过期时间为 10s,然后在执行业务逻辑的时候耗费了 15s,此时线程 A 获取的锁早已被 Redis 的过期机制自动释放了在线程A获取锁并经过 10s 之后,改锁可能已经被其它线程获取到了。当线程 A 执行完业务逻辑准备解锁(DEL key)的时候,有可能删除掉的是其它线程已经获取到的锁。当解锁时,也就是删除 key 的时候先判断一下 key 对应的 value 是否等于先前设置的值,如果相等才能删除 key。

最后,这里我们还是一眼就可以看出问题来:GETDEL是两个分开的操作,在 GET 执行之后且在 DEL 执行之前的间隙是可能会发生异常的,我们引入了一种新的方式,就是 Lua 脚本,解决原子性的问题。Redis 会将整个 Lua 脚本作为一个整体执行,中间不会被其他请求插入。

另外,为了防止多个线程同时执行业务代码,需要确保过期时间大于业务执行时间,可以在代码增加一个线程用于刷新定时过期时间,并增加一个 bool 类型的值表示是否开启定时刷新过期时间,在线程获取锁的时候,将其设置为 true,解锁前设置回 false。比如,Redisson 实现,获取锁成功就会开启一个定时任务,定时任务会定期检查去续期。

此外,还有一个问题:在集群中,主节点挂掉时,从节点会取而代之,客户端上却并没有明显感知。原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。然后从节点变成了主节点,这个新的节点内部没有这个锁,所以当另一个客户端过来请求加锁时,立即就批准了。这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生。此处可以用 RedLock 算法解决。


三、Redis 中的 Lua 脚本

Lua 是一种轻量小巧的脚本语言,用标准 C 语言编写并以源代码形式开放。其设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能

Redis 在 2.6 版本推出了 lua 脚本功能,允许开发者使用 Lua 语言编写脚本传到 Redis 中执行。

使用 Lua 脚本的好处:

  • 原子操作。Redis 会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务;
  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延;
  • 复用。客户端发送的脚本会永久存在 Redis 中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。

四、Redis 中的 RedLock 算法

1、Redis 中的 RedLock 算法

在集群中,主节点挂掉时,从节点会取而代之,客户端上却并没有明显感知。原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。然后从节点变成了主节点,这个新的节点内部没有这个锁,所以当另一个客户端过来请求加锁时,立即就批准了。这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生

Redlock 算法就是为了解决这个问题

使用 Redlock,需要提供多个 Redis 实例,这些实例之前相互独立没有主从关系。同很多分布式算法一样,Redlock 也使用大多数机制

加锁时,它会向过半节点发送 set 指令,只要过半节点 set 成功,那就认为加锁成功。释放锁时,需要向所有节点发送 del 指令。不过 Redlock 算法还需要考虑出错重试、时钟漂移等很多细节问题,同时因为 Redlock 需要向多个节点进行读写,意味着相比单实例 Redis 性能会下降一些

Redlock 算法是在单 Redis 节点基础上引入的高可用模式,Redlock 基于 N 个完全独立的 Redis 节点,一般是大于 3 的奇数个(通常情况下 N 可以设置为 5),可以基本保证集群内各个节点不会同时宕机。

2、 Redlock 算法的客户端的执行步骤

当 Redis 集群有 5 个节点,运行 Redlock 算法的客户端的执行步骤:

  1. 客户端记录当前系统时间,以毫秒为单位;
  2. 依次尝试从 5 个 Redis 实例中,使用相同的 key 获取锁,当向 Redis 请求获取锁时,客户端应该设置一个网络连接和响应超时时间,超时时间应该小于锁的失效时间,避免因为网络故障出现的问题;
  3. 客户端使用当前时间减去开始获取锁时间就得到了获取锁使用的时间,当且仅当从半数以上的 Redis 节点获取到锁,并且当使用的时间小于锁失效时间时,锁才算获取成功;
  4. 如果获取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间,减少超时的几率;
  5. 如果获取锁失败,客户端应该在所有的 Redis 实例上进行解锁,即使是上一步操作请求失败的节点,防止因为服务端响应消息丢失,但是实际数据添加成功导致的不一致。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis系列:Redis持久化机制与Redis事务

Redis 是个基于内存的数据库。那服务一旦宕机,内存中数据必将全部丢失。所以丢失数据的恢复对于 Redis 是十分重要的,我们首先想到是可以从数据库中恢复,但是在由 Redis 宕机时(说明相关工作正在运行)且数据量很大情况…

Java基础:Java程序设计环境

按应用范围,Java 可分为 3 个体系,即 Java SE、Java EE 和 Java ME。Java 语言的开发运行,也离不开 Java 语言的运行环境 JRE。没有 JRE 的支持,Java 语言便无法运行。当然,如果还想编译 Java 程序,搞搞小开…

负载均衡策略

轮循均衡(Round Robin):每一次来自网络的请求轮流分配给内部中的服务器,从1至N然后重新开始。此种均衡算法适合于服务器组中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。 我们的业务web服务器都是同样配置…

Java基础:Java数据类型

Java 是一种强类型语言,这就意味着必须为每一个变量声明一种类型。在 Java 中基本数据类型共有 8 种,包括 4 种整型、2 种浮点型、1 种用于表现 Unicode 编码的字符单元的字符类型 char 和一种用于表示真值的 boolean 类型 ~ 本篇主要记录内容包括&#…

TCP连接的建立与终止

TCP连接的建立与终止 1.三次握手 TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方…

日常问题:MySQL排序字段数据相同不能分页问题

【问题日期】 2022-11-14 22:45:12 【问题描述】 MySQL 排序字段数据相同不能分页问题:在分页查询数据时,按创建时间排序,由于数据是批量创建的,导致部分数据创建时间一样,而此时分页查询数据,翻页后出现…

数据缺失值处理

数据缺失值处理 In [1]: import pandas as pd import numpy as np from sklearn.ensemble import RandomForestRegressor,RandomForestClassifier from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer In [2]: df pd.DataFrame() df[…

Java基础:Java数字类型

Java 中包含多种运算符:算数运算符、关系运算符、逻辑运算符、位运算符。在 Math 类中,包含了各种各样的数学函数。在编写不同类别的程序时,可能需要的函数也不同。要生成一个随机数,可以使用 Random 对象。 ~ 本篇主要…

Java基础:Java流程控制

块(即复合语句)是指由一对大括号括起来的若干条简单的 Java 语句。块确定了变量的作用域。一个块可以嵌套在另一个块中。但是,不能在嵌套的两个块中声明同名的变量。使用块(有时称为复合语句)可以在Java程序结构中原本…

Java基础:Java类与对象

面向对象程序设计(简称OOP)是当今主流的程序设计范型,它已经取代了20世纪70年代的“结构化”过程化程序设计开发技术。Java是完全面向对象的,必须熟悉OOP才能够编写Java程序。面向对象的程序是由对象组成的,每个对象包…

SVN备份脚本

#!/bin/bash #svn全量备份脚本 wwytcode_path/home/wwytcode/project backup_path/home/bak Datedate %Y%m%d cd $backup_path echo date >> $back_path/svn_backup.log svnversionsvnlook youngest $wwytcode_path svnadmin dump --revision 0:$svnversion $wwytcode_pa…

Java基础:Java面向对象

面向过程的优点是性能比面向对象高,不需要面向对象的实例化;缺点是不容易维护、复用和扩展。面向对象的优点是具有封装、继承、多态的特性,因而容易维护、复用和扩展,可以设计出低耦合的系统;缺点是由于需要实例化对象…

薪资生成

import openpyxl from openpyxl.styles import Font,Alignment,Side,Border#设置字体样式 fontFont(name宋体,size20,boldTrue) font2Font(name宋体,size12,boldTrue) alignmentAlignment(horizontalcenter,verticalcenter,wrap_textTrue) sideSide(stylethin ,color000000) …

Java基础:Java抽象接口

在Java中,一个没有方法体的方法应该定义为抽象方法,而如果一个类中含有抽象方法,则该类必须定义为一个抽象类。接口是功能的集合,同样可看做是一种特殊的数据类型,是比抽象类更为抽象的类,接口只描述所应该…

13 张图带你学懂 Kubernetes Service(转载)

在 Kubernetes 中 Service 主要有4种不同的类型,其中的 ClusterIP 是最基础的,如下图所示: 当我们创建一个 NodePort 的 Service 时,它也会创建一个 ClusterIP,而如果你创建一个 LoadBalancer,它就会创建一…

Java基础:Java异常机制

异常是程序运行过程中出现的错误。Java 把异常当作对象来处理,把异常信息封装成了一个类,并定义一个基类java.lang.Throwable作为所有异常的超类。Throwable : 它是所有错误与异常的超类(祖宗类),有两个子类 Error 和 Exception。…

JavaWeb:Servlet的应用及接口介绍

广义的 Servlet 泛指在服务器上运行的 Java 程序,但是这个 Java 程序,并不能独立运行(因为 Servlet 没有 main 方法),需要部署在相应的 Servlet 容器中,比如 Tomcat 和 Jetty。Servlet 主要功能在于交互式地…

DOCKERFILE参数注解

Dockerfile由一行行命令语句组成,并且支持以#开头的注释行。 一般的,Dockerfile 分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。 Dockerfile的指令是忽略大小写的,建议使用大写,使用 # 作为…

Zookeeper:分布式过程协同技术

Zookeeper 是一个高性能的分布式一致系统,在分布式系统中有着广泛的应用。基于它,可以实现诸如“分布式同步”、“配置管理”、“命名空间管理”等众多功能,是分布式系统中常见的基础系统。Zookeeper 主要用来解决分布式集群中应用系统的一致…

面试题2021-2-24

给某CentOs6虑拟机添加了新的数据盘,设备名为/de/sdd.写命令格式化满加的效的并挂载到指定目录/opt fdisk -l mkfs.ext4 /de/sdd mount /de/sdd /opt 如何查看与RabbtMQ服务器之间的establish状态连接数?netstat -an |grep ESTABLISHED |grep tcp |wc -l…