Redis 分布式锁实现

news2024/10/7 20:27:39

Redis 分布式锁

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

特点:

  • 多线程可见
  • 互斥
  • 高可用
  • 高性能(高并发)
  • 安全性、可重入性、重试机制、锁超时自动续期等 …

加锁之后,对整个分布式集群都有效

  • 基于数据库
  • redis缓存:使用setnx上锁,使用del释放锁;设置过期时间,自动释放 set user 10 nx ex 120
  • zookeeper

实现基于分布式锁需要实现两个方法:
在这里插入图片描述

  • 获取锁

    确保只能有一个线程获取锁,确保添加锁和添加过期时间的原子性

    非阻塞:尝试一次,成功返回 true,失败返回 false

    set key name ex 10 nx #ex是设置超时时间,nx是互斥
    
  • 释放锁

    手动释放

    超时释放:获取锁时添加一个超时时间

    del key
    

Redis 分布式锁的初级版本

Lock 接口

public interface ILock {

    /**
     * 尝试获取锁
     *
     * @param timeoutSec 锁持有的超时时间,过期自动释放
     * @return true:成功/false:失败
     */
    boolean tryLock(long timeoutSec);

    /**
     * 释放锁
     */
    void unlock();
}

Lock 实现:

public class SimpleRedisLock implements ILock {

    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX = "lock:";

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

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

    @Override
    public void unlock() {
        //释放锁
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}
SimpleRedisLock lock = new SimpleRedisLock();
try {
    if(lock.tryLock(time)){
        //执行业务逻辑
    }
} finally {
    lock.unlock();
}

存在的问题:

  • 业务执行时间过长,导致锁超时,自动释放
  • 线程一,锁超时后,线程二又获取到锁,线程一执行完逻辑后,释放锁,此时释放的是线程二的锁

改进 Redis 分布式锁

  • 在获取锁时存入线程的标识(可以使用UUID)
  • 在释放锁时先获取锁中的标识,判断是否与当前的线程标识是否相等,是,则释放;不是,则不释放

在这里插入图片描述

public class SimpleRedisLock implements ILock {

    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @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);
        if (threadId.equals(id)) {
            //释放锁
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }
    }
}

存在的问题:

unlock 中判断锁标识的操作和释放锁的操作不是原子操作,如果threadId.equals(id)判断成功之后,产生了阻塞(如:Full GC时),导致多线程安全问题,解决方法可以使用 Lua 脚本,保证以上操作的原子性

再次改进 Redis 分布式锁

Redis提供了 Lua 脚本功能,在脚本中编写多条命令,确保多条命令执行时的原子性

释放锁思路:

  • 获取锁中的线程标识
  • 判断是否与当前的标识一致
  • 如果一致则释放(删除)锁,否则什么都不做
-- 锁的key
local key = KEY[1]
-- 当前线程标识
local threadId = ARGV[1]
-- 获取锁中的标识
local id = redis.call('get', key)
-- 比较线程标识与锁标识是否一致
if(id == threadId) then
    -- 释放锁
    return redis.call('del', key)
end
return 0
public class SimpleRedisLock implements ILock {

    private String name;
    private 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<>();
        //加载 Lua 脚本
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @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());
    }
}

Redisson

基于上述setnx实现的分布式锁还存在以下问题

  • 不可重入:同一个线程无法获取同一把锁
  • 不可重试:获取锁只尝试一次就返回 false,没有重试机制
  • 超时释放:锁超时释放虽然可以避免死锁,但是如果业务执行时间过长,也会导致锁释放,存在安全隐患
  • 主从一致性:如果 Redis 提供了主从集群,主从同步存在延迟,当主机宕机时,尚未同步至从节点,会出现安全问题

Redisson是一个在 Redis 基础实现分布式工具的集合,包括分布式锁

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

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

相关文章

多线程之常用线程安全类型分析

