“SET key value [EX seconds] [PX milliseconds] [NX|XX]“和redis分布式锁

一、可选参数介绍

  • EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
  • PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
  • XX :只在键已经存在时,才对键进行设置操作。

  因为 SET 命令可以通过参数来实现和 SETNX 、 SETEX 和 PSETEX 三个命令的效果,所以将来的 Redis 版本可能会废弃并最终移除 SETNX 、 SETEX 和 PSETEX 这三个命令。

二、其他介绍

2.1 可用版本:

大于1.0.0

2.2 返回值

在 Redis 2.6.12 版本以前, SET 命令总是返回 OK 。
从 Redis 2.6.12 版本开始, SET 在设置操作成功完成时,才返回 OK 。
如果设置了 NX 或者 XX ,但因为条件没达到而造成设置操作未执行,那么命令返回空批量回(NULL Bulk Reply)。

三、命令行实操

# 对不存在的键进行设置

redis 127.0.0.1:6379> SET key "value"
OK

redis 127.0.0.1:6379> GET key
"value"


# 对已存在的键进行设置

redis 127.0.0.1:6379> SET key "new-value"
OK

redis 127.0.0.1:6379> GET key
"new-value"


# 使用 EX 选项

redis 127.0.0.1:6379> SET key-with-expire-time "hello" EX 10086
OK

redis 127.0.0.1:6379> GET key-with-expire-time
"hello"

redis 127.0.0.1:6379> TTL key-with-expire-time
(integer) 10069


# 使用 PX 选项

redis 127.0.0.1:6379> SET key-with-pexpire-time "moto" PX 123321
OK

redis 127.0.0.1:6379> GET key-with-pexpire-time
"moto"

redis 127.0.0.1:6379> PTTL key-with-pexpire-time
(integer) 111939


# 使用 NX 选项

redis 127.0.0.1:6379> SET not-exists-key "value" NX
OK      # 键不存在,设置成功

redis 127.0.0.1:6379> GET not-exists-key
"value"

redis 127.0.0.1:6379> SET not-exists-key "new-value" NX
(nil)   # 键已经存在,设置失败

redis 127.0.0.1:6379> GEt not-exists-key
"value" # 维持原值不变


# 使用 XX 选项

redis 127.0.0.1:6379> EXISTS exists-key
(integer) 0

redis 127.0.0.1:6379> SET exists-key "value" XX
(nil)   # 因为键不存在,设置失败

redis 127.0.0.1:6379> SET exists-key "value"
OK      # 先给键设置一个值

redis 127.0.0.1:6379> SET exists-key "new-value" XX
OK      # 设置新值成功

redis 127.0.0.1:6379> GET exists-key
"new-value"


# NX 或 XX 可以和 EX 或者 PX 组合使用

redis 127.0.0.1:6379> SET key-with-expire-and-NX "hello" EX 10086 NX
OK

redis 127.0.0.1:6379> GET key-with-expire-and-NX
"hello"

redis 127.0.0.1:6379> TTL key-with-expire-and-NX
(integer) 10063

redis 127.0.0.1:6379> SET key-with-pexpire-and-XX "old value"
OK

redis 127.0.0.1:6379> SET key-with-pexpire-and-XX "new value" PX 123321
OK

redis 127.0.0.1:6379> GET key-with-pexpire-and-XX
"new value"

redis 127.0.0.1:6379> PTTL key-with-pexpire-and-XX
(integer) 112999


# EX 和 PX 可以同时出现,但后面给出的选项会覆盖前面给出的选项

redis 127.0.0.1:6379> SET key "value" EX 1000 PX 5000000
OK

redis 127.0.0.1:6379> TTL key
(integer) 4993  # 这是 PX 参数设置的值

redis 127.0.0.1:6379> SET another-key "value" PX 5000000 EX 1000
OK

redis 127.0.0.1:6379> TTL another-key
(integer) 997   # 这是 EX 参数设置的值

四、锁使用模式

命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
客户端执行以上的命令:

  • 如果服务器返回 OK ,那么这个客户端获得锁;
  • 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试;

设置的过期时间到达之后,锁将自动释放。

但是这样,锁仍然存在问题。

五、锁优化

修复setnx问题后,我们继续分析有另外一个进程进入的情况,考虑按时间顺序如下场景:
如果锁有效时间10s:

  1. 进程A加锁成功,开始操作,操作时间过了锁有效期;
  2. 进程B申请加锁,开始操作;
  3. 进程A释放锁(进程B的锁被释放掉了);

问题:

  • 锁过期时间控制:如果一个进程执行时间过长,导致锁超期释放,别的进程可获取锁,两个进程同时拥有一把锁,操作同一份RMW 代码;
  • 释放别人的锁:进程A释放锁的时候,把别的进程的锁释放掉了;

