【Redis】Redis 如何实现分布式锁
Redis 如何实现分布式锁
- 1. 什么是分布式锁
- 1.1 分布式锁的特点
- 1.2 分布式锁的场景
- 1.3 分布式锁的实现方式
- 2. Redis 实现分布式锁
- 2.1 setnx + expire
- 2.2 set ex px nx
- 2.3 set ex px nx + 校验唯一随机值,再删除
- 2.4 Redisson 实现分布式锁
1. 什么是分布式锁
分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
1.1 分布式锁的特点

-
互斥性:任意时刻,只有一个客户端能持有锁;
-
可重入性:一个线程获取锁之后,可以再次对其请求加锁;
-
锁超时释放:持有锁超时释放,防止不必要的资源浪费,也可以防止死锁;
-
高效、高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效;
-
安全性:锁只能被持有的客户端删除,不能被其他客户端删除。
1.2 分布式锁的场景
-
使用分布式锁的场景一般需要满足以下场景:
- 系统是一个
分布式系统,Java 的锁已经锁不住了; - 操作
共享资源,比如库里唯一的用户数据; 同步访问,即多个进程同时操作共享资源。
- 系统是一个
-
分布式锁的业务场景
- 扣减库存
- 抢红包
- …
1.3 分布式锁的实现方式
-
数据库乐观锁;
-
基于 ZooKeeper 的分布式锁;
-
基于 Redis 的分布式锁。
这里主要介绍使用 Redis 实现分布式锁的方案。
2. Redis 实现分布式锁
2.1 setnx + expire
SETNX 是 SET IF NOT EXISTS 的简写。命令格式是 SETNX key value,如果 lockKey 不存在,则 SETNX 成功返回 1,如果这个 lockKey 已经存在了,则返回 0。
伪代码:
// 1. 加锁
if(jedis.setnx(lockKey, lockValue) == 1){// 2. 设置过期时间jedis.expire(lockKey, expireTime);try {// 3. 业务处理do something;} catch (Exception e) {log.error("处理失败,", e);} finally {// 4. 释放锁jedis.del(lockKey);}
}
在这个方案中 setnx 与 expire 「不是原子操作」,如果执行完第一步 jedis.setnx() 加锁后异常了,第二步 jedis.expire() 未执行,相当于这个锁没有过期时间,「有产生死锁的可能」。正对这个问题如何改进?
2.2 set ex px nx
基于 Redis 的 SET 扩展命令(SET key value[EX seconds][PX milliseconds][NX|XX]),保证 SETNX + EXPIRE 两条指令的原子性。
-
EX second :设置键的过期时间为 second 秒;
-
PX millisecond :设置键的过期时间为 millisecond 毫秒;
-
NX :表示 key 不存在的时候,才能 set 成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取;
-
XX :只在键已经存在时,才对键进行设置操作。
伪代码:
// 1. 加锁并设置过期时间
if(jedis.set(lockKey, lockValue, "NX", "EX", 100s) == 1){try {// 2. 业务处理do something;} catch (Exception e) {log.error("处理失败,", e);} finally {// 3. 释放锁jedis.del(lockKey);}
}
在这个方案中存在两个问题:
-
锁过期释放了,业务还没执行完。假设线程a获取锁成功,一直在执行临界区的代码。但是100s过去后,它还没执行完。但是,这时候锁已经过期了,此时线程b又请求过来。显然线程b就可以获得锁成功,也开始执行临界区的代码。那么问题就来了,临界区的业务代码都不是严格串行执行的了。
-
锁被别的线程误删。假设线程a执行完后,去释放锁。但是它不知道当前的锁可能是线程b持有的(线程a去释放锁时,有可能过期时间已经到了,此时线程b进来占有了锁)。那线程a就把线程b的锁释放掉了,但是线程b临界区业务代码可能都还没执行完。
2.3 set ex px nx + 校验唯一随机值,再删除
既然锁可能被别的线程误删,那我们给 value 值设置一个标记当前线程唯一的随机数,在删除的时候,校验一下,就可以了。
伪代码:
// 1. 加锁并设置过期时间if(jedis.set(lockKey, uni_lockValue, "NX", "EX", 100s) == 1){try {// 2. 业务处理do something;} catch (Exception e) {log.error("处理失败,", e);} finally {// 3. 判断是不是当前线程加的锁if (uni_lockValue.equals(jedis.get(lockKey))) {// 4. 释放锁jedis.del(lockKey);}}}
这里的 3. 是非原子性的,我们使用 lua 脚本来优化一下
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1])
elsereturn 0
end;
这个方案还是会存在**「锁过期释放,业务没执行完」**的问题,有些小伙伴认为,稍微把锁过期时间设置长一些就可以了。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
2.4 Redisson 实现分布式锁