写在前面 本文一起看下在日常工作中我们经常用到的线程安全的数据类型&#xff0c;以及一些经验总结。 1&#xff1a;常用线程安全数据类型 1.1&#xff1a;jdk集合数据类型 jdk的集合数据类型分为两类&#xff0c;一种是线性数据结构&#xff0c;另外一种是字典结构&#…

【系统架构】第五章-软件工程基础知识(一)

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 软件工程 一、软件过程模型 1、瀑布模型 特点&#xff1a;前一个阶段工作的输出结果&#xff0c;是后一个阶段工作的输入 缺点&#xff1a; 软件需求的完整性、正确性等很难确定&#xff0c…

人力资源外包系统(社会实践管理系统)需求分析文档

该系统主要针对当前在校大学生、中专院校学生寒、暑假社会实践而设计的管理系统&#xff1b; 主要用户群为人力资源外包公司&#xff0c;也可以是各个学校管理学生使用&#xff1b; 主要功能有维护企业信息&#xff0c;招工信息&#xff0c;企业宣传资料&#xff1b;维护学校…

基于JavaSpringBoot+uniapp制作一个记账小程序

你不理财,财不理你,制作一个记账小程序对自己的收入/支出明细进行管理,守护好自己的钱袋子。 一、小程序1.1 项目创建1.2 首页1.3 收支报表页1.4 记账提交页1.5 记账列表页

NCI Core Framework

3.1 概述 NCI 核心包括以下必需的功能&#xff1a;  通过 NCI 传输命令、响应、通知和数据消息的数据包格式。  用于设备主机和 NFC 控制器之间不同操作&#xff08;第 4 节中指定&#xff09;的命令、响应和通知的定义。 &#xff08;本规范后面的一些部分定义了不属于…

一起学SF框架系列4.7-模块context-MessageResource

Spring通过MessageSource接口提供了国际化&#xff08;“i18n”&#xff09;功能。搞明白MessageResource相关接口和类&#xff0c;基本就掌握了国际化功能。 MessageSource接口 定义了消息访问接口。 String getMessage&#xff08;String code&#xff0c;Object[]args&…

全网唯一!Matlab原神配色包MGenshin

前段时间&#xff0c;在原神官网逛了一圈&#xff0c;发现里面角色的原画配色十分的好看&#xff0c;便突发奇想&#xff1a;要是把原神配色用在SCI论文插图上&#xff0c;emmmmm…… 于是&#xff0c;我默默打开了自己的Matlab&#xff0c;用TheColor工具箱的图片主题色提取功…

数据驱动的商业决策:BI在企业中的重要性

第一章&#xff1a;引言 在当今数字化时代&#xff0c;数据被广泛应用于各个行业和领域。对企业而言&#xff0c;数据是一项宝贵的资源&#xff0c;可以为其带来无限的商机和竞争优势。然而&#xff0c;海量的数据本身并不具备实际意义&#xff0c;只有通过有效的数据分析和洞…

亚马逊云科技中国峰会:Amazon DeepRacer-梦想加速度

零&#xff1a;前言 你是否憧憬在赛车赛道上开车&#xff0c;享受疾驰而过的感觉&#xff0c;感受无与伦比的驾驶乐趣&#xff1f; Amazon DeepRacer可以满足你的梦想&#xff0c;虚竹哥带你了解Amazon DeepRacer。 它为用户提供了一个虚拟仿真环境和一个真实赛车模型&#xf…

【JDK环境配置】| 两种JDK环境能在同一台电脑共存吗?(文末送书)

目录 &#x1f981; 前言&#x1f981; 基础环境&#x1f981; 安装JDK1.8Ⅰ. 下载Ⅱ. 安装 &#x1f981; 在项目里更改JDK版本---------------------------------------------福利在下面--------------------------------------------------&#x1f981; 福利&#xff08;送…

《基于同态加密和秘密分享的纵向联邦LR协议研究》论文阅读