5.1 释放别人加的锁

上述问题,图示如下:
在这里插入图片描述

  1. 进程A加锁成功;
  2. 进程A执行时间超出锁有效期,进程B获取锁;
  3. 进程A执行完成,释放了B进程加的锁;

5.2 解决方案:

可以通过以下修改,让这个锁实现更健壮:

  1. 通过控制加锁的value值为 唯一值:SET key random PX 5000 NX,其中,random应该是唯一值;
  2. 删除锁的时候,先获取锁的值是否等于random值,等于则释放,为了保证原子性采用lua脚
    本;

lua脚本:

//释放锁 比较random是否相等,避免误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
	return redis.call("del",KEYS[1])
else
	return 0
end

整体代码开起来如下:

function writeData(filename, data) {
	uuid = UUID.random().tostring;
	var lock = redis.set(filename,uuid,px,5000,NX);
if (!lock) {
	throw 'Failed to acquire lock';
}
try {
	var file = storage.readFile(filename);
	var updated = updateContents(file, data);
	storage.writeFile(filename, updated);
} finally {
	redis.eval("if redis.call("get",KEYS[1]) == ARGV[1] then
	return redis.call("del",KEYS[1])
else
	return 0
end")
}
}

  这段代码看起来,解决了我们想到的很多问题,事实上,很多项目中依然采用着,除了上述我们提及过:锁过期时间与线程执行时间不好确定之外,我们继续分析,还有什么问题:

  1. 锁过期时间;
  2. redis不能是主从部署方式;
  3. 更宽泛的说来,不支持很多锁的功能:比如,是否公平,是否可重入;

其实redisson已经帮我们提供了更加健壮简洁的锁实现。

五、Redisson

  Redisson 是架设在 Redis 基础上的一个 Java驻内存数据网格框架, 充分利用 Redis 键值数据库提供的一系列优势, 基于 Java 使用工具包中常用接口, 为使用者提供了 一系列具有分布式特性的常用工具类。其中就提供了一种RedLock的加锁算法和实现,讨论之前,我们可以先分析单机版的Redisson如何实现一个分布式锁。

RedLock官方介绍: Distributed locks with Redis – Redis

由于 Redisson自身太过于复杂, 设计的 API 调用大多用 Netty 相关, 所以本文只对 如何加锁、如何实现重入锁,释放锁进行讨论.

5.1 加锁流程

在这里插入图片描述

5.2 解锁流程

在这里插入图片描述

5.3 Redlock

5.3.1 Redlock 算法介绍

部署多台 Redis, 各实例之间相互独立, 不存在主从复制或者其他集群协调机制。
使用方式大体如下:

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock1");
RLock lock3 = redissonInstance3.getLock("lock1");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock1 lock1
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();

原理:
  加入设置节点数N=5,所以我们需要在不同的计算机或虚拟机上运行5个Redis主站,以确保它们会以一种基本独立的方式失败。

为了获得锁,客户端执行以下操作:

  • 获取当前的时间,以毫秒为单位;
  • 依次在所有N个实例中获取锁,在所有实例中使用相同的键名和随机值。在步骤2中,当在每个实例中设置锁时,客户端使用一个与总的锁自动释放时间相比很小的超时来获取它。例如,如果自动释放时间是10秒,超时可以在~ 5-50毫秒范围内。这可以防止客户端在试图与Redis节点对话时长时间受阻:如果一个实例不可用,我们应该尽快尝试与下一个实例对话;
  • 客户端通过从当前时间减去步骤1中获得的时间戳,计算出获得锁所需的时间。如果并且只有当客户端能够在大多数实例(至少3个)中获取锁,并且获取锁的总时间小于锁的有效期,锁才被认为是被获取;
  • 如果锁被获取,其有效性时间被认为是初始有效性时间减去经过的时间,如步骤3中计算的那样;
  • 如果客户端由于某种原因未能获得锁(要么它无法锁定N/2+1个实例,要么有效性时间为负数),它将尝试解锁所有的实例(甚至是它认为无法锁定的实例);

5.3.2 Redlock 算法是否安全(了解即可)

  分布式系统研究员Martin Kleppmann曾对 RedLock算法深入分析并强烈反对在生产中使用,其主要原因就是redlock的实现依赖了服务器的本地时钟。

如下例子,还是5个节点,Redlock失效:

  1. 客户端1获得了A、B、C节点上的锁,由于网络问题,无法到达D和E;
  2. 节点C上的时钟向前跳动,导致锁过期;
  3. 客户端2获得了节点C、D、E的锁,由于网络问题,A和B不能被联系到;
  4. 客户端1和2现在都认为他们持有锁;

也或者,在第二步骤,节点c如果出现宕机,恢复后没有之前的数据,客户端2也可能获取到锁。

再看如下例子:

  1. 客户端1请求锁定节点A、B、C、D、E;
  2. 当对客户端1的响应在路途中时,客户端1进入停止世界的GC;
  3. 所有Redis节点的锁都过期了;
  4. 客户端2获得了节点A、B、C、D、E的锁;
  5. 客户端1完成了GC,并收到了来自Redis节点的响应,表明它成功获得了锁(当进程暂停时,它们被保存在客户端1的内核网络缓冲区);
  6. 客户端1和2现在都认为他们持有该锁;

六、RedissonLock类

问:有没有哪个工具类有SET key value [EX seconds] [PX milliseconds] [NX]的api呢?
——答:在 Java 中,Redisson提供了一个RedissonLock类,它有类似于SET key value [EX seconds] [PX milliseconds] [NX]的 API 。

以下是一个使用RedissonLock实现上述功能的简单示例:

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisLock {

    @Autowired
    private RedissonClient redissonClient;

    private Config config;

    // 在构造函数中初始化 RedissonClient
    public RedisLock() {
        // 使用配置类 Config 来配置 Redisson 客户端
        config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        // 创建 Redisson 客户端实例
        redissonClient = Redisson.create(config);
    }

    // 获取锁并设置过期时间的方法
    public void lock(String lockName, int expireTime, TimeUnit timeUnit) {
        RLock lock = redissonClient.getLock(lockName);
        lock.lock(expireTime, timeUnit);
    }

    // 释放锁的方法
    public void unlock(String lockName) {
        RLock lock = redissonClient.getLock(lockName);
        lock.unlock();
    }
}

  在上述代码中,首先通过@Autowired注解注入RedissonClient实例,然后在lock()方法中通过redissonClient.getLock(lockName)获取锁,其中lockName是锁的名称,可根据实际需求进行调整,最后使用lock.unlock()方法释放锁。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【QT】VS-code报错:LNK2019: 无法解析的外部符号

目录 0.环境 1.问题简述 2.分析报错原因 3.解决方法 1)set() 相关语句 2)target_link_libraries() 相关语句 4.参考 0.环境 windows11 、 vs-code 、 qt 、 c、编译器为vs2019-x86_amd64 1.问题简述 项目编译release版本时会报错:报错…

