本文我的博客:https://abeille.top/blog/detail/AT811U9QOjava
抢购活动,限量供应;redis
首先第一步设计:将库存信息放入redis进行缓存;缓存
public class DistributedRedis { @Autowired private RedisTemplate<String, Integer> redisTemplate; private void buyGoods(){ // 获取key对应的数据 Integer stock = redisTemplate.opsForValue().get("stock"); // 若是库存大于0,对库存减1 if(null != stock && stock > 0){ int realStock = stock - 1; // 将修改后的库存放入redis redisTemplate.opsForValue().set("stock", realStock); System.out.println("库存扣减成功,当前库存为:" + realStock); } else { System.out.println("库存扣减失败"); } } }
以上代码存在一个问题:当高并发场景下,会有多个请求同时获取到一样的数据,而后进行操做,实际上操做了屡次,可是库存只减了一次;
那这样的场景的解决方案有:服务器
public class DistributedRedis { @Autowired private RedisTemplate<String, Integer> redisTemplate; private void buyGoods(){ // 加同步锁 synchronized (this){ // 获取key对应的数据 Integer stock = redisTemplate.opsForValue().get("stock"); // 若是库存大于0,对库存减1 if(null != stock && stock > 0){ int realStock = stock - 1; // 将修改后的库存放入redis redisTemplate.opsForValue().set("stock", realStock); System.out.println("库存扣减成功,当前库存为:" + realStock); } else { System.out.println("库存扣减失败"); } } } }
当服务为单机的状况下,加synchronized是能够解决问题的,可是若是多实例部署,那么这个锁就没有效果了;针对新产生的这个问题,能够经过使用redis.setnx()方法来解决;使用redistemplate,操做的是setIfAbsent()方法:网络
public class DistributedRedis { @Autowired private RedisTemplate<String, Integer> redisTemplate; private void buyGoods() { // synchronized (this){ String lockKey = "stockLock"; // 针对多实例部署,可使用setIfAbsent()方法,这个代码实际是redis.setNx()方法 Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119); if (null == aBoolean || !aBoolean) { return; } // 获取key对应的数据 Integer stock = redisTemplate.opsForValue().get("stock"); // 若是库存大于0,对库存减1 if (null != stock && stock > 0) { int realStock = stock - 1; // 将修改后的库存放入redis redisTemplate.opsForValue().set("stock", realStock); System.out.println("库存扣减成功,当前库存为:" + realStock); } else { System.out.println("库存扣减失败"); } // 操做完成以后,删除锁 redisTemplate.delete(lockKey); // } } }
当方法执行时,若是中间某一步执行发生异常,那么后面的代码是没法执行到的,那也就是说,redistemplate.delete()是执行不到的;这时候的解决方案是使用try-finally或者try-with-resource来解决,代码示例以下;并发
public class DistributedRedis { @Autowired private RedisTemplate<String, Integer> redisTemplate; private void buyGoods() { // synchronized (this){ String lockKey = "stockLock"; try { // 针对多实例部署,可使用setIfAbsent()方法,这个代码实际是redis.setNx()方法 Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119); if (null == aBoolean || !aBoolean) { return; } // 获取key对应的数据 Integer stock = redisTemplate.opsForValue().get("stock"); // 若是库存大于0,对库存减1 if (null != stock && stock > 0) { int realStock = stock - 1; // 将修改后的库存放入redis redisTemplate.opsForValue().set("stock", realStock); System.out.println("库存扣减成功,当前库存为:" + realStock); } else { System.out.println("库存扣减失败"); } // } } finally { // 操做完成以后,删除锁 redisTemplate.delete(lockKey); } } }
当方法执行过程当中,服务挂掉了,或者重启了,那没有释放掉的锁会一直存在,解决方案是给这个锁设置一个失效时间;框架
public class DistributedRedis { @Autowired private RedisTemplate<String, Integer> redisTemplate; private void buyGoods() { // synchronized (this){ String lockKey = "stockLock"; try { // 针对多实例部署,可使用setIfAbsent()方法,这个代码实际是redis.setNx()方法 // 给锁设置失效时间,方法有两种:一种是先设置锁,而后在自行设置失效时间 // Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119); // redisTemplate.expire(lockKey, 15, TimeUnit.SECONDS); // 方法二:在设置锁的同时设置其失效时间 // Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119, Duration.of(15, ChronoUnit.SECONDS)); // 此方法是对下面的额方法传入的时间的一个封装 Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119, 15, TimeUnit.SECONDS); if (null == aBoolean || !aBoolean) { return; } // 获取key对应的数据 Integer stock = redisTemplate.opsForValue().get("stock"); // 若是库存大于0,对库存减1 if (null != stock && stock > 0) { int realStock = stock - 1; // 将修改后的库存放入redis redisTemplate.opsForValue().set("stock", realStock); System.out.println("库存扣减成功,当前库存为:" + realStock); } else { System.out.println("库存扣减失败"); } // } } finally { // 操做完成以后,删除锁 redisTemplate.delete(lockKey); } } }
当服务多实例部署是,因为网络,服务器等缘由,方法执行时间不等,可能存在在释放锁的时候,这个锁已经失效的状况,或者释放的锁不是本次操做添加的锁,那么这个锁也就失效了;解决方法是在加锁时,添加一个身份标识,在释放锁时,判断这个锁是否本身添加的,示例代码以下:dom
public class DistributedRedis { @Autowired private RedisTemplate<String, Integer> redisTemplate; private void buyGoods() { // synchronized (this){ String lockKey = "stockLock"; int nextInt = 0; try { nextInt = new Random().nextInt(100); // 针对多实例部署,可使用setIfAbsent()方法,这个代码实际是redis.setNx()方法 // 给锁设置失效时间,方法有两种:一种是先设置锁,而后在自行设置失效时间 // Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119); // redisTemplate.expire(lockKey, 15, TimeUnit.SECONDS); // 方法二:在设置锁的同时设置其失效时间 // Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119, Duration.of(15, ChronoUnit.SECONDS)); // 此方法是对下面的额方法传入的时间的一个封装 Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, nextInt, 15, TimeUnit.SECONDS); if (null == aBoolean || !aBoolean) { return; } // 获取key对应的数据 Integer stock = redisTemplate.opsForValue().get("stock"); // 若是库存大于0,对库存减1 if (null != stock && stock > 0) { int realStock = stock - 1; // 将修改后的库存放入redis redisTemplate.opsForValue().set("stock", realStock); System.out.println("库存扣减成功,当前库存为:" + realStock); } else { System.out.println("库存扣减失败"); } // } } finally { // 操做完成以后,删除锁 Integer lockValue = redisTemplate.opsForValue().get(lockKey); if(0 != nextInt && null != lockValue && nextInt == lockValue){ redisTemplate.delete(lockKey); } } } }
当一个方法中使用redis分布式锁,它的失效时间肯定多少为合适?这个问题能够经过使用redisson框架来解决,示例代码以下;分布式
public class DistributedRedis { @Autowired private RedisTemplate<String, Integer> redisTemplate; @Autowired private Redisson redisson; private void buyGoods() { // synchronized (this){ String lockKey = "stockLock"; // int nextInt = 0; RLock lock = redisson.getLock(lockKey); try { // 默认失效时间为-1,不失效 lock.lock(15, TimeUnit.SECONDS); // nextInt = new Random().nextInt(100); // 针对多实例部署,可使用setIfAbsent()方法,这个代码实际是redis.setNx()方法 // 给锁设置失效时间,方法有两种:一种是先设置锁,而后在自行设置失效时间 // Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119); // redisTemplate.expire(lockKey, 15, TimeUnit.SECONDS); // 方法二:在设置锁的同时设置其失效时间 // Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, 119, Duration.of(15, ChronoUnit.SECONDS)); // 此方法是对下面的额方法传入的时间的一个封装 // Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, nextInt, 15, TimeUnit.SECONDS); // // if (null == aBoolean || !aBoolean) { // return; // } // 获取key对应的数据 Integer stock = redisTemplate.opsForValue().get("stock"); // 若是库存大于0,对库存减1 if (null != stock && stock > 0) { int realStock = stock - 1; // 将修改后的库存放入redis redisTemplate.opsForValue().set("stock", realStock); System.out.println("库存扣减成功,当前库存为:" + realStock); } else { System.out.println("库存扣减失败"); } // } } finally { // 操做完成以后,删除锁 // Integer lockValue = redisTemplate.opsForValue().get(lockKey); // if(0 != nextInt && null != lockValue && nextInt == lockValue){ // redisTemplate.delete(lockKey); // } lock.unlock(); } } }