“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"
OKredis 127.0.0.1:6379> GET key
"value"# 对已存在的键进行设置redis 127.0.0.1:6379> SET key "new-value"
OKredis 127.0.0.1:6379> GET key
"new-value"# 使用 EX 选项redis 127.0.0.1:6379> SET key-with-expire-time "hello" EX 10086
OKredis 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
OKredis 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) 0redis 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
OKredis 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) 10063redis 127.0.0.1:6379> SET key-with-pexpire-and-XX "old value"
OKredis 127.0.0.1:6379> SET key-with-pexpire-and-XX "new value" PX 123321
OKredis 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
OKredis 127.0.0.1:6379> TTL key
(integer) 4993  # 这是 PX 参数设置的值redis 127.0.0.1:6379> SET another-key "value" PX 5000000 EX 1000
OKredis 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] thenreturn redis.call("del",KEYS[1])
elsereturn 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] thenreturn redis.call("del",KEYS[1])
elsereturn 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 {@Autowiredprivate RedissonClient redissonClient;private Config config;// 在构造函数中初始化 RedissonClientpublic 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.hqwc.cn/news/455656.html

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

相关文章

CentOS基于volatility2的内存取证实验

CentOS,Redhat和Fedora 都是Red Hat体系,采用yum管理器,不同于Debian、Ubuntu作为Debian体系使用apt 本文以CentOS为例,采用avml制作内存镜像,并利用volatility官方所给工具制作profile符号文件,进行简单的…

javaEE - 23( 21000 字 Servlet 入门 -1 )

一:Servlet 1.1 Servlet 是什么 Servlet 是一种实现动态页面的技术. 是一组 Tomcat 提供给程序猿的 API, 帮助程序猿简单高效的开发一个 web app. 构建动态页面的技术有很多, 每种语言都有一些相关的库/框架来做这件事,Servlet 就是 Tomcat 这个 HTTP…

滑动窗口经典问题(算法村第十六关白银挑战)

最长字串专题 无重复字符的最长子串 3. 无重复字符的最长子串 - 力扣(LeetCode) 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是…

ChatGPT高效提问—prompt基础

ChatGPT高效提问—prompt基础 ​ 设计一个好的prompt对于获取理想的生成结果至关重要。通过选择合适的关键词、提供明确的上下文、设置特定的约束条件,可以引导模型生成符合预期的回复。例如,在对话中,可以使用明确的问题或陈述引导模型生成…

1.0 Zookeeper 分布式配置服务教程

ZooKeeper 是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。 ZooKeeper 的架构通过冗余服务实现高可用性。 Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高…

远程控制APP,你的远程控制专家

在这个科技日新月异的时代,我们的生活被各种手机软件所包围。几乎每个人都有一个甚至多个手机,你是否也有遇到过需要远程操作自己某一台手机的场景呢?今天,我要向大家推荐一款神奇的手机远程操作神器,让你可以随时随地…

windows集成的hyper-v三种网络形式

windows集成了hyper-v,有人反应不习惯使用hyper-v,hyper-v的一些使用习惯确实异于vmware的workstation,比如说hyper-v的三种虚拟交换机,和VMware worksation网络模式叫法有很大差异,这篇文章主要想谈谈hyper-v的三种虚拟交换机以及适应场景。 如图所示&…

Python中的for循环用法详解,一文搞定它

文章目录 for循环1.for循环的基本语法(1)遍历不等长多级容器(2)遍历不等长多级容器(3)遍历等长的容器 2.变量的解包3.for...else【详细讲解】4.range对象5.总结6.打印 1 ~ 10 跳过57.打印菱形小星星 for循环…

大众日报《大众日报》投稿要求//投稿邮箱

大众日报《大众日报》投稿要求 《大众日报》2000字符,1个月内,可加急。 《大众日报》教育纯新闻稿,500字左右 《大众日报》理论版,主要收思政稿,先看稿子。2000字符;有稿最快一周 山东党机关报纸《大众日…

感悟笔记——2024年2月5日

今日阅读了一篇挺有深度的文章,主要阐述进入职场后的大部分人,是怎么逐渐沦为螺丝钉的?即使起点巨高的优等生,也不可避免。文章指路: 「优等生思维」正在将你变成「螺丝钉」和「老黄牛」从小到大,我一直都是那个「别…

【Java 数据结构】泛型进阶

泛型 1 什么是泛型2 引出泛型2.1 语法 3 泛型类的使用3.1 语法3.2 示例3.3 类型推导(Type Inference) 泛型是如何编译的擦除机制裸类型4 泛型的上界4.1 语法4.2 示例4.3 复杂示例 5 泛型方法5.1 定义语法5.2 示例5.3 使用示例-可以类型推导5.4 使用示例-不使用类型推导 6 通配符…

AI嵌入式K210项目(26)-二维码识别

文章目录 前言一、什么是二维码?二、实验准备三、实验过程四、API接口总结 前言 本章介绍基于机器视觉实现二维码识别,主要包含两个过程,首先检测图像中是否有二维码,如果有则框出并打印二维码信息; 一、什么是二维码…