@RequestMapping("/deduct_stock") public String deductStock() { String lockKey = "product_001"; try { /*Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "aaa"); //jedis.setnx stringRedisTemplate.expire(lockKey, 30, TimeUnit.SECONDS); //設置超時*/ //爲解決原子性問題將設置鎖和設置超時時間合併 Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "aaa", 10, TimeUnit.SECONDS); //未設置成功,當前key已經存在了,直接返回錯誤 if (!result) { return "error_code"; }
//業務邏輯實現,扣減庫存 .... } catch (Exception e) { e.printStackTrace(); }finally { stringRedisTemplate.delete(lockKey); } return "end"; }
上述代碼能夠看到,當前鎖的失效時間爲10s,若是當前扣減庫存的業務邏輯執行須要15s時,高併發時會出現問題:redis
@RequestMapping("/deduct_stock") public String deductStock() { String lockKey = "product_001"; //定義惟一的客戶端ID String clientId = UUID.randomUUID().toString(); try { //爲解決原子性問題將設置鎖和設置超時時間合併,將clientID做爲值放入鎖中 Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS); //未設置成功,當前key已經存在了,直接返回錯誤 if (!result) { return "error_code"; } //業務邏輯實現,扣減庫存 .... } catch (Exception e) { e.printStackTrace(); }finally { //只有在獲取鎖的值爲當前clientId時纔會進行刪除鎖操做 if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) { stringRedisTemplate.delete(lockKey); } } return "end"; }
這樣能保證每一個線程刪除的鎖爲當前線程添加的鎖,可是仍是會有超賣的問題:由於線程1在尚未執行完成的時候,此時鎖已經到達過時時間,此時線程2則會加鎖成功數據庫
定義一個子線程,定時去查看是否存在主線程的持有當前鎖,若是存在則爲其延長過時時間。網絡
@Autowired Redisson redisson; @RequestMapping("/deduct_stock_redisson") public String deductStockRedisson() { String lockKey = "product_001"; RLock rlock = redisson.getLock(lockKey); try { rlock.lock(); //業務邏輯實現,扣減庫存 .... } catch (Exception e) { e.printStackTrace(); } finally { rlock.unlock(); } return "end"; }
當主Redis加鎖了,開始執行線程,若還未將鎖經過異步同步的方式同步到從Redis節點,主節點就掛了,此時會把某一臺從節點做爲新的主節點,此時別的線程就能夠加鎖了,這樣就出錯了,怎麼辦?併發
因爲zk集羣的特色,其支持的是CP。而Redis集羣支持的則是AP。app
假設有3個redis節點,這些節點之間既沒有主從,也沒有集羣關係。客戶端用相同的key和隨機值在3個節點上請求鎖,請求鎖的超時時間應小於鎖自動釋放時間。當在2個(超過半數)redis上請求到鎖的時候,纔算是真正獲取到了鎖。若是沒有獲取到鎖,則把部分已鎖的redis釋放掉。dom
@RequestMapping("/deduct_stock_redlock") public String deductStockRedlock() { String lockKey = "product_001"; //TODO 這裏須要本身實例化不一樣redis實例的redisson客戶端鏈接,這裏只是僞代碼用一個redisson客戶端簡化了 RLock rLock1 = redisson.getLock(lockKey); RLock rLock2 = redisson.getLock(lockKey); RLock rLock3 = redisson.getLock(lockKey); // 向3個redis實例嘗試加鎖 RedissonRedLock redLock = new RedissonRedLock(rLock1, rLock2, rLock3); boolean isLock; try { // 500ms拿不到鎖, 就認爲獲取鎖失敗。10000ms即10s是鎖失效時間。 isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS); System.out.println("isLock = " + isLock); if (isLock) { //業務邏輯處理 ... } } catch (Exception e) { } finally { // 不管如何, 最後都要解鎖 redLock.unlock(); } }
具體使用存在爭議,不太推薦使用。若是考慮高可用併發推薦使用Redisson,考慮一致性推薦使用zookeeper。異步
因爲Redisson實際上就是將並行的請求,轉化爲串行請求。這樣就下降了併發的響應速度,爲了解決這一問題,能夠將鎖進行分段處理:例如秒殺商品001,本來存在1000個商品,能夠將其分爲20段,爲每段分配50個商品...分佈式