Redis分布式锁的优化

news2024/11/23 7:59:19

分布式锁

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。

分布式锁的实现

分布式锁的核心是实现多进程之间互斥,而满足这一点的方式有很多,常见的有三种:

MySQLRedisZookeeper
互斥利用mysql本身的互斥锁机制利用setnx这样的互斥命令利用节点的唯一性和有序性实现互斥
高可用
高性能一般一般
安全性断开连接,自动释放锁利用锁超时时间,到期释放临时节点,断开连接自动释放

基于 Redis 的分布式锁

用 Redis 实现分布式锁,主要应用到的是 SETNX key value命令(如果不存在,则设置)
主要要实现两个功能:

  1. 获取锁(设置一个 key)
  2. 释放锁 (删除 key)

基本思想是执行了 SETNX命令的线程获得锁,在完成操作后,需要删除 key,释放锁。
加锁:

@Override
public boolean tryLock(long timeoutSec) {
    // 获取线程标示
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁
    Boolean success = stringRedisTemplate.opsForValue()
            .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(success);
}

释放锁:

@Override
public void unlock() {
    // 获取线程标示
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁中的标示
    String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
    // 释放锁
    stringRedisTemplate.delete(KEY_PREFIX + name);
}

可是这里会存在一个隐患——假设该线程发生阻塞(或者其他问题),一直不释放锁(删除 key)这可怎么办?

为了解决这个问题,我们需要为 key 设计一个超时时间,让它超时失效;但是这个超时时间的长短却不好确定:

  1. 设置过短,会导致其他线程提前获得锁,引发线程安全问题
  2. 设置过长,线程需要额外等待

锁的误删
引用
超时时间是一个非常不好把握的东西,因为业务线程的阻塞时间是不可预估的,在极端情况下,它总能阻塞到 lock 超时失效,正如上图中的线程1,锁超时释放了,导致线程2也进来了,这时候 lock 是 线程2的锁了(key 相同,value不同,value一般是线程唯一标识);假设这时候,线程1突然不阻塞了,它要释放锁,如果按照刚刚的代码逻辑的话,它会释放掉线程2的锁;线程2的锁被释放掉之后,又会导致其他线程进来(线程3),如此往复。。。
为了解决这个问题,需要在释放锁时多加一个判断,每个线程只释放自己的锁,不能释放别人的锁!
释放锁

@Override
public void unlock() {
    // 获取线程标示
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁中的标示
    String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
    
    // 判断标示是否一致
    if(threadId.equals(id)) {
        // 释放锁
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}

原子性问题

刚刚我们谈论的释放锁的逻辑:

  1. 判断当前锁是当前线程的锁
  2. 当前线程释放锁

可以看到释放锁是分两步完成的,如果你是对并发比较有感觉的话,应该一下子就知道这里会存在问题了。
分步执行,并发问题!
引用
假设 线程1 已经判断当前锁是它的锁了,正准备释放锁,可偏偏这时候它阻塞了(可能是 FULL GC 引起的),锁超时失效,线程2来加锁,这时候锁是线程2的了;可是如果线程1这时候醒过来,因为它已经执行了步骤1了的,所以这时候它会直接直接步骤2,释放锁(可是此时的锁不是线程1的了)

其实这就是一个原子性的问题,刚刚释放锁的两步应该是原子的,不可分的!

要使得其满足原子性,则需要在 Redis 中使用 Lua 脚本了。

引入 Lua 脚本保持原子性

lua 脚本:

-- 比较线程标示与锁中的标示是否一致
if(redis.call('get', KEYS[1]) ==  ARGV[1]) then
    -- 释放锁 del key
    return redis.call('del', KEYS[1])
end
return 0

Java 中调用执行:

public class SimpleRedisLock implements ILock {private String name;
    private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }@Override
    public boolean tryLock(long timeoutSec) {
        // 获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }@Override
    public void unlock() {
        // 调用lua脚本
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId());
    }
}

到了目前为止,我们设计的 Redis 分布式锁已经是生产可用的,相对完善的分布式锁了。

总结