论文地址&#xff1a;https://xueshu.baidu.com/usercenter/paper/show?paperid1b7e04e0r41x0ax0976q0gy0m5242465 摘要 提出了一种新颖的两方纵向联邦逻辑回归协议,并在半诚实安全模型下证明了该协议的安全性, 包括模型训练流程和模型推理流程的安全性,且无需对非线性函数使…

C++四种基本类型转换

C四种基本类型转换 1.static_cast2.const_cast3.reinterpret_cast4 .dynamic_cast 1.static_cast 用法: static_cast<type_name> (expression) 说明:该运算符把expression转换为typen_name类型&#xff0c;static_cast在编译时使用类型信息执行转换,在转换执行必要的检测…

spring事务源码详解-spring原码(一)

前面说过了aop源码&#xff0c;这里再稍微回顾一下 我们会用注解EnableAspectJautoProxy开启aop 当我们用了proxytargetClass会强制cglib动态代理 源码里有Import 里面会注册AnnotionAwareAspectJAutoProxyCreator 后面会在beanDefinationMap获取到所有定义的Objects循环 …

2020新基建决赛-crypto-onepiece

onepiece 一、概要 1、标题&#xff1a;onepiece 2、关键字&#xff1a;e2&#xff0c;rabin&#xff0c;凯撒 3、比赛&#xff1a;2020新基建决赛 4、工具&#xff1a;python&#xff0c;米斯特工具 二、开始 1、题目分析 题目给了一个pubkey.pem和onepiece.enc&#xff…

C++右值引用 移动语义 完美转发 引用叠加

右值引用 MyString浅拷贝与深拷贝浅赋值与深赋值 左值与右值左值概念左值右值与函数的结合移动构造函数移动赋值函数移动构造和移动赋值的应用 移动语义 有点问题完美转发引用叠加 MyString 浅拷贝与深拷贝 s1先在堆区申请了空间&#xff0c;然后将p指针指向的字符串复制到该…

设计模式之责任链模式笔记

设计模式之责任链模式笔记 说明Chain of Responsibility(责任链)目录责任链模式示例类图请假条类抽象处理者类小组长类部门经理类总经理类测试类 说明 记录下学习设计模式-责任链模式的写法。JDK使用版本为1.8版本。 Chain of Responsibility(责任链) 意图:使多个对象都有机…

Vue搜索组件,显示热门、近期搜索(结合element ui)

&#x1f680; 注重版权&#xff0c;转载请注明原作者和原文链接 &#x1f96d; 作者&#xff1a;全栈小袁 &#x1f34e; 原创个人开源博客项目(目前V3.0版本)&#xff1a;https://github.com/yuanprogrammer/xiaoyuanboke &#x1f349; 开源项目觉得还行的话点点star&#x…

【P4】Windows 下搭建 DVWA 及命令注入漏洞详解

文章目录 一、Windows 下搭建 DVWA1.1、DVWA 靶场搭建1.2、六步快速搭建 DVWA1.2.1、下载并安装 PHPstudy&#xff1a;http://public.xp.cn/upgrades/PhpStudy2018.zip1.2.2、将解压后的 DVWA 原代码放置 phpstudy 安装目录的 WWW文件夹1.2.3、进入 DVWA/config 目录&#xff0…

2022前端趋势报告(下)

前端博主&#xff0c;热衷各种前端向的骚操作&#xff0c;经常想到哪就写到哪&#xff0c;如果有感兴趣的技术和前端效果可以留言&#xff5e;博主看到后会去代替大家踩坑的&#xff5e; 主页: oliver尹的主页 格言: 跌倒了爬起来就好&#xff5e; 一、前言 本文内容来自于《St…

springBoot学习——spring+springMVC 集成mybatis 拦截器

目录 引出入门案例&#xff1a;登陆和注册 & 用户信息分页 之 固定的步骤&#xff1a;&#xff08;1&#xff09;建普通项目配置pom.xml文件&#xff08;2&#xff09;写主启动类 application.yml文件【bug】pom.xml文件导了mybatis的包&#xff0c;但是application.yml文…