本文我的博客: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(); } } }