為了防止用戶在頁面上重復點擊或者同時發起多次請求,請求處理需要操作redis緩存,這個時候需要對並發邊界進行並發鎖控制,實現思路:
由於每個頁面發起的請求帶的token具備唯一性,可以將token作為鎖(key),當前時間作為value進行並發鎖控制,分為兩個方法:acquireLock和realeaseLock
/**嘗試獲取鎖並設置有效時間*/ 53 + public boolean acquireLock(String lock, long expired){ 54 + boolean isSuccess = false; 55 + Jedis jedis = jedisPool.getResource(); 56 + long value = System.currentTimeMillis() + expired + 1; 57 + long acquired = jedis.setnx(lock, String.valueOf(value)); 58 + if (acquired == 1) 59 + isSuccess = true; 60 + else { 61 + long oldValue = Long.valueOf(jedis.get(lock)); 62 + //如果其他資源之前獲得鎖已經超時 63 + if (oldValue < System.currentTimeMillis()){ 64 + String getValue = jedis.getSet(lock, String.valueOf(value)); 65 + if (Long.valueOf(getValue) == oldValue) 66 + isSuccess = true; 67 + else 68 + isSuccess = false; 69 + } 70 + else isSuccess = false; 71 + } 72 + jedisPool.returnResource(jedis); 73 + return isSuccess; 74 + } 75 + 76 + /**釋放鎖資源*/ 77 + public void releaseLock(String lock){ 78 + Jedis jedis = jedisPool.getResource(); 79 + long currentTime = System.currentTimeMillis(); 80 + if (currentTime < Long.valueOf(jedis.get(lock))){ 81 + jedis.del(lock); 82 + } 83 + jedisPool.returnResource(jedis); 84 + }
經過代碼review,對照開源的jedisLock源碼,發現以上實現邏輯問題在於,對於jedisPool.getResource如果發生異常,沒有對異常進行處理,在外面包裝類加上如下處理:
Object runTask(Callback callback) { Jedis jedis = null; boolean broken = false; try { jedis = jedisPool.getResource(); return callback.onTask(jedis); } catch (JedisException e) { broken = handleJedisException(e); } catch (Exception e) { log.error("Redis runTask error: ", e); } finally { closeResource(jedis, broken); jedis = null; } return null; }
private boolean handleJedisException(JedisException jedisException) { if (jedisException instanceof JedisConnectionException) { log.error("Redis connection lost.", jedisException); } else if (jedisException instanceof JedisDataException) { if ((jedisException.getMessage() != null) && (jedisException.getMessage().indexOf("READONLY") != -1)) { log.error("Redis connection are read-only slave.", jedisException); } else { // dataException, isBroken=false return false; } } else { log.error("Jedis exception happen.", jedisException); } return true; }
private void closeResource(Jedis jedis, boolean conectionBroken) { try { if (conectionBroken) { jedisPool.returnBrokenResource(jedis); } else { jedisPool.returnResource(jedis); } } catch (Exception e) { log.error("return back jedis failed, will fore close the jedis.", e); jedis.close(); } }