分布式锁的实现,基于Redis实现分布式锁


分布式锁的实现,基于Redis实现分布式锁

  • 前言
  • 基于mysql实现分布式锁
  • 基于Reids实现分布式锁
    • 基于Redis的具体实现
    • 问题1 :线程误删锁
    • 解决方案一:在删除锁时进行校验
    • 问题2:释放锁的检验和释放不具有原子性
    • 解决方案:基于lua脚本使检验和删除操作具有原子性
    • 其他问题


前言

对于一些业务来说,比如商品或者优惠卷的抢杀,我们常常需要进行一些加锁的操作比如synchronized等来避免商品的超卖问题,但是对于大型的购物平台,都是使用了分布式的系统,将服务部署在多台服务器中,让请求通过轮询算法发送到多个服务器中,用来减少服务器的压力,但是使用了分布式系统也就会导致单机锁的失效,比如synchronized来讲,它是基于JVM的监视器锁,线程想要进入被其修饰的代码块就要获取JVM的监视器锁,但是对于多个服务器,它们有着不同的JVM,监视器锁也就不同,也就导致了多个服务器的线程可以同时进入被其修饰的代码块中,此时我们就需要来实现一个分布式锁,可以同时阻塞多个服务器的线程。


基于mysql实现分布式锁

我们可以使用mysql的数据库表来实现,添加几个字段,例如id、lock_name、thread_id分表表示锁的id、锁的名称、加锁的服务器的线程id,给lock_name加唯一约束,线程需要获取锁就需要尝试在数据库插入锁,如果数据库中存在相同的锁,就不能插入也就导致获取锁失败,成功插入锁就获取到了锁,这是基于mysql的锁机制(插入数据时会添加排他性的行级锁)来保证并发安全。
特点:
可用性:高可用
性能:一般
安全性:当mysql连接断开时,会发生线程安全问题

基于Reids实现分布式锁

我们还可以基于Redis实现分布式锁,Redis的缓存可以被多个服务器所共享,我们可以基于Redis的set nx实现分布式锁,当要存储的值不存在时才能插入成功,否则插入失败,还可以使用参数ex设置线程持有锁的超时时间,防止线程一直持有锁。
特点:
可用性:高可用
性能:比mysql更好
安全性:好,可以给锁设置过期时间

基于Redis的具体实现

public class RedisLock implements ILock {


    private final static String NAME = "lock:";

    private String key;

    private StringRedisTemplate redisTemplate;

