Redis分散式鎖 實現秒殺系統 SET命令實現
阿新 • • 發佈:2018-12-12
基於Redis命令:SET key valueNX EX max-lock-time
可適用於redis單機和redis叢集模式
1.SET命令是原子性操作,NX指令保證只要當key不存在時才會設定value
2.設定的value要有唯一性,來確保鎖不會被誤刪(value=系統時間戳+UUID)
3.當上述命令執行返回OK時,客戶端獲取鎖成功,否則失敗
3.客戶端可以通過redis釋放指令碼來釋放鎖
4.如果鎖到達了最大生存時間將會自動釋放
只有當前key的value和傳入的value相同才會執行DEL命令
import java.util.Collections; import java.util.UUID; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.exceptions.JedisException; /** * * @author rencai.tang * */ public class DistributedLock { private static final String LOCK_SUCCESS = "OK"; private static final Long RELEASE_SUCCESS = 1L; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; private final JedisPool jedisPool; public DistributedLock(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * 加鎖 * @param locaName 鎖的key * @param acquireTimeout 獲取超時時間 * @param timeout 鎖的超時時間 * @return 鎖標識 */ public String lockWithTimeout(String locaName, long acquireTimeout, long timeout) { Jedis jedis = null; String retIdentifier = null; try { // 獲取連線 jedis = jedisPool.getResource(); // 隨機生成一個value String identifier = UUID.randomUUID().toString(); // 鎖名,即key值 String lockKey = "lock:" + locaName; // 超時時間,上鎖後超過此時間則自動釋放鎖 int lockExpire = (int)(timeout / 1000); // 獲取鎖的超時時間,超過這個時間則放棄獲取鎖 long end = System.currentTimeMillis() + acquireTimeout; while (System.currentTimeMillis() < end) {//重試機制 /*第一個為key,我們使用key來當鎖,因為key是唯一的。 第二個為value,我們傳的是requestId,很多童鞋可能不明白,有key作為鎖不就夠了嗎,為什麼還要用到value?原因就是我們在上面講到可靠性時,分散式鎖要滿足加鎖和解鎖必須是同一個客戶端,通過給value賦值為requestId,我們就知道這把鎖是哪個請求加的了,在解鎖的時候就可以有依據。requestId可以使用UUID.randomUUID().toString()方法生成。 第三個為nxxx,這個引數我們填的是NX,意思是SET IF NOT EXIST,即當key不存在時,我們進行set操作;若key已經存在,則不做任何操作; 第四個為expx,這個引數我們傳的是PX,意思是我們要給這個key加一個過期的設定,具體時間由第五個引數決定。 第五個為time,與第四個引數相呼應,代表key的過期時間。*/ String result = jedis.set(lockKey, identifier, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, lockExpire); if (LOCK_SUCCESS.equals(result)) { retIdentifier = identifier; return retIdentifier; } try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (JedisException e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return retIdentifier; } /** * 釋放鎖 * @param lockName 鎖的key * @param identifier 釋放鎖的標識 * @return */ public boolean releaseLock(String lockName, String identifier) { Jedis jedis = null; String lockKey = "lock:" + lockName; try { jedis = jedisPool.getResource(); /* * 確保釋放鎖的過程是原子性的,使用lua程式碼 */ String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(identifier)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } catch (JedisException e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return false; } }
在分散式環境中,對資源進行上鎖有時候是很重要的,比如搶購某一資源,訂單防重,這時候使用分散式鎖就可以很好地控制資源。 當然,在具體使用中,還需要考慮很多因素,比如超時時間的選取,獲取鎖時間的選取對併發量都有很大的影響,上述實現的分散式鎖也只是一種簡單的實現,主要是一種思想。
下一次我會使用zookeeper實現分散式鎖,使用zookeeper的可靠性是要大於使用redis實現的分散式鎖的,但是相比而言,redis的效能更好。