分布式锁之-redis

什么是分布式锁?
即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
分布式锁应该具备哪些条件?
1:在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
2:高可用的获取锁与释放锁
3:高性能的获取锁与释放锁
4:具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
5:具备锁失效机制,即自动解锁,防止死锁
分布式锁的实现方式有那些?
1.使用关系型mysql数据库实现分布式锁。
2.使用redis非关系型数据库实现分布式锁。
3.使用zookeeper注册中心来实现分布式锁。
本文将详细介绍重要的分布式锁–给予redis的分布式锁。
A:Redisson 实现的分布式锁使用演示
B:自己实现的 Redis 分布式锁使用演示
数据库脚本

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for product_stock
-- ----------------------------
DROP TABLE IF EXISTS `product_stock`;
CREATE TABLE `product_stock`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `stock` int(11) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '产品库存表\n' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of product_stock
-- ----------------------------
INSERT INTO `product_stock` VALUES (1, 20);

SET FOREIGN_KEY_CHECKS = 1;

yml配置信息

spring:
  application:
    name: lock-redis
  # 数据库链接 要改成自己的数据链接信息
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${MYSQL_URL:127.0.0.1}:3306/lock-test?autoReconnect=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true
    username: root
    password: ${MYSQL_PASSWORD:abc123}
    hikari:
      max-lifetime: 500000
  # redis链接,要改成自己的链接配置
  redis:
    host: ${REDIS_URL:127.0.0.1}
    port: 6379
    password: ${REDIS_PASSWORD:abc123}
    database: 11

自己实现的分布式锁的方式。

  /**
     * 模拟减库存操作 - 自己实现 redis 锁接口
     *
     * @return str
     */
    @GetMapping("/reduceStockByMyLock/{id}")
    public String reduceStockByMyLock(@PathVariable("id") Integer id) {
        return productStockService.reduceStockByMyLock(id);
    }
 @SneakyThrows
    @Override
    public String reduceStockByMyLock(Integer id) {
        // requestId 确保每一个请求生成的都不一样,这里使用 uuid,也可以使用其他分布式唯一 id 方案
        String requestId = UUID.randomUUID().toString().replace("-", "");
        int expireTime = 10;
        bulkRedisLock.lock(requestId, expireTime);
        // 开启续命线程,
        Thread watchDog = bulkRedisLock.watchDog(expireTime, requestId);
        watchDog.setDaemon(true);
        watchDog.start();
        try {
            ProductStock stock = productStockMapper.selectById(id);
            if (stock != null && stock.getStock() > 0) {
                productStockMapper.reduceStock(id);
            } else {
                throw new RuntimeException("库存不足!");
            }
        } finally {
            watchDog.interrupt();
            bulkRedisLock.unlock(requestId);
        }
        return "ok";
    }

自己实现的配置锁BulkRedisLock

@Slf4j
@Component
@SuppressWarnings("all")
public class BulkRedisLock {
    private static final String LOCK_PREFIX = "redisLock";
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 尝试获取锁
     *
     * @param requestId  请求id
     * @param expireTime 过期时间  单位毫秒
     * @return true false
     */
    public boolean lock(String requestId, int expireTime) {
        // 也可以使用 lua 脚本 "return redis.call('set',KEYS[1], ARGV[1],'NX','PX',ARGV[2])"
        // 使用redis保证原子操作(判断是否存在,添加key,设置过期时间)
        while (true) {
            if (Boolean.TRUE.equals(stringRedisTemplate.boundValueOps(LOCK_PREFIX).
                    setIfAbsent(requestId, expireTime, TimeUnit.SECONDS))) {
                return true;
            }
        }
    }

    /**
     * 将锁释放掉
     * <p>
     * 为何解锁需要校验 requestId 因为不是自己的锁不能释放
     * 客户端A加锁,一段时间之后客户端A解锁,在执行 lock 之前,锁突然过期了。
     * 此时客户端B尝试加锁成功,然后客户端A再执行 unlock 方法,则将客户端B的锁给解除了。
     *
     * @param requestId 请求id
     * @return true false
     */
    public boolean unlock(String requestId) {
        // 这里使用Lua脚本保证原子性操作
        String script = "if  redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long res = stringRedisTemplate.execute(redisScript, Collections.singletonList(LOCK_PREFIX), requestId);
        return new Long(1).equals(res);
    }

