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


场景

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

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662

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

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

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

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

1、Redisson

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

Redisson – Redis Java 客户端

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

官方文档地址:

https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95

gitHub地址:

https://github.com/redisson/redisson

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的相关信息已经在配置文件中进行配置

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

 

 

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多线程实现):

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126242904

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

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

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

 

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

 

 

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

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);
    }
}

加锁效果

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

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

 

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/279807.html

(0)
上一篇 2022年8月11日
下一篇 2022年8月11日

相关推荐

发表回复

登录后才能评论