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).可用性( ...
随机推荐
- 云服务器Linux 时间与本地时间不一致
云服务器Linux 时间与本地时间不一致 问题解释: 云服务器和本地计算机之间的时间不一致可能是因为它们使用的时间同步服务不同,或者云服务器没有配置自动对时. 解决方法: 手动同步时间:可以使用d ...
- 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
引言 ❝ 小编是一名10年+的.NET Coder,期间也写过Java.Python,从中深刻的认识到了软件开发与语言的无关性.现在小编已经脱离了一线开发岗位,在带领团队的过程中,发现了很多的问题,究 ...
- DeepSeek 多模态大模型 Janus-Pro 本地部署教程
下载模型仓库 git clone https://github.com/deepseek-ai/Janus.git 国内下载仓库失败时,可以使用以下代理: git clone https://gith ...
- Golang Linux、Windows、Mac 下交叉编译
前言 Golang 支持交叉编译, 即同一份代码,在一个平台上生成,然后可以在另外一个平台去执行. 之前写过一篇 Golang windows下 交叉编译 感觉写的不够全面,这篇作为补充. 交叉编译 ...
- Flask快速入门2
六,Flask HTTP方法 Http协议是万维网中数据通信的基础.在该协议中定义了从指定URL检索数据的不同方法. 下表总结了不同的http方法: 序号 方法 描述 1 GET 以未加密的形式将数据 ...
- JOKER可视化开发工具迎来重大更新
为紧跟行业前沿趋势,满足开发者日益增长的需求,我们于2025年3月13日对平台开展了全方位升级.此次更新聚焦前端交互的便捷性.服务端功能的强大性以及通用操作的流畅性,在多方面进行了深度优化.尤为值得一 ...
- Joker 智能开发平台再推重磅新品,引领开发新潮流
自 Joker 智能开发平台的前端可视化版本惊艳亮相后,便迅速在行业内掀起热潮,其创新的理念与出色的性能,为开发者和企业打造了高效.便捷的开发新路径,备受瞩目与好评.如今,Joker 智能开发平台即将 ...
- vue实现不同用户权限的方法
Vue 实现不同用户权限的方法 在项目中,实现不同用户的权限控制是常见的需求也是常见的功能模块,例如管理系统中不同角色(管理员.普通用户等)应有不同的访问权限,小程序.App等在不同角色登入的时候显示 ...
- TdxpageControl融合窗口和free
for I := cxpgcntrl1.PageCount - 1 downto 0do begin if cxpgcntrl1.Pages[i].Caption <> '首页' then ...
- argo-cd基于Kubernetes的声明式持续部署
argo-cd基于Kubernetes的声明式持续部署 什么是argo-cd? Argo CD是一个基于Kubernetes的声明式GitOps持续交付工具. 为什么CD ? 应用程序定义.配置和环境 ...