    /**
     * 创建续命子线程
     *
     * @param time      操作预期耗时
     * @param requestId 唯一标识
     * @return 续命线程 Thread
     */
    public Thread watchDog(int time, String requestId) {
        return new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(time * 2 / 3);
                    //重置时间
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                            "return redis.call('expire', KEYS[1],ARGV[2]) " +
                            "else return '0' end";
                    List<Object> args = new ArrayList();
                    args.add(requestId);
                    args.add(time);
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                    stringRedisTemplate.execute(redisScript, Collections.singletonList(LOCK_PREFIX), args);
                } catch (Exception e) {
                    // sleep interrupted 是因为 sleep
                    // log.info("watchDog异常:{}", e.getMessage());
                    return;
                }
            }
        });
    }
}

mapper的xml文件

 <update id="reduceStock">
        update product_stock
        set stock = stock - 1
        where id = #{id}
    </update>

控制层入库contoller

 /**
     * 模拟减库存操作 - 自己实现 redis 锁接口
     *
     * @return str
     */
    @GetMapping("/reduceStockByMyLock/{id}")
    public String reduceStockByMyLock(@PathVariable("id") Integer id) {
        return productStockService.reduceStockByMyLock(id);
    }

使用edisson 实现的分布式锁使用
控制controller

 /**
     * 模拟减库存操作 - redisson 实现
     *
     * @return str
     */
    @GetMapping("/reduceStock/{id}")
    public String reduceStockByRedisson(@PathVariable("id") Integer id) {
        return productStockService.reduceStock(id);
    }

业务实现

@Override
    public String reduceStock(Integer id) {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        try {
            ProductStock stock = productStockMapper.selectById(id);
            if (stock != null && stock.getStock() > 0) {
                productStockMapper.reduceStock(id);
            } else {
                throw new RuntimeException("库存不足!");
            }
        } finally {
            lock.unlock();
        }
        return "ok";
    }

以上的是分布式锁之-redis 若需完整代码 可识别二维码后 给您发代码。
若友友们有更好的分布式锁的实现方式 请在评论区留下你可贵的分享 谢谢!!!!
在这里插入图片描述

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

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

相关文章

JVM垃圾回收器G1大总结-详解

一、介绍: 1.停顿时间模型?? 作为CMS收集器的替代者和继承人&#xff0c;G1是基于“停顿时间模型”诞生的的垃圾收集器&#xff0c;停顿时间模型的意思是能够支持指定在一个长度为M毫秒的时间片段内&#xff0c;消耗在垃圾收集上的时间大概率不超过N毫秒这样的目标. 2.G1摒…

进程与线程(进程)

进程&#xff1a; 概念&#xff1a;进程是进程实体的运行过程&#xff0c;是系统进行资源分配和调度的一个独立单位 PID:当进程被创建时&#xff0c;操作系统会为该进程分配一个唯一的、不重复的“身份证号” 组成&#xff1a; PCB&#xff08;进程控制块&#xff09;&#…

使用AIGC生成软件类图表

文章目录 如何使用 AI 生成软件类图表什么是 MermaidMermaid 的图片如何保存&#xff1f;mermaid.liveDraw.io Mermaid可以画什么图&#xff1f;流程图时序图 / 序列图类图状态图甘特图实体关系图 / ER图 如何使用 AI 生成软件类图表 ChatGPT 大语言模型不能直接生成各类图表。…

W801学习笔记二十:宋词学习应用

前三章完成了唐诗的应用&#xff0c;本章将实现宋词的学习应用。 宋词与唐诗的区别不大&#xff0c;马上开始。 1、我们需要参考前面唐诗的方式&#xff0c;把宋词文本下载下来&#xff0c;并进行格式整理。 W801学习笔记十七&#xff1a;古诗学习应用——上 2、在菜单中添加…

电脑上的视频在电视上播放

视频右键->播放到设备->客厅电视 海信电视测试成功

BI不等同数据分析,别搞错了!

✅作者简介&#xff1a;《数据运营&#xff1a;数据分析模型撬动新零售实战》作者、《数据实践之美》作者、数据科技公司创始人、多次参加国家级大数据行业标准研讨及制定、高端企培合作讲师。 &#x1f338;公众号&#xff1a;风姑娘的数字视角&#xff0c;免费分享数据应用相…

【Transformer系列(1)】self-attention自注意力

一、self-attention流程 自注意力机制和注意力机制的区别在于&#xff0c;注意力机制中的Q&#xff08;查询向量&#xff09;&#xff0c;K&#xff08;键向量&#xff09;&#xff0c;V&#xff08;值向量&#xff09;是同源的&#xff0c;而一般的注意力机制&#xff0c;Q和…

谈谈Tcpserver开启多线程并发处理遇到的问题!

最近在学习最基础的socket网络编程&#xff0c;在Tcpserver开启多线程并发处理时遇到了一些问题&#xff01; 说明 在linux以及Windows的共享文件夹进行编写的&#xff0c;所以代码中有的部分使用 #ifdef WIN64 ... #else ... #endif 进入正题&#xff01;&#xff01;&…

