分布式锁之-redis

news2024/11/17 5:39:16

什么是分布式锁?
即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
分布式锁应该具备哪些条件?
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 若需完整代码 可识别二维码后 给您发代码。
若友友们有更好的分布式锁的实现方式 请在评论区留下你可贵的分享 谢谢!!!!
在这里插入图片描述

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

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

相关文章

NumPy库与PyTorch库的异同点

目录 1.单位的创建和操作 1.创建 2.形状变换 2.数学和统计操作 1.矩阵乘法 2.广播 3.统计计算 3.GPU支持 4.在深度学习中的作用 5.应用范围 NumPy库为数组服务&#xff0c;PyTorch库为张量服务&#xff0c;这是最本质的区别。 1.单位的创建和操作 1.创建 NumPy:使…

SQL注入-基础知识

目录 前言 一&#xff0c;SQL注入是什么 二&#xff0c;SQL注入产生的条件 三&#xff0c;学习环境介绍 四、SQL注入原理 五&#xff0c;SQL中常用的函数 六&#xff0c;关于Mysql数据库 前言 在网络安全领域中&#xff0c;sql注入是一个无法被忽视的关键点&#xff0c…

matlab例题大全

1.第一章 1.1 注&#xff1a;plot函数为画图函数。例plot&#xff08;x1,y1,:,x2,y2,*&#xff09;; 1.2 注&#xff1a;root为求根函数。p为方程变量前面系数矩阵。 1.3 注&#xff1a; 2*x3y-1*z 2; 8*x2*y3*z 4; 45*x3*y9*z 23 求&#xff1a;x,y,z的值 注&#xf…

eNSP-浮动静态路由配置

ip route-static 192.168.1.0 24 192.168.3.2 preference 60 #设置路由 目标网络地址 和 下一跳地址 preference值越大 优先级越低 一、搭建拓扑结构 二、主机配置 pc1 pc2 三、配置路由器 1.AR1路由器配置 <Huawei>sys #进入系统视图 [Huawei]int g0/0/0 #进入接…

uniapp 微信开发工具上访问正常,真机调试一直跨域报错

微信小程序真机调试时&#xff0c;出现跨域问题&#xff0c;需要同时在后端设置多种允许跨域的设置&#xff1a; // 指定允许其他域名访问 header(Access-Control-Allow-Origin:*); // 响应类型 header(Access-Control-Allow-Methods:GET,POST,OPTION); // 响应头设置 header(…

使用Python爬取淘宝商品并做数据分析

使用Python爬取淘宝商品并做数据分析&#xff0c;可以按照以下步骤进行操作&#xff1a; 确定需求&#xff1a;确定要爬取的淘宝商品的种类、数量、关键词等信息。 编写爬虫程序&#xff1a;使用Python编写爬虫程序&#xff0c;通过模拟浏览器请求&#xff0c;获取淘宝商品的页…

redis 缓存一致性,缓存穿透,缓存雪崩,缓存击穿

1.缓存一致性&#xff1a; 缓存一致性就是通过各种方法保证缓存与数据库信息一种&#xff0c;其中最多的办法就是想尽一切办法对过期key进行清除&#xff0c;以保证redis和数据库信息一只&#xff0c;其中就包括了这篇文章中提到的内存淘汰策略&#xff0c;过期key的清除等等&…

Bookends for Mac:文献管理工具

Bookends for Mac&#xff0c;一款专为学术、研究和写作领域设计的文献管理工具&#xff0c;以其强大而高效的功能深受用户喜爱。这款软件支持多种文件格式&#xff0c;如PDF、DOC、RTF等&#xff0c;能够自动提取文献的关键信息&#xff0c;如作者、标题、出版社等&#xff0c…

深入理解网络原理3----TCP核心特性介绍(上)【面试高频考点】