代码随想录算法训练营第十四天 | 110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和

代码随想录算法训练营第十四天 | 110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和 文章目录 代码随想录算法训练营第十四天 | 110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和1 LeetCode 110.平衡二叉树2 LeetCode 257.二叉树的所有路径3 LeetCode 404.左叶子之和…

浅析一款非驱动考试网关程序(一)

前言 监考程序需要对网络流量进行过滤,不允许除了考试网站以外的其他网站的访问。其实就是实现了一个小型的网关程序,一般地有驱动实现和非驱动实现两种方式。本文只针对一款简易的非驱动实现的监考程序进行分析。 注意:本文通过对某考试监…

第十篇【传奇开心果系列】Python的OpenCV技术点案例示例:图像分割

传奇开心果短博文系列 系列短博文目录Python的OpenCV技术点案例示例系列短博文目录一、前言二、OpenCV图像分割介绍三、OpenCV分割算法示例代码四、归纳总结系列短博文目录 Python的OpenCV技术点案例示例系列 短博文目录 一、前言 OpenCV是一个广泛应用于计算机视觉和图像处…

2023年09月CCF-GESP编程能力等级认证Python编程五级真题解析

Python等级认证GESP(1~6级)全部真题・点这里 一、单选题(共15题,共30分) 第1题 近年来,线上授课变得普遍,很多有助于改善教学效果的设备也逐渐流行,其中包括比较常用的手写板,那么它属于哪类设备?( ) A:输入 B:输出 C:控制 D:记录 答案:A 第2题 以下关于…

计算机服务器中了mkp勒索病毒如何解密,mkp勒索病毒解密流程

随着网络技术的不断发展与应用,越来越多的企业走向数字化办公模式,计算机极大地方便了企业的正常生产运营,但网络威胁的手段也不断增加。近期,云天数据恢复接到很多企业的求助,企业的计算机服务器遭到了mkp勒索病毒攻击…

Qt 常见容器类用法(一)

目录 QMap类 QHash类 QVector类 QMap类 QMap<key,T>提供一个从类型为Key的键到类型为T的值的映射。通常&#xff0c;QMap存储的数据形式是一个键对应一个值&#xff0c;并且按照键Key的次序存储数据。为了能够支持一键多值的情况&#xff0c;QMap提供QMap<key,T&g…

相机图像质量研究(4)常见问题总结:光学结构对成像的影响--焦距

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

MPLS——多协议标签交换

