@RestController
@RequestMapping("/skill")
@Slf4j
public class SecKillController {
@Autowired
private SecKillService secKillService;
//查询秒杀活动特价商品的信息
@GetMapping("/query/{productId}")
public String query(@PathVariable String productId)throws Exception {
return secKillService.querySecKillProductInfo(productId);
}
//秒杀
@GetMapping("/order/{productId}")
public String skill(@PathVariable String productId)throws Exception {
log.info("@skill request, productId:" productId);
secKillService.orderProductMockDiffUser(productId);
return secKillService.querySecKillProductInfo(productId);
}
}
@Service
public class SecKillServiceImpl implements SecKillService {
private static final int TIMEOUT = 10 * 1000; //超时时间 10s
@Autowired
private RedisLock redisLock;
// 雅诗兰黛特价小棕瓶,限量100000份
static Map<String,Integer> products;
static Map<String,Integer> stock;
static Map<String,String> orders;
static {
//模拟多个表,商品信息表,库存表,秒杀成功订单表
products = new HashMap<>();
stock = new HashMap<>();
orders = new HashMap<>();
//商品Id---商品库存
products.put("123456", 100000);
//商品id---商品库存
stock.put("123456", 100000);
}
private String queryMap(String productId) {
return "雅诗兰黛小棕瓶特价,限量份"
products.get(productId)
" 还剩:" stock.get(productId) " 份"
" 该商品成功下单用户数目:"
orders.size() " 人" ;
}
@Override
public String querySecKillProductInfo(String productId) {
return this.queryMap(productId);
}
//秒杀的逻辑:可以在该方法生加上Synchronized解决超卖
@Override
public void orderProductMockDiffUser(String productId) {
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if(stockNum == 0) {
throw new SellException(100,"活动结束");
}else {
//2.下单(模拟不同用户openid不同)
orders.put(KeyUtil.genUniqueKey(),productId);
//3.减库存
stockNum =stockNum-1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.更新库存
stock.put(productId,stockNum);
}
}
}
127.0.0.1:6379> set num 10 OK 127.0.0.1:6379> setnx lock-num 1 -- 加锁 (integer) 1 127.0.0.1:6379> incrby num -1 (integer) 9 127.0.0.1:6379> del lock-num -- 释放锁 (integer) 1 127.0.0.1:6379> setnx lock-num 1 -- 当前客户端加锁 (integer) 1 127.0.0.1:6379> setnx lock-num 1 -- 其他客户端获取不到锁 (integer) 0
127.0.0.1:6379> set name 123 OK 127.0.0.1:6379> setnx lock-name 1 -- 锁的名称key (integer) 1 127.0.0.1:6379> expire lock-name 20 -- 使用expire为锁key添加时间限定 (integer) 1 127.0.0.1:6379> get name "123"
redis> GETSET db mongodb # 没有旧值,返回 nil (nil) redis> GET db "mongodb" redis> GETSET db redis # 返回旧值 mongodb "mongodb" redis> GET db "redis"
@Component
@Slf4j
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key:productId
* @param value 当前时间 超时时间
*/
public boolean lock(String key, String value) {
//setnx----对应方法 setIfAbsent(key, value),如果可以加锁返回true,不可以加锁返回false
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//下面这段代码时为了解决可能出现的死锁情况
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间:重新设置锁的过期时间value,并返回上一个过期时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
//currentValue =2020-12-28,两个线程的value=2020-12-29,只会有一个线程拿到锁
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
//解锁
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e) {
log.error("【redis分布式锁】解锁异常, {}", e);
}
}
}
@Override
public void orderProductMockDiffUser(String productId) {
//加锁
//锁的过期时间为当前时间 过期时长
long time = System.currentTimeMillis() TIMEOUT;
if(!redisLock.lock(productId,String.valueOf(time))){
throw new SellException(101,"人太多,稍后再来");
}
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if(stockNum == 0) {
throw new SellException(100,"活动结束");
}else {
//2.下单(模拟不同用户openid不同)
orders.put(KeyUtil.genUniqueKey(),productId);
//3.减库存
stockNum =stockNum-1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.更新库存
stock.put(productId,stockNum);
}
//解锁
redisLock.unlock(productId,String.valueOf(time));
}
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//下面的代码是为了解决可能出现的死锁的情况????
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//下面这个逻辑又怎么理解????
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
//秒杀业务方法
@Override
public void orderProductMockDiffUser(String productId) {
//加锁
//锁的过期时间为当前时间 过期时长
long time = System.currentTimeMillis() TIMEOUT;
if(!redisLock.lock(productId,String.valueOf(time))){
throw new SellException(101,"人太多,稍后再来");
}
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if(stockNum == 0) {
throw new SellException(100,"活动结束");
}else {
//2.下单
orders.put(KeyUtil.genUniqueKey(),productId);
//3.减库存
stockNum =stockNum-1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.更新库存
stock.put(productId,stockNum);
}
//解锁
redisLock.unlock(productId,String.valueOf(time));
}
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
return false;
}
//下面的代码是为了解决可能出现的死锁的情况????
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//下面这个逻辑又怎么理解????
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
currentValue=2020-12-18
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//下面这个逻辑又怎么理解????
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}