大厂秒杀系统后端Redis高并发分布式锁实战
一,秒杀下单减库存实例讲解
这里模拟一个下单场景,具体架构如下
比如说tomcat1和tomcat2是同一个下单系统,springboot项目,分布式处理在两台机器上,使用nginx进行负载,然后商品数量存储在redis中,对nginx请求且高并发压测。
1,首先先搭建一下基础环境
springboot集成redis参考文章:https://www.cnblogs.com/fantongxue/p/12626620.html
创建两个springboot工程(复制两份),接口如下
@RestController
public class TestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/order")
public void order(){
//redis中的库存
stringRedisTemplate.opsForValue().set("order","200");
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("order"));
if(stock>0){
int realStock = stock -1;
stringRedisTemplate.opsForValue().set("order",realStock+"");
System.out.println("扣减成功,剩余库存:"+realStock);
}else{
System.out.println("扣减失败,库存不足");
}
}
}
然后配置nginx负载均衡,如何负载参考文章:https://www.cnblogs.com/fantongxue/p/12443583.html
然后访问nginx地址和端口,http://localhost:90/order,会负载到两个springboot上
2,如果是单机环境,问题就很简单了
使用synchronized代码块,每次执行只能保证一个线程进入方法执行代码,但如果是分布式的话,怎么处理?
@RequestMapping("/order")
public void order(){
//redis中的库存
synchronized (this){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("order"));
if(stock>0){
int realStock = stock -1;
stringRedisTemplate.opsForValue().set("order",realStock+"");
System.out.println("扣减成功,剩余库存:"+realStock);
}else{
System.out.println("扣减失败,库存不足");
}
}
}
3,入门级分布式锁
在redis处理之前,先存一个K,V,这里的K的作用就是作为一个锁,使用redisTemplate的setIfAbsent方法可以实现锁机制。
setIfAbsent就是如果存在这个K,不做任何处理,如果不存在,设置V的值,仔细品!
@RequestMapping("/order")
public String order(){
try{
String lockKey="lockKey";//给一个锁起个名字
String uuid = UUIDUtil.getUUID();//随机值
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, uuid,10,TimeUnit.SECONDS);//和jedis.setnx(k,v)一样
if(!result){
return "error_code";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("order"));
if(stock>0){
int realStock = stock -1;
stringRedisTemplate.opsForValue().set("order",realStock+"");
System.out.println("扣减成功,剩余库存:"+realStock);
}else{
System.out.println("扣减失败,库存不足");
}
}finally{
if(uuid.equals(stringRedisTemplate.opsForValue().get(lockKey))){
//删除锁
stringRedisTemplate.delete(lockKey);
}
}
return "下单成功";
}
设置超时时间的目的:如果后端服务器宕机,当加了锁之后宕机了,那么其他线程就进不来这个方法了,就这么简单!
设置UUID的目的:把每个线程区分开,如果不设置会导致什么结果?比如执行完这个方法需要15秒(慢查询,高并发等因素),而执行到10秒的时候,lockKey自动失效,那么其他线程就可以获取锁进入方法。
上面的分布式锁表面看着没多大问题了对吧?其实还有问题!超时时间设置多久合适?
结果:设置多久都不合适!如果设置时间短,线程A还没有执行完方法中的代码,那边锁就已经自动释放了,不合适;如果设置时间过长,线程A加了锁之后宕机了,其他线程都必须要等到自动释放锁之后才可以继续进入方法。
二,大厂分布式锁redission框架实战
redission内部优化了上面的锁机制,解决了超时时间的设置问题。
它的实现思路:redissonLock.lock()方法的底层会启动另一个线程每隔一段时间检查锁是否快到期了,如果快到期了,延长超时时间。
Redisson实现Redis分布式锁的底层原理:https://www.cnblogs.com/windpoplar/p/11964088.html
手写一个redission分布式锁见下篇文章:手写一个基于redis的分布式锁(watch dog看门狗 / redisson分布式锁的底层原理)
引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
初始化
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
//初始化
@Bean
public Redisson redisson(){
//此为单机模式
Config config=new Config();
config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
return (Redisson)Redisson.create(config);
}
}
然后使用即可
@RestController
public class TestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private Redisson redisson;
@RequestMapping("/order")
public void order(){
RLock redissonLock = redisson.getLock("lockName");
try{
//加锁
redissonLock.lock();
//redis中的库存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("order"));
if(stock>0){
int realStock = stock -1;
stringRedisTemplate.opsForValue().set("order",realStock+"");
System.out.println("扣减成功,剩余库存:"+realStock);
}else{
System.out.println("扣减失败,库存不足");
}
}finally {
//释放锁
redissonLock.unlock();
}
}
}
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~