springBoot中使用redis实现分布式锁实例demo

news/2024/11/25 13:30:04/

首先

RedisLockUtils工具类
package com.example.demo.utils;import org.junit.platform.commons.util.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;@Component
public class RedisLockUtils {@Resourceprivate RedisTemplate redisTemplate;private static Map<String, LockInfo> lockInfoMap = new ConcurrentHashMap<>();private static final Long SUCCESS = 1L;public static class LockInfo {private String key;private String value;private int expireTime;//更新时间private long renewalTime;//更新间隔private long renewalInterval;public static LockInfo getLockInfo(String key, String value, int expireTime) {LockInfo lockInfo = new LockInfo();lockInfo.setKey(key);lockInfo.setValue(value);lockInfo.setExpireTime(expireTime);lockInfo.setRenewalTime(System.currentTimeMillis());lockInfo.setRenewalInterval(expireTime * 2000 / 3);return lockInfo;}public String getKey() {return key;}public void setKey(String key) {this.key = key;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public int getExpireTime() {return expireTime;}public void setExpireTime(int expireTime) {this.expireTime = expireTime;}public long getRenewalTime() {return renewalTime;}public void setRenewalTime(long renewalTime) {this.renewalTime = renewalTime;}public long getRenewalInterval() {return renewalInterval;}public void setRenewalInterval(long renewalInterval) {this.renewalInterval = renewalInterval;}}/*** 使用lua脚本更新redis锁的过期时间* @param lockKey* @param value* @return 成功返回true, 失败返回false*/public boolean renewal(String lockKey, String value, int expireTime) {String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();redisScript.setResultType(Boolean.class);redisScript.setScriptText(luaScript);List<String> keys = new ArrayList<>();keys.add(lockKey);Object result = redisTemplate.execute(redisScript, keys, value, expireTime);System.out.println("更新redis锁的过期时间:{}"+result);return (boolean) result;}/*** @param lockKey    锁* @param value      身份标识(保证锁不会被其他人释放)* @param expireTime 锁的过期时间(单位:秒)* @return 成功返回true, 失败返回false*/public boolean lock(String lockKey, String value, long expireTime) {return redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);}/*** redisTemplate解锁* @param key* @param value* @return 成功返回true, 失败返回false*/public boolean unlock2(String key, String value) {Object currentValue = redisTemplate.opsForValue().get(key);boolean result = false;if (StringUtils.isNotBlank(String.valueOf(currentValue)) && currentValue.equals(value)) {result = redisTemplate.opsForValue().getOperations().delete(key);}return result;}/*** 定时去检查redis锁的过期时间*/@Scheduled(fixedRate = 5000L)@Async("redisExecutor")public void renewal() {long now = System.currentTimeMillis();for (Map.Entry<String, LockInfo> lockInfoEntry : lockInfoMap.entrySet()) {LockInfo lockInfo = lockInfoEntry.getValue();if (lockInfo.getRenewalTime() + lockInfo.getRenewalInterval() < now) {renewal(lockInfo.getKey(), lockInfo.getValue(), lockInfo.getExpireTime());lockInfo.setRenewalTime(now);}}}/*** 分布式锁设置单独线程池* @return*/@Bean("redisExecutor")public Executor redisExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(1);executor.setMaxPoolSize(1);executor.setQueueCapacity(1);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("redis-renewal-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());return executor;}
}

完整的

Controller
package com.example.demo.controller;import com.example.demo.utils.RedisLockUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {private String goodNumKey = "num";@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate RedisLockUtils redisLock;/*** 设置商品库存* @param num 库存数量* @return*/@GetMapping("/set-num/{num}")public int setNum(@PathVariable int num) {redisTemplate.opsForValue().set(goodNumKey, num);return num;}/*** 获取商品库存* @return*/@GetMapping("/get-num")public int getNum() {Object objNum = redisTemplate.opsForValue().get(goodNumKey);int num = Integer.parseInt((String) objNum);return num;}/*** 用户带着id来秒杀商品* @param id 用户id* @return*/@GetMapping("/user/{id}")public String getUser(@PathVariable String id) {String key = "user:" + id;String productId = "product001";String requestId = productId + Thread.currentThread().getId();boolean locked = redisLock.lock(productId, requestId, 10);//如果存在直接返回结果if (redisTemplate.hasKey(key)) {return (String) redisTemplate.opsForValue().get(key);}//如果有锁重试if (!locked) {return "error";}try {//查询库存Object objNum = redisTemplate.opsForValue().get(goodNumKey);int num = Integer.parseInt((String) objNum);if (num > 0) {num--;//保存库存redisTemplate.opsForValue().set(goodNumKey, num);//添加抢购成功的信息redisTemplate.opsForValue().set(key, 1);System.out.println(key + "成功");return (String) redisTemplate.opsForValue().get(key);} else {//添加抢购失败的信息System.out.println(key + "失败");// redisTemplate.opsForValue().set(key, 0);return "0";}} finally {redisLock.unlock2(productId, requestId);}}// 其他接口方法...
}

完整pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.11</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>spring-boot-redis</artifactId><version>0.0.1-SNAPSHOT</version><name>spring-boot-redis</name><description>spring-boot-redis</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-commons</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

以上是完整服务端   git地址   spting-boot-redis: springBoot中使用redis实现分布式锁实例demo

下面来写一个程序,多线程异步去模拟大量同时的商品抢购请求  看一下抢购成功的用户数量和库存情况

package maomi.com;import maomi.com.tools.RedisDistributedLock;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;import java.io.IOException;
import java.util.UUID;public class ConcurrentHttpRequestTest implements Runnable {/*** 线程id*/public int i;/*** 线程名称*/public String name;public ConcurrentHttpRequestTest(int i) {this.i = i;this.name = String.format("线程[%s]", i);}public void run() {// 执行线程操作dosom();}public static void main(String[] args) {//开启500个线程去抢购这个商品for (int i = 1; i < 500; i++) {new Thread(new ConcurrentHttpRequestTest(i)).start();}}/*** 一个线程模拟30次抢购 带着随机用户id* @return*/public boolean dosom() {for (int j = 0; j <30 ; j++) {CloseableHttpClient httpClient = HttpClients.createDefault();String url = "http://127.0.0.1:8080/user/" + UUID.randomUUID();System.out.println(name + ":" + url);HttpGet httpGet = new HttpGet(url);try {CloseableHttpResponse response = httpClient.execute(httpGet);System.out.println(name+"-Request: " + response.getEntity().toString());} catch (IOException e) {e.printStackTrace();}}return false;}
}

下面我们来测试

1.首先设置200个库存 

 

 2.然后我们来模拟抢购

我们来看打印 一共服务端收到了2232个请求

 成功数量只有200个

 看下redis成功写入用户和库存  成功写入用户id为200  库存为0

下面我们去掉分布式锁 

来同样设置200库存模拟一下 发现库存为0 但是抢购成功的有6000多个用户

大家猜一下为什么会这样


http://www.ppmy.cn/news/83508.html

相关文章

Revit建模|怎么创建轴网标高?

大家好&#xff0c;这里是建模助手&#xff0c;今天给大家讲一讲怎么创建轴网标高。 标高用来定义楼层层高以及生成平面视图&#xff0c;轴网用于为构件定位&#xff0c;在Revit中轴网确定了一个不可见的工作平面&#xff0c;轴网编号以及标高符号样式均可定制修改。目前&…

MySQL8.0高级篇(上)-架构与索引

文章目录 一、MySQL环境安装与介绍1、MySQL安装1.1 安装前说明1.2 MySQL的Linux版安装1.3 MySQL登录1.4 字符集的相关操作1.5 字符集与比较规则(了解)1.6 请求到响应过程中字符集的变化1.7 SQL大小写规范1.8 sql_mode的合理设置 2、MySQL的数据目录2.1 MySQL8的主要目录结构2.2…

Azure Services -5.25-summary

文章目录 1. Resources2.Data processing process3.Virtual network and public ip address4. Kubernetes services5. Yaml file first , we enter the homepage of microsoft azure, and we can see a lot of servicse provided by the microsoft azure , 1. Resources accou…

嘉兴桐乡技能培训-平面设计入门看过来

想要当一个设计师&#xff0c;首先要确定自己是否有学习的耐心和勇气。所有学科的新人成长都需要一个过程&#xff0c;这自然要从模仿开始。要多看优秀的设计作品&#xff0c;学习人家作品中优秀的地方&#xff0c;并且多多练习&#xff0c;不断地进步&#xff0c;不断地提高自…

Mac 原神电脑版下载安装使用教程,MacBook 上也可以玩原神了

最近发现了一个很棒的工具&#xff0c;他可以让你的 Mac 苹果电脑运行原神&#xff0c;而且画质和流畅度都是在线的&#xff0c;今天分享给大家 软件名字叫 playCover &#xff0c;根据作者的介绍这款软件最初就是国外的一位博主想在 Mac 上玩原神特意开发的一款软件&#xff…

uniapp实现微信小程序的电子签名

签名页的效果如图下所示&#xff1a; 封装的组件代码如下所示&#xff1a; <template><view><view class"wrapper"><view class"handBtn"><button click"handleReset" class"delBtn">清除</button&…

版图设计工具解析-virtuoso的display.drf文件解析

1. display.drf文件解析 virtuoso的版图颜色定义分析 下图为virtuoso的版图颜色&#xff0c;包括填充&#xff0c;轮廓&#xff0c;彩点&#xff0c;线形 本文以smic18mmrf的display.drf文件进行解析 smic18的PDK包下存在display.drf文件 打开文件display.drf文件后看到如下…

Python学习之pygame模块介绍并制作代码雨

前言 对Python游戏有所了解的朋友都知道&#xff0c;在2D的游戏制作中&#xff0c;经常会用到一个模块pygame&#xff0c;他能帮助我们实现很多方便使用的功能&#xff0c;例如绘制窗口&#xff0c;反馈键盘鼠标信息&#xff0c;播放音频文件&#xff0c;渲染图片文字等等功能…