1.配置
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) { RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } }
2 . 1,0版存在的问题→ final块的判断del删除操作不是原子性的
@RestController public class GoodController { private static final String REDIS_LOCK = "redisLock"; @Autowired private RedisTemplate redisTemplate; @RequestMapping("/buy") public String buy() { String value = UUID.randomUUID().toString() + Thread.currentThread().getName(); try { Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS); if (!flag) { return "抢锁失败"; } String result = (String) redisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result); if (goodsNumber > 0) { int realNumber = goodsNumber - 1; redisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber)); System.out.println("成功买到商品,库存还剩下" + realNumber); } else { System.out.println("商品已卖完"); } return "商品已卖完,活动结束"; } finally { //判断加锁与解锁是不是同一个客户端 if(redisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) { //若在此时,这把锁突然不是这个客户端的,则会误解锁 ,因为这把不是原子操作 redisTemplate.delete(REDIS_LOCK); } } } }
3 . 2,0版使用Redis自身事务解决→ final块的判断del删除操作不是原子性的
@RestController public class GoodController { private static final String REDIS_LOCK = "redisLock"; @Autowired private RedisTemplate redisTemplate; @RequestMapping("/buy") public String buy() { String value = UUID.randomUUID().toString() + Thread.currentThread().getName(); try { Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS); if (!flag) { return "抢锁失败"; } String result = (String) redisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result); if (goodsNumber > 0) { int realNumber = goodsNumber - 1; redisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber)); System.out.println("成功买到商品,库存还剩下" + realNumber); } else { System.out.println("商品已卖完"); } return "商品已卖完,活动结束"; } finally { while (true) { redisTemplate.watch(REDIS_LOCK); if(redisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) { redisTemplate.setEnableTransactionSupport(true); redisTemplate.multi(); redisTemplate.delete(REDIS_LOCK); List list = redisTemplate.exec(); if(list == null) { continue; } } redisTemplate.unwatch(); break; } } } }
4.使用lua脚本来解决→ final块的判断del删除操作不是原子性的
@RestController public class GoodController { private static final String REDIS_LOCK = "redisLock"; @Autowired private RedisTemplate redisTemplate; @RequestMapping("/buy") public String buy() throws Exception { String value = UUID.randomUUID().toString() + Thread.currentThread().getName(); try { Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS); if (!flag) { return "抢锁失败"; } String result = (String) redisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result); if (goodsNumber > 0) { int realNumber = goodsNumber - 1; redisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber)); System.out.println("成功买到商品,库存还剩下" + realNumber); } else { System.out.println("商品已卖完"); } return "商品已卖完,活动结束"; } finally { Jedis jedis = RedisUtils.getJedis(); String script = "if redis.call('get',KEYS[1]) == ARGV[1]" + "then " + "return redis.call('del',KEYS[1]) " + "else " + " return 0" + "end"; try { Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value)); if("1".equals(o.toString())) { System.out.println("------del redis lock ok"); } else { System.out.println("-------del redis lock error"); } } finally { if(null != jedis) { jedis.close(); } } } } }
5.分布式锁如何续期? 确保redisLock过期时间大于业务的执行时间的问题.
1.Redis (AP) ,redis异步复制造成的锁丢失,比如:主节点没来得及把刚刚set进来的这条数据给从节点,就挂了,,它是先返回结果再同步给从节点.跟zookeeper不一样.
2.Zookeeper(CP) ,主节点先同步完从节点再返回结果
综合上述,redis集群环境下,我们自己写的也不ok,直觉上RedLock之Redisson落地实现,(最终版)
@RestController public class GoodController { private static final String REDIS_LOCK = "redisLock"; @Autowired private RedisTemplate redisTemplate; @Autowired private Redisson redisson; @RequestMapping("/buy") public String buy() throws Exception { String value = UUID.randomUUID().toString() + Thread.currentThread().getName(); RLock redissonLock = redisson.getLock(REDIS_LOCK); redissonLock.lock(); try { String result = (String) redisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result); if (goodsNumber > 0) { int realNumber = goodsNumber - 1; redisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber)); System.out.println("成功买到商品,库存还剩下" + realNumber); } else { System.out.println("商品已卖完"); } return "商品已卖完,活动结束"; } finally { if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) { redissonLock.unlock(); } } } }