分佈式鎖解決方案

分佈式鎖一般有三種實現方式:

  1. 數據庫樂觀鎖;
  2. 基於Redis的分佈式鎖;
  3. 基於ZooKeeper的分佈式鎖

基於zookeeper瞬時有序節點實現的分佈式鎖,其主要邏輯如下。大致思想即爲:每個客戶端對某個功能加鎖時,在zookeeper上的與該功能對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。

鎖安全性高,zk可持久化
基於Redis的分佈式鎖

使用常用命令
SETNX
SETNX key val當且僅當key不存在時,set一個key爲val的字符串,返回1;若key存在,則什麼都不做,返回0。
Expire
expire key timeout
爲key設置一個超時時間,單位爲second,超過這個時間鎖會自動釋放,避免死鎖。
Delete

delete key
刪除key

在使用Redis實現分佈式鎖的時候,主要就會使用到這三個命令。

實現原理
使用的是jedis來連接Redis。

實現思路
1.獲取鎖的時候,使用setnx加鎖,並使用expire命令爲鎖添加一個超時時間,超過該時間則自動釋放鎖,鎖的value值爲一個隨機生成的UUID,通過此在釋放鎖的時候進行判斷。
2.獲取鎖的時候還設置一個獲取的超時時間,若超過這個時間則放棄獲取鎖。
3.釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。

核心代碼
Maven依賴信息

<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
		<version>2.9.0</version>
	</dependency>

LockRedis

public class LockRedis {

private JedisPool jedisPool;

public LockRedis(JedisPool jedisPool) {
	this.jedisPool = jedisPool;
}

/**
 * redis 上鎖方法	 * 
 * @param lockKey
 *            鎖的key<br>
 * @param acquireTimeout
 *            在沒有上鎖之前,獲取鎖的超時時間<br>
 * @param timeOut
 *            上鎖成功後,鎖的超時時間<br>
 * @return
 */
public String lockWithTimeout(String lockKey, Long acquireTimeout, Long timeOut) {
	Jedis conn = null;
	String retIdentifierValue = null;
	try {
		// 1.建立redis連接
		conn = jedisPool.getResource();
		// 2.隨機生成一個value
		String identifierValue = UUID.randomUUID().toString();
		// 3.定義鎖的名稱
		String lockName = "redis_lock" + lockKey;
		// 4.定義上鎖成功之後,鎖的超時時間
		int expireLock = (int) (timeOut / 1000);
		// 5.定義在沒有獲取鎖之前,鎖的超時時間
		Long endTime = System.currentTimeMillis() + acquireTimeout;
		while (System.currentTimeMillis() < endTime) {
			// 6.使用setnx方法設置鎖值
			if (conn.setnx(lockName, identifierValue) == 1) {
				// 7.判斷返回結果如果爲1,則可以成功獲取鎖,並且設置鎖的超時時間
				conn.expire(lockName, expireLock);
				retIdentifierValue = identifierValue;
				return retIdentifierValue;
			}
			// 8.否則情況下繼續循環等待
		}

	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		if (conn != null) {
			conn.close();
		}
	}
	return retIdentifierValue;
}

/**
 * 釋放鎖
 * 
 * @return
 */
public boolean releaseLock(String lockKey, String identifier) {

	Jedis conn = null;
	boolean flag = false;
	try {

		// 1.建立redis連接
		conn = jedisPool.getResource();
		// 2.定義鎖的名稱
		String lockName = "redis_lock" + lockKey;
		// 3.如果value與redis中一直直接刪除,否則等待超時
		if (identifier.equals(conn.get(lockName))) {
			conn.del(lockName);
			System.out.println(identifier + "解鎖成功......");
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		if (conn != null) {
			conn.close();
		}
	}
	return flag;
	}
}

LockService:

public class LockService {
private static JedisPool pool = null;

static {
	JedisPoolConfig config = new JedisPoolConfig();
	// 設置最大連接數
	config.setMaxTotal(200);
	// 設置最大空閒數
	config.setMaxIdle(8);
	// 設置最大等待時間
	config.setMaxWaitMillis(1000 * 100);
	// 在borrow一個jedis實例時,是否需要驗證,若爲true,則所有jedis實例均是可用的
	config.setTestOnBorrow(true);
	pool = new JedisPool(config, "39.107.69.43", 6379, 3000);
}

LockRedis lockRedis = new LockRedis(pool);

public void seckill() {
	String identifier = lockRedis.lockWithTimeout("test", 5000l, 5000l);
	if (StringUtils.isEmpty(identifier)) {
		// 獲取鎖失敗
		System.out.println(Thread.currentThread().getName() + ",獲取鎖失敗,原因時間超時!!!");
		return;
	}
	System.out.println(Thread.currentThread().getName() + "獲取鎖成功,鎖id identifier:" + identifier + ",執行業務邏輯");
	try {
		Thread.sleep(30);
	} catch (Exception e) {

	}
	// 釋放鎖
	boolean releaseLock = lockRedis.releaseLock("test", identifier);
	if (releaseLock) {
		System.out.println(Thread.currentThread().getName() + "釋放鎖成功,鎖id identifier:" + identifier);
		}
	}
}

class ThreadRedis extends Thread {
private LockService lockService;

public ThreadRedis(LockService lockService) {
	this.lockService = lockService;
}

@Override
public void run() {
	lockService.seckill();

	}

}

Test001:

public class Test001 {

public static void main(String[] args) {
	LockService lockService = new LockService();
	for (int i = 0; i < 50; i++) {
		ThreadRedis threadRedis = new ThreadRedis(lockService);
		threadRedis.start();
		}
	}

}

在分佈式環境中,對資源進行上鎖有時候是很重要的,比如搶購某一資源,這時候使用分佈式鎖就可以很好地控制資源。
當然,在具體使用中,還需要考慮很多因素,比如超時時間的選取,獲取鎖時間的選取對併發量都有很大的影響,上述實現的分佈式鎖也只是一種簡單的實現,主要是一種思想。

三種分佈式對比

上面幾種方式,哪種方式都無法做到完美。就像CAP一樣,在複雜性、可靠性、性能等方面無法同時滿足,所以,根據不同的應用場景選擇最適合自己的纔是王道。

從理解的難易程度角度(從低到高)
數據庫 > 緩存 > Zookeeper

從實現的複雜性角度(從低到高)
Zookeeper >= 緩存 > 數據庫

從性能角度(從高到低)
緩存 > Zookeeper >= 數據庫

從可靠性角度(從高到低)
Zookeeper > 緩存 > 數據庫

Redis實現分佈式鎖與Zookeeper實現分佈式鎖區別

使用redis實現分佈式鎖

redis中的set nx 命令,當key不存在時,才能在redis中將key添加成功,利用該屬性可以實現分佈式鎖,並且redis對於key有失效時間,可以控制當某個客戶端加鎖成功之後掛掉,導致阻塞的問題。

使用Zookeeper實現分佈式鎖

多個客戶端在Zookeeper上創建一個相同的臨時節點,因爲臨時節點只能允許一個客戶端創建成功,那麼只要任意一個客戶端創建節點成功,誰就成功的獲取到鎖,當釋放鎖後,其他客戶端同樣道理在Zookeeper節點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章