50个前端实战项目之04:隐藏的搜索小组件

大家好&#xff0c;我是宝哥。 今天讲50个前端实战项目之04&#xff1a;隐藏的搜索小组件。 源码下载地址 https://github.com/bradtraversy/50projects50days/tree/master/hidden-search 前端实战项目系列正在更新&#xff1a;04/50 01&#xff1a;可展开卡片02&#xff1a;进…

C语言中的goto语句

goto label; C 语言中的 goto 语句允许把控制无条件转移到同一函数内的被标记的语句。 #include <stdio.h> int main(){goto first;printf("我是你好\n");first:printf("nihao\n");second:printf("This is 2\n");return 0; } 使用goto会…

西门子1200脉冲轴【PTO】

原理图&#xff1a; PTO (Pulse Train Output) 脉冲串输出 PLC的电压是DC24v的&#xff0c;所以驱动器的信号电压是否支持&#xff0c;以免烧坏。 台达A2&#xff1a; //L型机&#xff1a; //&#xff08;方向&#xff1a;3524v&#xff0c;393.3v&#xff0c;37-GN…

力扣 647. 回文子串

题目来源&#xff1a;https://leetcode.cn/problems/palindromic-substrings/description/ C题解1&#xff1a;暴力解法。不断地移动窗口&#xff0c;判断是不是回文串。 class Solution { public:int countSubstrings(string s) {int len s.size();int res 0;for(int i 0;…

【JAVA项目】基于个人需求和地域特色的【外卖推荐系统】

技术简介&#xff1a;采用B/S架构、ssm 框架、Java技术、MySQL等技术实现。 系统简介&#xff1a;统权限按管理员&#xff0c;商家和用户这三类涉及用户划分。(a) 管理员&#xff1b;管理员使用本系统涉到的功能主要有&#xff1a;首页&#xff0c;个人中心&#xff0c;用户管理…

GDPU unity游戏开发 碰撞器与触发器

砰砰叫&#xff0c;谁动了她的奶酪让你的小鹿乱撞了。基于此&#xff0c;亦即碰撞与触发的过程。 碰撞器与触发器的区别 通俗点讲&#xff0c;碰撞器检测碰撞&#xff0c;触发器检测触发&#xff0c;讲了跟没讲似的。碰撞器是用来检测碰撞事件的&#xff0c;在unity中&#xff…

JavaWeb请求响应概述

目录 一、请求响应流程-简述 二、深入探究 三、DispatcherServlet 四、请求响应流程-详细分析 一、请求响应流程-简述 web应用部署在tomcat服务器中&#xff0c;前端与后端通过http协议进行数据的请求和响应。前端通过http协议向后端发送数据请求&#xff0c;就可以访问到部…

Golang日志管理:使用log/slog实现高级功能和性能优化

Golang日志管理&#xff1a;使用log/slog实现高级功能和性能优化 简介基础使用初始化和配置日志级别 高级技巧自定义日志格式器条件日志处理 实战案例场景一&#xff1a;API请求日志记录场景二&#xff1a;错误跟踪和用户通知 性能优化优化日志记录的性能异步日志处理选择合适的…

算法设计与分析——期末1h

目录 第一章 算法的定义 算法的三要素 算法的基本性质 算法的时间复杂度数量级&#xff1a; 第二章 兔子繁殖问题&#xff08;递推法&#xff09; 猴子吃桃问题&#xff08;递推法&#xff09; 穿越沙漠问题&#xff08;递推法&#xff08;倒推&#xff09;&#xff09; 百钱百…

C++进阶----多态

1.多态的概念 1.1 概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同类型的对象去完成时会 产生出不同的状态。 举个例子&#xff1a;比如有一个基类Animal&#xff0c;它有两个子类Dog和Cat。每个…

SpringCloud知识点梳理

1. Spring Cloud 综述 1.1 Spring Cloud 是什么 [百度百科]Spring Cloud是⼀系列框架的有序集合。它利⽤Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中⼼、消息总线、负载均衡、断路器、数据监控等,都可以⽤ Spring Boot的开发⻛格…

eNSP-动态路由(ospf协议)

一、拓扑结构搭建 二、主机配置 pc1 pc2 三、路由器配置 1.AR2配置 <Huawei>sys #进入系统视图 [Huawei]int g0/0/0 #进入接口 [Huawei-GigabitEthernet0/0/0]ip address 192.168.0.2 24 #设置ip地址 [Huawei-GigabitEthernet0/0/0]q #返回上一级 [Huawei]int g0/0/1 …