1.分布式锁实现
上锁:setnx key value
解锁:del key
超时机制:set key value nx ex time
基于此思路实现伪代码:
public class RedisLock{@Autowiredprivate static RedisTemplate rt; private static final LOCK_WORD = "lock";private static final int TIME_OUT = 3;public void tryLock(String uuid ){//String uuid = UUID.get();boolean lock = rt.setnx(LOCK_WORD,uuid,TIME_OUT,TimeUnit.SECOND);if(lock){ // 没有考虑可重入锁设计try{do(业务逻辑)}finally{ // 需要保证原子操作String tempLock = rt.get(LOCK_WORD);if(uuid.equals(tempLock)){rt.del(LOCK_WORD);}} }else {try{Thread.sleep(100);tryLock(); // 递归调用可能会爆栈,应该实现一个等待队列}catch(InterruptedException e){e.printStackTrace();}}}
}
实际实现代码:
@GetMapping("testLock")
public void testLock() {// 1 获取锁,setIfAbsent,同时设置3秒过期,以避免中间出现异常,导致锁一直无法释放Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111", 3, TimeUnit.SECONDS);// 2 获取锁成功,查询num的值if (lock) {Object value = redisTemplate.opsForValue().get("num");// 2.1 判断num为空,returnif (StringUtils.isEmpty(value)) {return;}// 2.2 有值就转成intint num = Integer.parseInt(value + "");// 2.3 把redis的num加1redisTemplate.opsForValue().set("num", ++num);// 2.4 释放锁,删除lockString lockUuid = (String) redisTemplate.opsForValue().get("lock");if ("111".equals(lockUuid)) {redisTemplate.delete("lock");}} else {// 3 获取锁失败,每隔0.1秒再获取try {Thread.sleep(100);testLock();} catch (InterruptedException e) {e.printStackTrace();}}
}
2.秒杀场景(解决超卖)
思路:
- 维护一个用户 id,商品 id,库存信息,秒杀成功名单
- 先确认库存正常,即秒杀开始
- 先查询 uid 是否在成功名单,避免重复秒杀
- 再查询库存是否充足,
- 利用事务完成: 扣除库存、添加名单,秒杀成功
伪代码:
@Autowired
private RedisTemplate rt;public boolean doSecondKill(String uid,String productId){String successListKey = "User-"+productId;String invenKey = "Inventory-"+productId;if(rt.get(invenKey)==null){print("秒杀尚未开启");return false;}if(rt.sismember(successListKey,uid)){print("不可重复秒杀");return false;}if(Integer.parseInt(rt.get(invenKey))<=0){print("秒杀已结束");return false;}Transaction multi = rt.multi();multi.decr(invenKey); // 扣除库存multi.sadd(successListKey,uid); // 添加成功秒杀List<Object> result = multi.exec();if(result==null || result.size()==0){print("秒杀失败");return false;}print("秒杀成功");return true;
}
代码实现
public class SecKill {public static void main(String[] args) {Jedis jedis = new Jedis();}public static boolean doSecKill(String uid, String proid) throws IOException {// 1.uid和proid判断if (uid == null || proid == null) {return false;}// 2.连接jedis,使用连接池,避免超时JedisPool jedisPool = new JedisPool();Jedis jedis = jedisPool.getResource();// 3. 拼接keyString invenKey = "sk:" + proid + "qt";String userKey = "sk:" + proid + "qt";// 监视库存jedis.watch(invenKey);// 4. 判断是否开始String inven = jedis.get(invenKey);if (inven == null) {System.out.println("秒杀尚未开始");jedis.close();return false;}// 5.判断是否抢过if (jedis.sismember(userKey, uid)) {System.out.println("不可重复参与!");jedis.close();return false;}// 6.判断库存是否充足if (Integer.parseInt(jedis.get(invenKey)) <= 0) {System.out.println("秒杀已结束,失败");jedis.close();return false;}// multi事务操作Transaction multi = jedis.multi();multi.decr(invenKey);multi.sadd(userKey, uid);List<Object> result = multi.exec();if (result == null || result.size() == 0) {System.out.println("秒杀失败");jedis.close();return false;}System.out.println("秒杀成功");jedis.close();return true;}
}