    public RedisLock(String key, StringRedisTemplate redisTemplate) {
        this.key = key;
        this.redisTemplate=redisTemplate
}
    @Override
    public boolean getLock(Long timeOut) {
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(NAME + key, “1”, timeOut, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

    @Override
    public void releaseLock() {
        redisTemplate.delete(NAME_key)   
    }
}


如上,我们封装了lock+key的锁的获取和释放方法,这样看起来觉得实现非常简单,实际有很多问题存在


问题1 :线程误删锁

有一个用户的不同服务器的不同线程,分别是线程一和线程二,线程一先获取到了锁,线程一由于业务阻塞超过了锁的过期时间,导致锁被释放,此时线程二获取到了锁,开始执行业务,线程一又继续执行业务执行完毕后删除锁,把线程二持有的锁给释放了,其他线程又获取到了锁,就发生了线程安全问题

解决方案一:在删除锁时进行校验

解决问题一很容易想到,我们可以添加锁时给其value值设置成服务器线程的唯一标识,每次删除锁时都会检验是否是当前线程持有的锁

public class RedisLock implements ILock {


    private final static String NAME = "lock:";

    private String key;

    private StringRedisTemplate redisTemplate;

    public RedisLock(String key, StringRedisTemplate redisTemplate) {
        this.key = key;
        this.redisTemplate=redisTemplate
}
    @Override
    public boolean getLock(Long timeOut) {
        String uid = PREFIX_ID + Thread.currentThread().getId();
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(NAME + key, uid, timeOut, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

    @Override
    public void releaseLock() {
        String id = PREFIX_ID + Thread.currentThread().getId();
        String uid=redisTemplate.opsForValue().get(NAME+key);
        if (id.equals(uid)){
         redisTemplate.delete(NAME+key)      
        }
}

问题2:释放锁的检验和释放不具有原子性

在解决方法一和方法二后,其实还有一个问题,显然在释放锁的操作中,校验和释放不具有原子性,这样就可能会发生这样一个问题,例如线程一和线程二是同一个用户的不同的线程请求,线程一获取到了锁,执行完业务准备释放锁,在释放锁校验成功后,发生了长时间的阻塞(比如FullGC会阻塞所有线程),导致线程的持有锁的时间超过了锁的过期时间,导致锁被释放,被线程二成功获取到了锁,开始执行,此时线程一恢复后释放了锁,此时又有其他线程可以获取到锁,就发生了线程安全的问题。

解决方案:基于lua脚本使检验和删除操作具有原子性

创建lua脚本,lua语言具有原子性

if(redis.call('get',KEYS[1]==ARGV[1])) then
    --如果相同删除
    return redis.call('del',KEYS[1])
end
return 0


public class RedisLock implements ILock {


    private final static String NAME = "lock:";

    private String key;

    private StringRedisTemplate redisTemplate;

    private final static String PREFIX_ID = UUID.randomUUID().toString(true) + "_";

    private final static DefaultRedisScript<Long> UNLOCK_SCRIPT;

    static {
        UNLOCK_SCRIPT=new DefaultRedisScript<Long>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("noLock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    public RedisLock(String key, StringRedisTemplate redisTemplate) {
        this.key = key;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean getLock(Long timeOut) {
        String id = PREFIX_ID + Thread.currentThread().getId();
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(NAME + key, id, timeOut, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

    @Override
    public void releaseLock() {
        String id = PREFIX_ID + Thread.currentThread().getId();
        redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(NAME+key),id);
    }
}

其他问题

基于Redis实现的分布式锁还具有其他问题,比如不可重入、不能多次尝试获取锁、超时释放、主从一致性问题,我们在下一篇文章来介绍这些问题产生的原因以及解决方案

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

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

相关文章

【2024亚太杯亚太赛APMCM C题】数学建模竞赛|宠物行业及相关产业的发展分析与策略|建模过程+完整代码论文全解全析

第一个问题是&#xff1a;请基于附件 1 中的数据以及你的团队收集的额外数据&#xff0c;分析过去五年中国宠物行业按宠物类型的发展情况。并分析中国宠物行业发展的因素&#xff0c;预测未来三年中国宠物行业的发展。 第一个问题&#xff1a;分析中国宠物行业按宠物类型的发展…

外排序中的归并排序

外排序中的归并排序 7.11 外排序中的归并排序相关基础知识原理最终参考程序 7.11 外排序中的归并排序 外部排序&#xff1a;数据元素太多不能同时放在内存中&#xff0c;根据排序过程的要求不能在内外存之间移动数据的排序。&#xff08;下文也称外排序&#xff09; 例如 1 G…

wsl2中kali linux下的docker使用教程(教程总结)

一、前言 上一篇关于kali linux的文章是图形界面的配置&#xff0c;这里作者准备补充两点&#xff0c;一点是在使用VNC时&#xff0c;如果F8不能用的话&#xff0c;可以试试AltF8&#xff1b;然后就是VNC在初始化设置时的三个设置选项依次是密码、再输一次以及设置仅观看密码。…

Linux系统使用valgrind分析C++程序内存资源使用情况

内存占用是我们开发的时候需要重点关注的一个问题&#xff0c;我们可以人工根据代码推理出一个消耗内存较大的函数&#xff0c;也可以推理出大概会消耗多少内存&#xff0c;但是这种方法不仅麻烦&#xff0c;而且得到的只是推理的数据&#xff0c;而不是实际的数据。 我们可以…

【通俗理解】ELBO(证据下界)——机器学习中的“情感纽带”

【通俗理解】ELBO&#xff08;证据下界&#xff09;——机器学习中的“情感纽带” 关键词提炼 #ELBO #证据下界 #变分推断 #机器学习 #潜变量模型 #KL散度 #期望 #对数似然 第一节&#xff1a;ELBO的类比与核心概念【尽可能通俗】 ELBO&#xff0c;即证据下界&#xff0c;在…

【pyspark学习从入门到精通14】MLlib_1

目录 包的概览 加载和转换数据 在前文中&#xff0c;我们学习了如何为建模准备数据。在本文中&#xff0c;我们将实际使用这些知识&#xff0c;使用 PySpark 的 MLlib 包构建一个分类模型。 MLlib 代表机器学习库。尽管 MLlib 现在处于维护模式&#xff0c;即它不再积极开发…

业务架构、数据架构、应用架构和技术架构

TOGAF(The Open Group Architecture Framework)是一个广泛应用的企业架构框架&#xff0c;旨在帮助组织高效地进行架构设计和管理。 TOGAF 的核心就是由我们熟知的四大架构领域组成:业务架构、数据架构、应用架构和技术架构。 企业数字化架构设计中的最常见要素是4A 架构。 4…

阿里巴巴官方「SpringCloudAlibaba全彩学习手册」限时开源!

最近我在知乎上看过的一个热门回答&#xff1a; 初级 Java 开发面临的最大瓶颈在于&#xff0c;脱离不出自身业务带来的局限。日常工作中大部分时间在增删改查、写写接口、改改 bug&#xff0c;久而久之就会发现&#xff0c;自己的技术水平跟刚工作时相比没什么进步。 所以我们…

后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用

目录 后端数据增删改查Springboot 实体&#xff08;entity&#xff09;类引进添加UserMapper接口 创建对用的UserController注意数据库查询不一致新增数据更新删除postman测试 后端数据增删改查 基于之前构建系统&#xff0c;实现用户数据的CRUD。 打开navicat16&#xff0c;…

堆外内存泄露排查经历

优质博文&#xff1a;IT-BLOG-CN 一、问题描述 淘宝后台应用从今年某个时间开始docker oom的量突然变多&#xff0c;确定为堆外内存泄露。 后面继续按照上一篇对外内存分析方法的进行排查(jemalloc、pmap、mallocpmap/mapsNMTjstackgdb)&#xff0c;但都没有定位到问题。至于…

WebSocket详解、WebSocket入门案例

目录 1.1 WebSocket介绍 http协议&#xff1a; webSocket协议&#xff1a; 1.2WebSocket协议&#xff1a; 1.3客户端&#xff08;浏览器&#xff09;实现 1.3.2 WebSocket对象的相关事宜&#xff1a; 1.3.3 WebSOcket方法 1.4 服务端实现 服务端如何接收客户端发送的请…

Vue3 源码解析(三):静态提升

什么是静态提升 Vue3 尚未发布正式版本前&#xff0c;尤大在一次关于 Vue3 的分享中提及了静态提升&#xff0c;当时笔者就对这个亮点产生了好奇&#xff0c;所以在源码阅读时&#xff0c;静态提升也是笔者的一个重点阅读点。 那么什么是静态提升呢&#xff1f;当 Vue 的编译器…

C++优选算法十四 优先级队列(堆)

C 中的优先级队列&#xff08;Priority Queue&#xff09;是一种容器适配器&#xff0c;它提供队列的功能&#xff0c;但元素不是按照插入的顺序被访问&#xff0c;而是根据它们的优先级被访问。默认情况下&#xff0c;优先级队列是一个最大堆&#xff08;Max-Heap&#xff09;…

综合练习--轮播图

本篇博客将教大家实现一个基础的轮播图。 源代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0&qu…

“AI玩手机”原理揭秘:大模型驱动的移动端GUI智能体

作者&#xff5c;郭源 前言 在后LLM时代&#xff0c;随着大语言模型和多模态大模型技术的日益成熟&#xff0c;AI技术的实际应用及其社会价值愈发受到重视。AI智能体&#xff08;AI Agent&#xff09;技术通过集成行为规划、记忆存储、工具调用等机制&#xff0c;为大模型装上…

光伏电站的智慧施工详解

光伏电站的智慧施工是利用先进的技术和管理方法&#xff0c;提高施工效率、质量和安全性&#xff0c;降低成本&#xff0c;实现光伏电站建设的智能化、数字化和绿色化。 下面从鹧鸪云智慧施工软件详细施工管理的步骤说起。 项目总览 包含我负责的项目、我参与的项目、我创建…

django——创建 Django 项目和 APP

2.创建 Django 项目和 APP 命令&#xff1a; 创建Django项目 django-admin startproject name 创建子应用 python manager.py startapp name 2.1 创建工程 在使用Flask框架时&#xff0c;项目工程目录的组织与创建是需要我们自己手动创建完成的。 在django中&#xff0c;…

李春葆《数据结构》-课后习题代码题

一&#xff1a;假设不带权有向图采用邻接矩阵 g 存储&#xff0c;设计实现以下功能的算法&#xff1a; &#xff08;1&#xff09;求出图中每个顶点的入度。 代码&#xff1a; void indegree(MatGraph g){int i,j,n;printf("各个顶点的入度&#xff1a;\n");for(i…

wsl安装

一. wsl简介 1. wsl和wsl2的区别 wsl需要把linux命令翻译为windows命令&#xff0c;性能差一些。 wsl2直接使用linux内核&#xff0c;不需要翻译&#xff0c;性能好&#xff0c;但开销相对大一点&#xff0c;因为需要多运行一个hyper-v虚拟机 (并非完整的虚拟机&#xff0c;是…

Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…