只要线程一加锁成功,就会启动一个 watch dog 看门狗,它是一个后台线程,会每隔 10s 检查一下,如果 线程1 还持有锁,那么就会不断的延长锁 key 的生存时间。Redisson 完美解决了「锁过期释放,业务没执行完」问题。
Redisson lock 和 tryLock 原理解析
package com.pointer.mall.common.util;import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;/*** @author gaoyang* @date 2023-02-23 20:24*/
@Slf4j
@Component
public class RedissonUtil {@Resourceprivate RedissonClient redissonClient;/*** 加锁** @param lockKey*/public void lock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);lock.lock();}/*** 带过期时间的锁** @param lockKey key* @param leaseTime 上锁后自动释放锁时间*/public void lock(String lockKey, long leaseTime) {RLock lock = redissonClient.getLock(lockKey);lock.lock(leaseTime, TimeUnit.SECONDS);}/*** 带超时时间的锁** @param lockKey key* @param leaseTime 上锁后自动释放锁时间* @param unit 时间单位*/public void lock(String lockKey, long leaseTime, TimeUnit unit) {RLock lock = redissonClient.getLock(lockKey);lock.lock(leaseTime, unit);}/*** 尝试获取锁** @param lockKey key* @return*/public boolean tryLock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);return lock.tryLock();}/*** 尝试获取锁** @param lockKey key* @param waitTime 最多等待时间* @param leaseTime 上锁后自动释放锁时间* @return boolean*/public boolean tryLock(String lockKey, long waitTime, long leaseTime) {RLock lock = redissonClient.getLock(lockKey);try {return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);} catch (InterruptedException e) {log.error("RedissonUtils - tryLock异常", e);}return false;}/*** 尝试获取锁** @param lockKey key* @param waitTime 最多等待时间* @param leaseTime 上锁后自动释放锁时间* @param unit 时间单位* @return boolean*/public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {RLock lock = redissonClient.getLock(lockKey);try {return lock.tryLock(waitTime, leaseTime, unit);} catch (InterruptedException e) {log.error("RedissonUtils - tryLock异常", e);}return false;}/*** 释放锁** @param lockKey key*/public void unlock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);lock.unlock();}/*** 是否存在锁** @param lockKey key* @return*/public boolean isLocked(String lockKey) {RLock lock = redissonClient.getLock(lockKey);return lock.isLocked();}
}
相关文章:
【Redis】Redis 如何实现分布式锁
Redis 如何实现分布式锁1. 什么是分布式锁1.1 分布式锁的特点1.2 分布式锁的场景1.3 分布式锁的实现方式2. Redis 实现分布式锁2.1 setnx expire2.2 set ex px nx2.3 set ex px nx 校验唯一随机值,再删除2.4 Redisson 实现分布式锁1. 什么是分布式锁 分布式锁其实…...
C++ 断言
文章目录前言assertstatic_assert前言 断言(Assertion)是一种常用的编程手段,用于排除程序中不应该出现的逻辑错误。它是一种很好的Debug工具。其作用是判断表达式是否为真。C提供了assert和static_assert来进行断言。在C库中也有断言,其中断言与C的相同…...
C++修炼之练气期第五层——引用
目录 1.引用的概念 2.引用的性质 3.常量引用 4.使用场景 1.作参数 2.作返回值 5.传值与传引用的效率比较 6.值和引用作为返回值的性能比较 7.引用与指针 指针与引用的不同点 要说C语言中哪个知识点最难学难懂,大部分人可能和我一样的答案——指针。C既然…...
从企业数字化发展的四个阶段,看数字化创新战略
《Edge: Value-Driven Digital Transformation》一书根据信息技术与企业业务发展的关系把企业的数字化分为了四个阶段: 技术与业务无关技术作为服务提供者开始合作科技引领差异化优势以技术为业务核心 下图展示了这四个阶段的特点: 通过了解和分析各个…...
vulnhub five86-1
总结:私钥登录,隐藏文件很多 目录 下载地址 漏洞分析 信息收集 网站渗透 爆破密码 提权 下载地址 Five86-1.zip (Size: 865 MB)Download (Mirror): https://download.vulnhub.com/five86/Five86-1.zip使用:下载以后打开压缩包,使用vm直…...
28个案例问题分析---01---redis没有及时更新问题--Redis
redis没有及时更新问题一:背景介绍二:前期准备pom依赖连接Redis工具类连接mysql工具类三:过程使用redis缓存,缓存用户年龄业务对应流程图使用redis缓存用户年龄对应代码四:总结一:背景介绍 业务中使用redis…...
[1.3_3]计算机系统概述——系统调用
文章目录第一章 计算机系统概述系统调用(一)什么是系统调用,有何作用(二)系统调用与库函数的区别(三)小例子:为什么系统调用是必须的(四)什么功能要用到系统调…...
Vue基础学习 第一个Vue程序 el挂载点 v-指令(1)
Vue简介 Vue是一个Javascript框架Vue框架可以简化Dom操作响应式数据驱动 : 页面是由数据生成的,当数据出现改动,页面也会即时改变 第一个Vue程序 Vue中文文档官网:https://v2.cn.vuejs.org/v2/guide/ 根据官方文档的说法&#…...
前端页面性能
提升页面性能的方法资源压缩合并,减少HTTP请求非核心代码异步加载异步加载方式?1)动态脚本加载、2)defer、3)async(在加载js的时候在script标签上添加这两个属性,<script src"./test.js" charset"utf-8" defer><…...
2023-03-04 反思
摘要: 当前的时期确实比较特殊,不但是对于一个生命周期的最后的挣扎,更是在经历了各种浮浮沉沉的波澜之后还有更多的波浪。 精神分析-GRY: 非常奇怪的一个跳梁小丑, 不过我个人认为用这个标签是对跳梁小丑的侮辱和上层管理者对于这种人的纵容有很大关系…...
奇思妙想:超链接唤起本地应用
文章目录分析实现参考很多人的博客都有这样的小玩意,点击之后就可以直接与博主进行对话,而且无需添加好友。 先研究一下网页源代码: <a href"tencent://message/?uin88888888&Siteqq&Menuyes">联系我</a>很明…...
初识数据结构——“数据结构与算法”
各位CSDN的uu们你们好呀,今天小雅兰进入一个全新的内容的学习,就是算法和数据结构啦,话不多说,让我们进入数据结构的世界吧 什么是数据结构? 什么是算法? 数据结构和算法的重要性 如何学好数据结构和算…...
华为OD机试Golang解题 - 计算网络信号
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典文章目录 华为Od必看系列使用说明本期题目…...
ESP32编译及运行错误记录
1、打印格式不对 一般都是因为日志中某个参数打印格式不匹配造成。 ESP_LOGI(TAG, "[APP] Free memory: %lu bytes", esp_get_free_heap_size());//将之前的%d 改为%lu 2、配置载不对 这里选择了蓝牙模块需要引入蓝牙组件才能编译通过 idf.py menuconfig Component…...
GEE开发之降雨(CHIRPS)数据获取和分析
GEE开发之降雨CHIRPS数据获取和分析1.数据介绍2.初识CHIRPS2.1 代码一2.2 代码二3.逐日数据分析和获取4.逐月数据分析和获取4.1 代码一4.2 代码二(简洁)5.逐年数据分析和获取5.1 代码一5.2 代码二(简洁)前言:主要获取和分析UCSB-CHG/CHIRPS/DAILY的日数据、月数据和…...
TypeScript中面向对象
面向对象 要想面向对象,操作对象,首先便要拥有对象; 要创建对象,必须要先定义类,所谓的类可以理解为对象的模型; 程序中可以根据类创建指定类型的对象; 举例来说: 可以通过Perso…...
Transformer 模型:入门详解(1)
动动发财的小手,点个赞吧! 简介 众所周知,transformer 架构是自然语言处理 (NLP) 领域的一项突破。它克服了 seq-to-seq 模型(如 RNN 等)无法捕获文本中的长期依赖性的局限性。事实证明,transformer 架构是…...
深入理解js中的new关键字
在js中我们经常会使用到new关键字,那我们在使用new关键字的时候,new到底做了什么呢?今天我们就来深入探究一下 1.初步使用 我们先来使用一下,这是一个正常操作 function Person() {this.name "John";}let person new…...
RT-Thread Nano(2) - 线程
参考:RT-Thread API参考手册: 线程管理 线程的分类:动态线程,静态线程 动态线程是系统自动从动态内存堆上分配栈空间的线程句柄(程序运行时再分配空间),静态线程是由用户分配栈空间与线程句柄(可以说是程序编译时已经分配好空间) 1.创建线程 创建一个动态线程 rt_thread_t …...
真香,Grafana开源Loki日志系统取代ELK?
一、Loki是什么? Loki是由Grafana Labs开源的一个水平可扩展、高可用性,多租户的日志聚合系统的日志聚合系统。它的设计初衷是为了解决在大规模分布式系统中,处理海量日志的问题。Loki采用了分布式的架构,并且与Prometheus、Graf…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