这一次我们从秒杀场景的业务需求出发,一步步地利用 Redis 设计出一种生产可用的分布式锁:

实现思路:

  1. 利用set nx ex获取锁,并设置过期时间,保存线程标示
  2. 释放锁时先判断线程标示是否与自己一致,一致则删除锁 (Lua 脚本保证原子性)

有哪些特性?

  1. 利用set nx满足互斥性
  2. 利用set ex保证故障时锁依然能释放,避免死锁,提高安全性
  3. 利用Redis集群保证高可用和高并发特性

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

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

相关文章

韩顺平Java | C21网络编程

1 网络的相关概念 ip地址的组成&#xff1a;网络地址 主机地址 A类&#xff1a;0 ~ 2^7-1 0 ~ 127 B类&#xff1a;128 ~ 1282^6-1 128 ~ 191 C类&#xff1a;192 ~ 1922^5-1 192 ~ 223 D类&#xff1a;224 ~ 2242^4-1 224 ~ 239 E类&#xff1a;240 ~ 2402^3-1 240 ~ 2…

XenCenter 2024 导出虚拟机

选择导出 选择需要导出的虚拟机 导出位置&#xff0c;导出格式&#xff0c;名称 EULA 文档&#xff0c;根据自己需求配置 OVA是否需要加密验证&#xff0c;自己需要看&#xff0c;是否单独的OVA 确认导出配置&#xff0c;等待导出完成。 本地目录查看导出完成

批次号字符串的定制格式化处理

碰到个处理批次号的需求&#xff0c;处理成各种特殊的格式&#xff0c;方法还比较粗糙&#xff0c;可以优化&#xff0c;这里只做个记录。不仅仅适用于日期的格式化处理&#xff0c;特殊的字符规则都可以使用。 通过这个方法&#xff0c;维护成 "yyMMddpkx"这种特殊…

vue3中怎么点击按钮就上传文件

<el-button text type"primary" click"importBillExcel(row)">导入账单</el-button> // 导入客户账单Excel表 const importBillExcel (row) > {let input document.createElement(input)input.type fileinput.accept .pdf, .png, .zip…

基于知识图谱的跨项目安全缺陷报告预测方法

源自&#xff1a;软件学报 作者&#xff1a;郑炜, 刘程远, 吴潇雪, 陈翔, 成婧源, 孙小兵, 孙瑞阳 “人工智能技术与咨询” 发布 摘 要 安全缺陷报告可以描述软件产品中的安全关键漏洞. 为了消除软件产品的安全攻击风险, 安全缺陷报告(security bug report, SBR)预测越来越…

QT - 日志:qDebug/qInfo/qWarning/qCritical

篇一、日志打印函数 头文件&#xff1a; #include <QDebug> 代码&#xff1a;qDebug()<<"hello world!"; 其他打印级别&#xff1a; qInfo(): 普通信息 qDebug(): 调试信息 qWarning(): 警告信息 qCritical(): 严重错误 qFatal(): 致命错误 1. qDebug…

认识什么是Webpack

目录 1. 认识Webpack 1.1. 什么是Webpack?&#xff08;定义&#xff09; 1.2. 使用Webpack 1.2.1. 需求 1.2.2. 步骤 1.3. 入口和出口默认值 1.3.1. 需求代码如下 2. 修改Webpack打包入口和出口 2.1. 步骤&#xff1a; 2.2. 注意 3. Webpack自动生成html文件 3.1.…

OpenHarmony实战:轻量带屏解决方案之恒玄芯片移植案例

本文章基于恒玄科技 BES2600W 芯片的欧智通 Multi-modal V200Z-R 开发板&#xff0c;进行轻量带屏开发板的标准移植&#xff0c;开发了智能开关面板样例&#xff0c;同时实现了 ace_engine_lite、arkui_ui_lite、aafwk_lite、appexecfwk_lite、HDF 等部件基于 OpenHarmony Lite…

【C++庖丁解牛】高阶数据结构---红黑树详解(万字超详细全面介绍红黑树)

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 前言1.红黑树的概念2.红黑…

