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

news/2024/4/29 18:21:27/文章来源:https://blog.csdn.net/qq_28130847/article/details/130728494

首先

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.luyixian.cn/news_show_302287.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SAP入门到放弃系列之需求管理的基本要素

需求管理目标&#xff1a; 一般而言&#xff0c;生产计划&#xff08;PP&#xff09;的总体目标&#xff0c;特别是需求管理的总体目标是通过减少以下内容来更好地为客户服务&#xff1a; 补货提前期存货成本 需求管理的要素&#xff1a; 需求管理工作的主要要素广义上可分…

❤ cannot read properties of null(reading appendChild)解决办法

❤ 操作元素报&#xff1a;cannot read properties of null(reading appendChild)解决办法 1、场景&#xff1a; 写的一个js渲染&#xff0c;但是出了个小问题&#xff0c;cannot read properties of null(reading appendChild)报错。 <div id"divps" class&qu…

机器学习项目实战-能源利用率 Part-1(数据清洗)

1. 项目背景 2009年的《当地法案84号》&#xff0c;或纽约市基准法案&#xff0c;要求对能源和用水量进行年度基准测试和披露信息。被覆盖的财产包括单个建筑物的税收地块&#xff0c;其总建筑面积大于50,000平方英尺&#xff08;平方英尺&#xff09;&#xff0c;以及具有超过…

OpenAI新作Shap-e算法使用教程

一、知识点 Shap-e是基于nerf的开源生成3d模型方案。它是由如今热火朝天的Open AI公司&#xff08;chatgpt&#xff0c;Dell-E2&#xff09;开发、开源的。Shap-e生成的速度非常快&#xff0c;输入关键词即可生成简单模型&#xff08;限于简单单体模型&#xff09;。 二、环境…

别去外包,干了三年,废了....

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

jetson nx 用windows远程连接

VNC Viewer远程连接 一、jetson nx配置vnc 1、安装客户端 sudo apt-get install xrdp vnc4server xbase-clients2、进入nano/nx桌面&#xff0c;打开“Setting–>Desktop sharing”&#xff0c;没反应&#xff0c;据说是bug&#xff0c;我试过nano和nx都一样。首先输入下…

springboot+jsp法律知识分享网站普法平台

法律知识分享平台&#xff0c;主要的模块包括查看主页、个人中心、用户管理、律师事务所管理、律师管理、法律资讯管理、案例分析管理、案例分享管理、法规信息管理、法规分享管理、留言信息管理、留言回复管理、论坛管理、系统管理等功能。系统中管理员主要是为了安全有效地存…

Docker笔记7 | 如何使用 Docker Compose 搭建一个拥有权限 认证、TLS 的私有仓库?

7 | 如何使用 Docker Compose 搭建一个拥有权限 认证、TLS 的私有仓库&#xff1f; 1 准备工作2 准备站点证书2.1 创建CA私钥2.2 创建CA根证书请求文件2.3 配置CA根证书2.4 签发根证书2.5 生成站点SSL私钥2.6 私钥生成证书请求文件2.7 配置证书2.8 签署站点SSL证书 3 配置私有仓…

低代码行业的发展真的可以让复杂的代码编写一去不复返?

前言 传统的软件开发过程往往需要耗费大量的时间和精力&#xff0c;因为开发人员需编写复杂的代码以完成各种功能。 低代码行业的发展&#xff0c;正好解决了这个问题&#xff0c;让复杂的代码编写一去不复返了。 文章目录 前言引入强大的平台总结 引入 低代码平台 是一种通过可…

Go基础篇:接口

目录 前言✨一、什么是接口&#xff1f;二、空接口 interface{}1、eface的定义2、需要注意的问题 三、非空接口1、iface的定义2、itab的定义3、itab缓存 前言✨ 前段时间忙着春招面试&#xff0c;现在也算告一段落&#xff0c;找到一家比较心仪的公司实习&#xff0c;开始慢慢回…

yum和repo详细解析

目录 一、rpm、yum、repo 二、repo文件详细解析 三、常用命令 四、更改epel.repo为清华源 一、rpm、yum、repo RPM RPM(Red-hat Package Manager)&#xff0c;是一个由红帽最早开发出来的包管理器&#xff0c;目前已经是大多数Linux发行的默认包管理器。RPM管理的包都是以…

程序员一个月拿两万,得知卖猪肉可以赚五万,你是选择做程序员还是卖猪肉?

在知乎上看到这么个帖子&#xff0c;觉得挺有意思&#xff0c;大家一起瞧瞧&#xff1f; 对此&#xff0c;我也看到了许多犀利的回答哈 **A&#xff1a;**我反过来问你&#xff0c;如果一对夫妇卖猪肉一个月只能挣一万&#xff0c;听说一名程序员一个月拿五万&#xff0c;他们…

刷题day66:目标和

题意描述&#xff1a; 给你一个整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 &#xff0c;在 1 之前添…

【简介】限流

限流 为什么要限流限流算法单机限流计数器算法滑动窗口算法漏桶算法令牌桶算法 分布式限流配额算法 限流策略限流位置 为什么要限流 作为有追求的程序员&#xff0c;我们都希望自己的系统跑的飞快&#xff0c;但是速度再快&#xff0c;系统处理请求耗时也不可能为0&#xff0c…

[MYAQL / Mariadb] 数据库学习-管理表记录2:匹配条件

管理表记录-匹配条件 匹配条件基本条件查询逻辑匹配&#xff08;多个条件判断&#xff09; 高级条件范围匹配模糊查询正则表达式&#xff1a; regexp四则运算 操作查询结果&#xff08;对查找到的数据再做处理&#xff09;排序分组&#xff08;一样的显示一次&#xff09;&…

【网络】交换机基本原理与配置

目录 &#x1f341;交换机工作原理 &#x1f341;交换机接口的双工模式 &#x1f341;交换机命令行模式 &#x1f341;交换机常见命令 &#x1f9e7;帮助命令 &#x1f9e7;常用命令介绍 &#x1f341;交换机的基本配置 &#x1f9e7;配置接口的双工模式及速率 &#x1f990;博…

janus videoroom 对接freeswitch conference 篇1

janus videoroom 实时性非常好&#xff0c; freeswitch conference的功能也很多 &#xff0c;有没办法集成到一块呢 让很多sip 视频终端也能显示到videoroom 里面&#xff0c; 实现方式要不两种 1.改源码实现 &#xff08;本文忽略 难度高&#xff09; 2.找一个videoroom管…

05mysql---函数

目录 1:日期函数 2:字符函数 3:数值函数 4:流程函数 1:日期函数 select 函数(参数) 函数功能举例curdate()返回当前日期2023-05-17curtime()返回当前时间14:44:33now()返回当前日期和时间2023-05-17 14:44:33year(date)获取指定date的年份month(date)获取指定date的月份day…

Python学习26:个人所得税计算器

描述‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ 2018年10月1日以前&#xff…

特征选择与特征提取

目录 一、 特征选择1、特征2、特征选择3、扩展——特征选择算法(有兴趣和精力可了解)拓展--完全搜索:拓展--启发式搜索:拓展--随机搜索:拓展--遗传算法: 二、 特征提取三、特征提取主要方法——PCA(主成分分析)1、PCA算法是如何实现的&#xff1f;PCA--零均值化&#xff08;中心…