1. 程式人生 > >Redis分散式鎖 實現秒殺系統 SET命令實現

Redis分散式鎖 實現秒殺系統 SET命令實現

基於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的效能更好。