一、版本与环境搭建:
服务器百度云(Centos7)、Redis(6.2.6)、Jdk(17.0.1)
详情如图(Redis单机部署):
Centos | 7.9 |
---|---|
Redis | 2.5.7 |
Redisson | 3.16.6 |
Jdk | 17.0.1 |
Springboot | 2.5.7 |
二、分布式锁:模拟秒杀扣减库存活动
写在前面:Redis是单线程,I/O多路复用
1.单机模式下简单设置:
问题:如果最后多个请求同时到来,同时获取库存;则均进入扣减环节,最后库存为0 订单超额
解决:加入synchronized锁,包裹扣减代码,变为单线程执行
package com.demo.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* Description: 减库存
*
* @Author: zhx & moon hongxu_1234@163.com
* @Date: 2021-12-19 14:09
* @version: V1.0.0
*/
@RestController
@RequestMapping("/reduce")
@Slf4j
public class IndexLearn {
@Resource
StringRedisTemplate stringRedisTemplate;
/**
* 获取库存
* 库存为 1 如果3个请求同时获取,则在减库存时数据异常
* 加入synchronized锁 则牺牲性能解决 单机 情况问题
*/
@GetMapping("deductStock")
public String deductStock(){
//获取剩余库存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
//如果有库存 则扣减
if (stock>0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");
log.info("剩余库存:{}",realStock);
return "success";
}else{
log.error("库存不足");
return "error";
}
}
}
2.双机部署,两台服务,通过Nginx代理进行均衡配置
问题:此时,synchronized锁无法保证两台机器之间的同步,可能两个机器同时获取同一个库存,则扣减后,库存值
减一,但是实际生成了两条订单,出现超卖情况
解决:在扣减前,通过Redis加锁,谁扣减库存谁加锁,扣减完成在释放
package com.demo.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* Description: 减库存
*
* @Author: zhx & moon hongxu_1234@163.com
* @Date: 2021-12-19 14:09
* @version: V1.0.0
*/
@RestController
@RequestMapping("/reduce")
@Slf4j
public class IndexLearn {
@Resource
StringRedisTemplate stringRedisTemplate;
/**
* 获取库存
* 1.获取库存前,通过Redis模拟锁操作,如key:product_0 value:pod_1
* 则节点1在对产品0加锁,申请库存,进行扣减
* 2.setIfAbsent 键值不存在时成功,存在时失败
*/
@GetMapping("deductStock")
public String deductStock(){
String locKey = "product_0";
String clientId = "pod_1";
try {
boolean result = stringRedisTemplate.opsForValue().setIfAbsent(locKey,clientId);
//判断是否存在锁
if (!result){
return "error";
}
//获取库存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
//扣减
if (stock>0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");
log.info("剩余库存:{}",realStock);
return "success";
}else{
log.error("库存不足");
return "error";
}
}finally {
//删除锁
stringRedisTemplate.delete(locKey);
}
}
}
三、问题来了:
1.如果节点1加锁后,宕机,则锁无法释放,此时系统将死锁,无法进行秒杀:为锁设置超时时间
stringRedisTemplate.opsForValue().setIfAbsent(locKey,clientId,10, TimeUnit.SECONDS);
2.设置超时时间后,节点1服务器异常卡顿,