目录 1 多协议标签交换 MPLS 1.1 MPLS 的工作原理 1.1.1 MPLS 工作特点 1.1.2 MPLS 协议的基本原理 1.1.3 MPLS 的基本工作过程 1.2 转发等价类 FEC 1.2.1 FEC 用于负载平衡 1.3 MPLS 首部的位置与格式 1.3.1 MPLS 首部的位置 1.3.2 MPLS 首部的格式 1.4 新一代的…

Jmeter 示例,格式为001-100,按顺序生成三位数的函数

1.先添加一个循环控制器&#xff0c;每次执行生成一个数, 2.添加一个beanshell Sample,编写代码,把按00X这个格式的数字&#xff0c;赋值给一个变量LoopCount // 从JMeter变量中获取当前的计数器值 String loopCountStr vars.get("LoopCount"); int loopCount (lo…

1896_Linux中free命令小结

1896_Linux中free命令小结 全部学习汇总&#xff1a; little_bits_of_linux: 一星半点的Linux经验 (gitee.com) 查看Linux中存储的使用情况&#xff0c;我经常使用htop&#xff0c;毕竟这个命令提供的信息是十分直观的。我现在常用的一个小主机其实是我的树莓派3B&#xff0c;虽…

ETL是什么,有哪些ETL工具?就业前景如何?

ETL是什么 ETL&#xff08;Extract-Transform-Load&#xff09;&#xff0c;用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目标端的过程。ETL一词较常用在数据仓库&#xff0c;但其对象并不限于数据仓库。它可以自动化数据处理过程&#xff0c;减少…

C#向数组指定索引位置插入新的元素值:自定义插入方法 vs List<T>.Add(T) 方法

目录 一、使用的方法 1.自定义插入方法 2.使用List.Add(T) 方法 二、实例 1.示例1&#xff1a;List.Add(T) 方法 2.示例&#xff1a;自定义插入方法 一、使用的方法 1.自定义插入方法 首先需要定义一个一维数组&#xff0c;然后修改数组的长度(这里使用Length属性获取…

正点原子--STM32基本定时器学习笔记(1)

这部分是笔者对基本定时器的理论知识进行学习与总结&#xff01;主要记录学习过程中遇到的重难点&#xff0c;其他一些基础点就一笔带过了&#xff01; 1. 定时器概述 1.1 软件定时原理 使用纯软件&#xff08;CPU死等&#xff09;的方式实现定时&#xff08;延时&#xff0…

C++ 动态规划 状态压缩DP 蒙德里安的梦想

求把 NM 的棋盘分割成若干个 12 的长方形&#xff0c;有多少种方案。 例如当 N2&#xff0c;M4 时&#xff0c;共有 5 种方案。当 N2&#xff0c;M3 时&#xff0c;共有 3 种方案。 如下图所示&#xff1a; 2411_1.jpg 输入格式 输入包含多组测试用例。 每组测试用例占一行…

A64指令集架构之PCS过程调用标准

Arm架构对通用寄存器的使用几乎没有限制。简而言之&#xff0c;整数寄存器和浮点寄存器都是通用寄存器。然而&#xff0c;如果你希望你的代码与他人编写的代码互动&#xff0c;或者与编译器生成的代码互动&#xff0c;那么你需要就寄存器的使用达成一致的规则。对于Arm架构&…

Chronos靶机渗透

Chronos靶机 一.信息收集1.靶机IP地址确认2.目录扫描3.常见漏洞扫描5.web网站探测1.网页2.源代码 二.网站渗透1.命令执行2.抓包---burp suite3.反弹shell 三.提权1.node.js原核污染第一个flag 2.sudo提权第二个flag 一.信息收集 1.靶机IP地址确认 ┌──(root㉿kali)-[/] └─…

【Linux系统化学习】文件描述符fd

目录 基础IO预备知识 C语言文件接口 "w"的方式打开&#xff0c;fputs写入 以"a"的方式打开&#xff0c;fputs写入 使用位图传参 系统调用操作文件 open的使用 第一种形式 第二种形式 write() 文件描述符 文件描述符和进程的关系 默认的三个IO流…

JAVASE进阶:高级写法——方法引用(Mybatis-Plus必学前置知识)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;JAVASE进阶&#xff1a;一文精通Stream流函数式编程 &#x1f4da;订阅专栏&#xff1a;JAVASE进阶 希望文章对你们有所帮助 相信…

SpringCloud--Eureka注册中心服务搭建注册以及服务发现

注意springboot以及springcloud版本&#xff0c;可能有莫名其妙的错误&#xff0c;这里使用的是springboot-2.6.13&#xff0c;springcloud-2021.0.5 一&#xff0c;Eureka-Server搭建&#xff1a; 1.创建项目&#xff1a;引入依赖 <dependency><groupId>org.sp…