分佈式鎖一般有三種實現方式:
- 數據庫樂觀鎖;
- 基於Redis的分佈式鎖;
- 基於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節點。