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依赖

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.16.8</version>
        </dependency>

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

package com.ruoyi.quartz.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfiguration {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        String url = "redis://" + host + ":" + port;
        config.useSingleServer().setAddress(url).setPassword(password).setDatabase(0);
        return Redisson.create(config);
    }

}

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

 

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

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

        ExecutorService executorService = Executors.newFixedThreadPool(50);
        //存储每个线程的返回结果
        ArrayList<Future<Integer>> futureArrayList = new ArrayList<>();
        //模拟50个任务发起购票/秒杀商品操作,每个任务买一个
        for (int i = 0; i < 50; i++) {
            Future<Integer> submit = executorService.submit(() -> {
                int count = 0;
                int num = redisCache.getCacheObject("num");
                num--;
                redisCache.setCacheObject("num", num);
                count++;
                return count;
            });
            futureArrayList.add(submit);
        }

        Integer saleCount = 0;

        for (int i = 0; i < futureArrayList.size(); i++) {
            Future<Integer> integerFuture = futureArrayList.get(i);
            saleCount = saleCount + integerFuture.get();
        }
        System.out.println("累计卖出:"+saleCount);

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

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

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

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

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

 

 

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

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

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

每个线程操作redis之前加锁

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

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

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

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

完整示例代码

package com.ruoyi.quartz.task;

import com.ruoyi.common.core.redis.RedisCache;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.concurrent.*;

@Component("redissonDemoTask")
public class RedissonDemoTask {



    @Autowired
    private RedisCache redisCache;

    @Autowired
    RedissonClient redissonClient;

    public void PlatformOne() throws ExecutionException, InterruptedException {

        //加锁的实现方式
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        ArrayList<Future<Integer>> futureArrayList = new ArrayList<>();
        RLock numLock = redissonClient.getLock("numLock");
        for (int i = 0; i < 50; i++) {
            Future<Integer> submit = executorService.submit(() -> {
                int count = 0;
                if(numLock.tryLock(1,1,TimeUnit.SECONDS)){
                    int num = redisCache.getCacheObject("num");
                    num--;
                    redisCache.setCacheObject("num", num);
                    count++;
                    //isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定
                    if(numLock.isHeldByCurrentThread()){
                        numLock.unlock();
                    }
                }
                return count;
            });
            futureArrayList.add(submit);
        }

        Integer saleCount = 0;

        for (int i = 0; i < futureArrayList.size(); i++) {
            Future<Integer> integerFuture = futureArrayList.get(i);
            saleCount = saleCount + integerFuture.get();
        }
        System.out.println("累计卖出:"+saleCount);
    }
}

加锁效果

 


版权声明:本文为BADAO_LIUMANG_QIZHI原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。