【Go】十七、进程、线程、协程

文章目录 1、进程、线程2、协程3、主死从随4、启动多个协程5、使用WaitGroup控制协程退出6、多协程操作同一个数据7、互斥锁8、读写锁9、deferrecover优化多协程 1、进程、线程 进程作为资源分配的单位&#xff0c;在内存中会为每个进程分配不同的内存区域 一个进程下面有多个…

为什么mac文件拖拽不了 mac文件拖不进硬盘里 macbookpro文件无法拖进移动硬盘 Tuxera NTFS for Mac 2023绿色

如果你是一位Mac用户&#xff0c;你可能会遇到这样的问题&#xff1a;你想把Mac上的文件拖拽到其他位置&#xff0c;比如桌面、文件夹或者外接硬盘&#xff0c;但是却发现无法操作&#xff0c;这是为什么呢&#xff1f;这篇文章将为你解答为什么mac文件拖拽不了&#xff0c;以及…

深入探索Go语言:hash/maphash实战应用与优化技巧

深入探索Go语言&#xff1a;hash/maphash实战应用与优化技巧 引言基础概念哈希函数简介Go中的哈希处理maphash的位置和结构关键特性 maphash的基本用法创建和使用Hash对象maphash.Hash的关键方法使用场景 maphash的高级技巧优化数据结构避免哈希碰撞实现自定义哈希函数 maphash…

使用 XCTU 进行 XBee 无线 (OTA) 固件更新

通过 X-CTU 无线 (OTA) 更新固件&#xff1a; 1. 将XBee加载到 XCTU。将此XBee模块AP参数设置为 API 模式 1 (AP1) 和波特率 115200 (BD7) 以加快进一步的步骤。 2. 在无线模块列表中&#xff0c;选择“发现无线模块”按钮。 3. XCTU 将执行网络发现。然后&#xff0c;您…

Open-Sora环境搭建推理测试

引子 Sora&#xff0c;2024年2月15日&#xff0c;OpenAI发布的人工智能文生视频大模型。支持60秒视频生成&#xff0c;震荡了国内国际学术圈、广告圈、AI教培圈。Sora最主要有三个优点&#xff1a;第一&#xff0c;“60s超长视频”&#xff0c;之前文本生成视频大模型一直无法真…

校园局域网钓鱼实例

Hello &#xff01; 我是"我是小恒不会java" 本文仅作为针对普通同学眼中的网络安全&#xff0c;设计的钓鱼案例也是怎么简陋怎么来 注&#xff1a;本文不会外传代码&#xff0c;后端已停止使用&#xff0c;仅作为学习使用 基本原理 内网主机扫描DNS劫持前端模拟后端…

篮球竞赛预约平台的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)篮球馆,篮球赛,竞赛项目,赛事预约

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读300套最新项目持续更新中..... 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含ja…

如何启用远程访问?

在当今信息化的社会中&#xff0c;远程访问已成为许多企业和个人不可或缺的工具。通过远程访问&#xff0c;用户可以在任何时间、地点轻松连接到他们的数据和应用程序&#xff0c;提高工作效率&#xff0c;增加便利性。本文将探讨如何启用远程访问&#xff0c;以及天联组网在解…

绿色wordpress外贸建站模板

绿色wordpress外贸建站模板 https://www.mymoban.com/wordpress/6.html

docker容器技术篇:Docker API配置与常用操作

docker容器技术篇&#xff1a;Docker API配置与使用 一、API具体是什么&#xff1f; 百科解释应用程序接口&#xff08;API&#xff09;&#xff0c;又称为应用编程接口&#xff0c;就是软件系统不同组成部分衔接的约定&#xff0c;蒙了吧&#xff01;&#xff01;&#xff0…

Python 之 Flask 框架学习

毕业那会使用过这个轻量级的框架&#xff0c;最近再来回看一下&#xff0c;依赖相关的就不多说了&#xff0c;直接从例子开始。下面示例中的 html 模板&#xff0c;千万记得要放到 templates 目录下。 Flask基础示例 hello world from flask import Flask, jsonify, url_fora…