@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; }