Spring Boot基于redis分布式锁模拟直播秒杀场景
摘要:Spring Boot基于redis分布式锁模拟秒杀场景,未完待续
§前言
在Java中,关于锁我想大家都很熟悉,例如synchronized和Lock等。在并发编程中,我们通过加锁来保证数据一致。但是Java中的锁,只能保证在同一个JVM进程内中执行,如果在分布式集群环境下,就缴械投降,那如何处理呢?使用Redis锁来处理。
测试用例所用软件开发环境如下:
java version 13.0.1
IntelliJ IDEA 2019.3.2 (Ultimate Edition)
Spring Boot 2.3.0.RELEASE
Redis 5.0.10(暂时使用单台部署,后面使用集群)
§案例分析
模拟一个比较常见的秒杀场景,假如只有1000件商品上架,这时候就需要用到锁。在《Spring Boot 整合Jedis连接Redis和简单使用》的JedisUtil基础上增加加锁和解锁函数:
package com.eg.wiener.utils;
import com.eg.wiener.config.JedisPoolFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.Collections;
import java.util.Map;
@Component
public class JedisUtil {
private static Logger logger = LoggerFactory.getLogger(JedisUtil.class);
private static String lock_key = "lock_"; //锁键
private static String lock_ok = "OK"; //锁键
protected static long internalLockLeaseTime = 30000;//锁过期时间
private long timeout = 999999; //获取锁的超时时间
private static String lua_script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//SET命令的参数
private final static SetParams params = SetParams.setParams().nx().px(internalLockLeaseTime);
@Autowired
private JedisPool jedisPool;
@Autowired
private JedisPoolFactory jedisPoolFactory;
/**
* 存储字符串键值对,永久有效
* @param key
* @param value
* @return
* @author hw
* @date 2018年12月14日
*/
public String set(String key, String value) {
Jedis jedis = jedisPool.getResource();
try {
return jedis.set(key, value);
} catch (Exception e) {
return "-1";
} finally {
jedis.close();
}
}
/**
* 根据传入key获取指定Value
* @param key
* @return
* @author hw
* @date 2018年12月14日
*/
public String get(String key) {
Jedis jedis = jedisPool.getResource();
try {
return jedis.get(key);
} catch (Exception e) {
return "-1";
} finally {
jedis.close();
}
}
/**
* 删除字符串键值对
* @param key
* @return
* @author hw
* @date 2018年12月14日
*/
public Long del(String key) {
Jedis jedis = jedisPool.getResource();
try {
return jedis.del(key);
} catch (Exception e) {
return -1L;
} finally {
jedis.close();
}
}
/**
* 校验Key值是否存在
*/
public Boolean exists(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.exists(key);
} catch (Exception e) {
return false;
} finally {
jedis.close();
}
}
/**
* 分布式锁
* @param key
* @param value
* @param time 锁的超时时间,单位:秒
*
* @return 获取锁成功返回"OK",失败返回null
*/
public String getDistributedLock(String key,String value,int time){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.set(key, value, new SetParams().nx().ex(time));
} catch (Exception e) {
return null;
} finally {
jedis.close();
}
}
/**
* 加锁
*
* @param business_code 业务编码
* @param id
* @return
*/
public boolean lock(String business_code, String id) {
Jedis jedis = jedisPool.getResource();
Long start = System.currentTimeMillis();
try {
while (true) {
//SET命令返回OK ,则证明获取锁成功
String lock = jedis.set(lock_key.concat(business_code), id, params);
if (lock_ok.equals(lock)) {
return true;
}
//否则循环等待,在timeout时间内仍未获取到锁,则获取失败
long waitTime = System.currentTimeMillis() - start;
if (waitTime >= timeout) {
return false;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
logger.error("sleep失败,", e);
}
}
} finally {
jedis.close();
}
}
/**
* 解锁
*
* @param id
* @return
*/
public boolean unlock(String business_code, String id) {
Jedis jedis = jedisPool.getResource();
try {
Object result = jedis.eval(lua_script,
Collections.singletonList(lock_key.concat(business_code)),
Collections.singletonList(id));
if ("1".equals(result.toString())) {
return true;
}
return false;
} finally {
jedis.close();
}
}
public Map<String, String> getMap(String key) {
Jedis jedis = jedisPool.getResource();
try {
return jedis.hgetAll(key);
} catch (Exception e) {
return null;
} finally {
jedis.close();
}
}
public Long setMap(String key, Map<String, String> value) {
Jedis jedis = jedisPool.getResource();
try {
return jedis.hset(key, value);
} catch (Exception e) {
logger.info("-------向Redis存入Map失败--------", e);
return -1L;
} finally {
jedis.close();
}
}
}
解锁是通过jedis.eval来执行一段LUA来实现的,这是当下流行的靠谱方案,它把锁的Key键和生成的字符串当做参数传入。
在UserController中添加测试函数,模拟直播秒杀场景,上架商品数量右主播设置。由于是模拟是否加锁解锁成功,为了简化,故在程序中自动生成客户id。
private int count = 0;
/**
* 模拟直播秒杀场景,上架商品数量限指定件
*
* @param productNum 上架商品数量
* @return
* @throws InterruptedException
*/
@ApiOperation(value = "测试Redis分布式锁")
@GetMapping("/testRedisLock")
@ResponseBody
public String testRedisLock(Integer productNum) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(productNum);
ExecutorService executorService = Executors.newFixedThreadPool(100);
long start = System.currentTimeMillis();
for (int i = 0; i < productNum; i++) {
executorService.execute(() -> {
String businessCode = "test";
// 模拟下单用户,其实作为入参,为了简化,故自动生成
String id = UUID.randomUUID().toString();
try {
// jedisUtil.lock(businessCode, id);
jedisUtil.getDistributedLock(businessCode, id, 1);
count++;
} finally {
jedisUtil.unlock(businessCode, id);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
String ret = String.format("执行线程数:%s,总耗时:%s,count数为:%s", productNum, System.currentTimeMillis() - start, count);
logger.info(ret);
return ret;
}
由代码可知,商品售罄即止Swagger3测试效果如图所示:
大家可以试试另外一种加锁方式,你有Redis分布式锁的更佳实现方案吗?欢迎留言!
§小结
对于上述应用单节点Redis分布式锁模拟直播秒杀场景,您怎么看?欢迎参与话题互动讨论,分享你的观点和看法, 评论区留言哦,喜欢小编文章的朋友请点赞,谢谢你的参与!
Spring Boot基于redis分布式锁模拟直播秒杀场景的更多相关文章
- 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁
spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...
- RedLock.Net - 基于Redis分布式锁的开源实现
工作中,经常会遇到分布式环境中资源访问冲突问题,比如商城的库存数量处理,或者某个事件的原子性操作,都需要确保某个时间段内只有一个线程在访问或处理资源. 因此现在网上也有很多的分布式锁的解决方案,有数据 ...
- Spring Boot 2实现分布式锁——这才是实现分布式锁的正确姿势!
参考资料 网址 Spring Boot 2实现分布式锁--这才是实现分布式锁的正确姿势! http://www.spring4all.com/article/6892
- springboot+redis分布式锁-模拟抢单
本篇内容主要讲解的是redis分布式锁,这个在各大厂面试几乎都是必备的,下面结合模拟抢单的场景来使用她:本篇不涉及到的redis环境搭建,快速搭建个人测试环境,这里建议使用docker:本篇内容节点如 ...
- Redis分布式锁实现简单秒杀功能
这版秒杀只是解决瞬间访问过高服务器压力过大,请求速度变慢,大大消耗服务器性能的问题. 主要就是在高并发秒杀的场景下,很多人访问时并没有拿到锁,所以直接跳过了.这样就处理了多线程并发问题的同时也保证了服 ...
- 基于Redis分布式锁的正确打开方式
分布式锁是在分布式环境下(多个JVM进程)控制多个客户端对某一资源的同步访问的一种实现,与之相对应的是线程锁,线程锁控制的是同一个JVM进程内多个线程之间的同步.分布式锁的一般实现方法是在应用服务器之 ...
- 基于redis分布式锁实现“秒杀”
转载:http://blog.5ibc.net/p/28883.html 最近在项目中遇到了类似“秒杀”的业务场景,在本篇博客中,我将用一个非常简单的demo,阐述实现所谓“秒杀”的基本思路. 业务场 ...
- 基于redis分布式锁实现“秒杀”(转载)
转载:http://blog.csdn.net/u010359884/article/details/50310387 最近在项目中遇到了类似“秒杀”的业务场景,在本篇博客中,我将用一个非常简单的de ...
- c# 基于redis分布式锁
在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量. 而同步的本质是通过锁来实现的.为了实现多个线程在 ...
- 基于Redis分布式锁(获取锁及解锁)
目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency).可用性( ...
随机推荐
- abaqus&FEA资料-科研&工具-导航
复合材料力学 BLOGs上的书籍共享文件夹 2004-Mechanics of Composite Structural Elements.pdf,onedrive link Mechanics Of ...
- Mysql join算法深入浅出
导语 联表查询在日常的数据库设计中非常的常见,但是联表查询可能会带来性能问题,为了调优.避免设计出有性能问题的SQL,在explain命令中,会显示用的是哪个join算法,学习一下join过程是非常有 ...
- 如何调用CMD实现多个同类文件合并的研究 · 二进制 · 依次 · 文本图像视频音频
引言 视频网站内,使用视频下载嗅探器下载了视频,打开资源管理器一看,是几千个.ts文件,见下图: 通过播放部分视频,发现其实内容是完整的,只是自动切割了多份,倘若无缝拼接为一个完整视频单元,就可以 ...
- Kubernetes的工作机制
云计算时代的操作系统 Kubernetes 是一个生产级别的容器编排平台和集群管理系统,能够创建.调度容器,监控.管理服务器. Kubernetes 的基本架构 操作系统的一个重要功能就是抽象,从繁琐 ...
- wps时间戳转换成日期
第一步 打开WPS表格,选择空表格 第二步 右击选择"设置单元格格式" 第三步 选择"日期",然后选择需要的日期类型 第四步 然后在表格里,输入公式 =(D2/ ...
- 通过 JS 修改具体标签的属性的属性值
博客地址:https://www.cnblogs.com/zylyehuo/ window.addEventListener('DOMContentLoaded', function() { var ...
- 本地项目上传到gitee
前置条件:本地已经装好了GIt和GITEE有远程地址 检查本地装好了GIT:鼠标右键 检查准备好了远程:地址 本地项目拷贝到目录 D:\tmp2024-02-19\code 本地项目所在文件夹打开gi ...
- php代码审计实战-开源项目Materialized CMS漏洞检测
一.下载Materialized CMS 链接地址:https://sourceforge.net/projects/materialized-cms/files/latest/download 二. ...
- .NET多线程编程之CountdownEvent使用
简单来说,使用这个类可以让主线程等待子线程都完成任务之后才执行任务 1 static void Main(string[] args) 2 { 3 ///子任务的数量 4 CountdownEvent ...
- EditorGUILayout.BeginVertical("textfield")