• SpringBoot中使用Redisson分布式锁的应用场景-多线程、服务、节点秒杀/抢票处理


    场景

    若依前后端分离版手把手教你本地搭建环境并运行项目:

    若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-CSDN博客_前后端分离项目本地运行

    在上面搭建起来前后端分离的项目,如果在某些业务场景下比如抢票、秒杀时会有多线程、多定位任务、多服务节点

    对同一个redis中的key进行获取、更改和存储的操作。

    如果每次进行操作时不进行加锁处理,就会导致数据不准确(多卖、少卖)的情况。

    注:

    博客:
    霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
    关注公众号
    霸道的程序猿
    获取编程相关电子书、教程推送与免费下载。

    实现

    1、Redisson

    Redisson - Redis Java client
    with features of an in-memory data grid。

    Redisson - Redis Java 客户端

    具有内存数据网格的特征。

    官方文档地址:

    目录 · redisson/redisson Wiki · GitHub

    gitHub地址:

    GitHub - redisson/redisson: Redisson - Redis Java client with features of In-Memory Data Grid. Over 50 Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Publish / Subscribe, Bloom filter, Spring Cache, Tomcat, Scheduler, JCache API, Hibernate, MyBatis, RPC, local cache ...

    2、参考官方文档快速开始

    引入maven依赖

    1.         <dependency>
    2.             <groupId>org.redissongroupId>
    3.             <artifactId>redissonartifactId>
    4.             <version>3.16.8version>
    5.         dependency>

    新建Redisson配置类,配置redis的连接地址等信息

    1. package com.ruoyi.quartz.config;
    2. import org.redisson.Redisson;
    3. import org.redisson.api.RedissonClient;
    4. import org.redisson.config.Config;
    5. import org.springframework.beans.factory.annotation.Value;
    6. import org.springframework.context.annotation.Bean;
    7. import org.springframework.context.annotation.Configuration;
    8. @Configuration
    9. public class RedissonConfiguration {
    10.     @Value("${spring.redis.host}")
    11.     private String host;
    12.     @Value("${spring.redis.port}")
    13.     private String port;
    14.     @Value("${spring.redis.password}")
    15.     private String password;
    16.     @Bean
    17.     public RedissonClient redissonClient() {
    18.         Config config = new Config();
    19.         String url = "redis://" + host + ":" + port;
    20.         config.useSingleServer().setAddress(url).setPassword(password).setDatabase(0);
    21.         return Redisson.create(config);
    22.     }
    23. }

    这里的redis的相关信息已经在配置文件中进行配置

     

    3、模拟一个多线程的任务,同时对redis中存储的num这个key进行操作,这个key代表票或者商品的总数量。

    如果不加锁,任由所有任务都去获取同一个key,进行减1操作并存储回去,代码实现

    1.         ExecutorService executorService = Executors.newFixedThreadPool(50);
    2.         //存储每个线程的返回结果
    3.         ArrayList> futureArrayList = new ArrayList<>();
    4.         //模拟50个任务发起购票/秒杀商品操作,每个任务买一个
    5.         for (int i = 0; i < 50; i++) {
    6.             Future submit = executorService.submit(() -> {
    7.                 int count = 0;
    8.                 int num = redisCache.getCacheObject("num");
    9.                 num--;
    10.                 redisCache.setCacheObject("num", num);
    11.                 count++;
    12.                 return count;
    13.             });
    14.             futureArrayList.add(submit);
    15.         }
    16.         Integer saleCount = 0;
    17.         for (int i = 0; i < futureArrayList.size(); i++) {
    18.             Future integerFuture = futureArrayList.get(i);
    19.             saleCount = saleCount + integerFuture.get();
    20.         }
    21.         System.out.println("累计卖出:"+saleCount);

    关于多线程的使用方式可以参考下文

    Java中ExecutorService线程池的使用(Runnable和Callable多线程实现):

    Java中ExecutorService线程池的使用(Runnable和Callable多线程实现)_霸道流氓气质的博客-CSDN博客

    这种不加任何锁的实现方式,导致的结果是

    累计计数卖出的是50,实际上redis中减少的数量却不是

     

     

    4、所有为了保持一致性,需要给每次操作同一个key之前添加锁

    获取一把锁,只要锁的名字一样,就是一把锁

    RLock numLock = redissonClient.getLock("numLock");

    每个线程操作redis之前加锁

    1.                 if(numLock.tryLock(1,1,TimeUnit.SECONDS)){
    2.                     int num = redisCache.getCacheObject("num");
    3.                     num--;
    4.                     redisCache.setCacheObject("num", num);
    5.                     count++;
    6.                     //isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定
    7.                     if(numLock.isHeldByCurrentThread()){
    8.                         numLock.unlock();
    9.                     }
    10.                 }

    这里的tryLock的参数为最大等待时间为1秒,上锁1秒后自动解锁。

    然后isHeldByCurrentThread的作用是查询当前线程是否保持此锁定。

    当对redis操作结束之后,如果还保持此锁定,则调用unlock进行解锁。

    完整示例代码

    1. package com.ruoyi.quartz.task;
    2. import com.ruoyi.common.core.redis.RedisCache;
    3. import org.redisson.api.RLock;
    4. import org.redisson.api.RedissonClient;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.stereotype.Component;
    7. import java.util.ArrayList;
    8. import java.util.concurrent.*;
    9. @Component("redissonDemoTask")
    10. public class RedissonDemoTask {
    11.     @Autowired
    12.     private RedisCache redisCache;
    13.     @Autowired
    14.     RedissonClient redissonClient;
    15.     public void PlatformOne() throws ExecutionException, InterruptedException {
    16.         //加锁的实现方式
    17.         ExecutorService executorService = Executors.newFixedThreadPool(50);
    18.         ArrayList> futureArrayList = new ArrayList<>();
    19.         RLock numLock = redissonClient.getLock("numLock");
    20.         for (int i = 0; i < 50; i++) {
    21.             Future submit = executorService.submit(() -> {
    22.                 int count = 0;
    23.                 if(numLock.tryLock(1,1,TimeUnit.SECONDS)){
    24.                     int num = redisCache.getCacheObject("num");
    25.                     num--;
    26.                     redisCache.setCacheObject("num", num);
    27.                     count++;
    28.                     //isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定
    29.                     if(numLock.isHeldByCurrentThread()){
    30.                         numLock.unlock();
    31.                     }
    32.                 }
    33.                 return count;
    34.             });
    35.             futureArrayList.add(submit);
    36.         }
    37.         Integer saleCount = 0;
    38.         for (int i = 0; i < futureArrayList.size(); i++) {
    39.             Future integerFuture = futureArrayList.get(i);
    40.             saleCount = saleCount + integerFuture.get();
    41.         }
    42.         System.out.println("累计卖出:"+saleCount);
    43.     }
    44. }

    加锁效果

     

  • 相关阅读:
    JSP足球场地预约平台系统myeclipse定制开发mysql数据库网页模式java编程jdbc
    【非技术】对自己接到工作任务时的一些提醒
    C++ 01
    Redis学习
    如何在vscode中編輯和預覽markdown文檔
    2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书海口经济学院图书馆
    pandas交叉表与透视表pd.crosstab()和pd.pivot_table()函数详解
    kafka分布式安装部署
    Windows系统配置CUDA编程环境
    智慧的仓库管家——WMS
  • 原文地址:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126272537