文章目录 前言TCP协议段格式一、确认应答【保证可靠性传输的机制】二、超时重传【保证可靠性传输的机制】三、连接管理机制【保证可靠性传输的机制】3.1建立连接&#xff08;TCP三次握手&#xff09;---经典面试题3.2断开连接&#xff08;四次挥手&#xff09;3.3TCP状态转换 四…

Delta lake with Java--在spark集群上运行程序

昨天写了第一篇入门&#xff0c;今天看见有人收藏&#xff0c;继续努力学习下去。今天要实现的内容是如何将昨天的HelloDetlaLake 在spark集群上运行&#xff0c;。具体步骤如下 1、安装spark,我使用的是 spark-3.5.1-bin-hadoop3-scala2.13&#xff0c;去官网下载&#xff0c…

微服务----nacos配置及简单使用

目录 什么是nacos 项目在nacos上进行注册 注入nacos依赖 配置application.yml文件 nacos写入配置文件 首先&#xff0c;还是需要导入依赖 然后在nacos中编写配置文件 prod是我自定义的一个命名空间&#xff0c;在这里面进行配置文件编写~ 启动类上加上注解 编写Patt…

Java与Go:并发

在此之前&#xff0c;我们先要明白什么是并发&#xff1f;为什么要并发编程&#xff1f; 在计算机中&#xff0c;同一时刻&#xff0c;只能有一条指令&#xff0c;在一个CPU上执行 后面的指令必须等到前面指令执行完才能执行&#xff0c;就是串行。在早年CPU核心数还少的时候倒…

求矩阵对角线元素之和(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int i 0;int j 0;int sum 0;int a[3][3] { 0 };//获取数组a的值&#xff1b;printf(&qu…

pandas学习笔记12

缺失数据处理 其实在很多时候&#xff0c;人们往往不愿意过多透露自己的信息。假如您正在对用户的产品体验做调查&#xff0c;在这个过程中您会发现&#xff0c;一些用户很乐意分享自己使用产品的体验&#xff0c;但他是不愿意透露自己的姓名和联系方式&#xff1b; 还有一些用…

【论文阅读】Learning Texture Transformer Network for Image Super-Resolution

Learning Texture Transformer Network for Image Super-Resolution 论文地址Abstract1. 简介2.相关工作2.1单图像超分辨率2.2 Reference-based Image Super-Resolution 3. 方法3.1. Texture TransformerLearnable Texture Extractor 可学习的纹理提取器。Relevance Embedding.…

Linux的socket详解

一、本机直接的进程通信方式 管道&#xff08;Pipes&#xff09;&#xff1a; 匿名管道&#xff08;Anonymous pipes&#xff09;&#xff1a;通常用于父子进程间的通信&#xff0c;它是单向的。命名管道&#xff08;Named pipes&#xff0c;也称FIFO&#xff09;&#xff1a;允…

【Linux】进程控制 之 进程创建 进程终止 进程等待 进程替换

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

H.265 与 H.264 的主要区别

H.265 与 H.264 的主要区别 H.265 与 H.264 的主要区别各模块技术差异汇总宏块划分帧内预测模式帧间预测模式去块滤波ALF自适应环路滤波采样点自适应偏移&#xff08;Sample Adaptive Offset&#xff09;滤波并行化设计TileEntropy sliceDependent SliceWPP&#xff08;Wavefro…

docker部署nginx并实现https

文章目录 docker部署nginx并实现https1、服务器环境2、安装docker3、准备证书4、准备nginx配置文件和dockerfile文件5、创建nginx镜像与容器6、验证访问 docker部署nginx并实现https 1、服务器环境 [rootliuyanfen12 ~]#systemctl stop firewalld [rootliuyanfen12 ~]#setenf…

HTML/CSS1

1.前置说明 请点这里 2.img元素 格式&#xff1a; <img src"图片地址" alt"占位文字" width"图片宽度" height"图片高度">其中alt是当图片加载失败时显示的文字 而且不同内核的浏览器显示出来的占位文字的效果也是不尽相同的…