天天看點

詳細解讀雲資料庫Redis高性能分布式鎖

雲栖号快速入門: 【點選檢視更多雲産品快速入門】 不知道怎麼入門?這裡分分鐘解決新手入門等基礎問題,可快速完成産品配置操作!

分布式鎖是大型應用中最常見的功能之一,基于Redis實作分布式鎖的方式有很多。本章節先介紹并分析常見的分布式鎖實作方式,之後結合阿裡巴巴集團在使用雲資料庫Redis企業版和分布式鎖方面的業務經驗,介紹使用Redis企業版實作高性能分布式鎖的實踐方案。

分布式鎖及其應用場景

應用開發時,如果需要在同程序内的不同線程并發通路某項資源,可以使用各種互斥鎖、讀寫鎖;如果一台主機上的多個程序需要并發通路某項資源,則可以使用程序間同步的原語,例如信号量、管道、共享記憶體等。但如果多台主機需要同時通路某項資源,就需要使用一種在全局可見并具有互斥性的鎖了。這種鎖就是分布式鎖,可以在分布式場景中對資源加鎖,避免競争資源引起的邏輯錯誤。

詳細解讀雲資料庫Redis高性能分布式鎖

分布式鎖的特性

  • 互斥性

    在任意時刻,隻有一個用戶端持有鎖。

  • 不死鎖

    分布式鎖本質上是一個基于租約(Lease)的租借鎖,如果用戶端獲得鎖後自身出現異常,鎖能夠在一段時間後自動釋放,資源不會被鎖死。

  • 一緻性

    硬體故障或網絡異常等外部問題,以及慢查詢、自身缺陷等内部因素都可能導緻Redis發生高可用切換,replica提升為新的master。此時,如果業務對互斥性的要求非常高,鎖需要在切換到新的master後保持原狀态。

使用原生Redis實作分布式鎖

詳細解讀雲資料庫Redis高性能分布式鎖
  • 加鎖

    在Redis中加鎖非常簡便,直接使用SET指令即可。示例及關鍵選項說明如下:

SET resource_1 random_value NX EX 5           

表 1. 關鍵選項說明

詳細解讀雲資料庫Redis高性能分布式鎖

示例代碼為resource_1這個key設定了5秒的過期時間,如果用戶端不釋放這個key,5秒後key将過期,鎖就會被系統回收,此時其它用戶端就能夠再次為資源加鎖并通路資源了。

  • 解鎖

    解鎖一般使用DEL指令,但可能存在下列問題。

詳細解讀雲資料庫Redis高性能分布式鎖

(1).t1時刻,App1設定了分布式鎖resource_1,過期時間為3秒。

(2).App1由于程式慢等原因等待超過了3秒,而resource_1已經在t2時刻被釋放。

(3).t3時刻,App2獲得這個分布式鎖。

(4).App1從等待中恢複,在t4時刻運作

詳細解讀雲資料庫Redis高性能分布式鎖

将App2持有的分布式鎖釋放了。

從上述過程可以看出,一個用戶端設定的鎖,必須由自己解開。是以用戶端需要先使用GET指令确認鎖是不是自己設定的,然後再使用DEL解鎖。在Redis中通常需要用Lua腳本來實作自鎖自解:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end           
  • 續租

    當用戶端發現在鎖的租期内無法完成操作時,就需要延長鎖的持有時間,進行續租(renew)。同解鎖一樣,用戶端應該隻能續租自己持有的鎖。在Redis中可使用如下Lua腳本來實作續租:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("expire",KEYS[1], ARGV[2])
else
    return 0
end           

使用Redis企業版實作分布式鎖

使用Redis企業版性能增強型執行個體的String增強指令,無需Lua即可實作分布式鎖。

  • 加鎖方式與原生Redis相同,使用SET指令:
SET resource_1 random_value NX EX 5           
  • 直接使用Redis企業版的 CAD 指令即可實作優雅而高效的解鎖:
/* if (GET(resource_1) == my_random_value) DEL(resource_1) */
CAD resource_1 my_random_value           
  • 續租可以直接使用 CAS 指令實作:
CAS resource_1 my_random_value my_random_value EX 10           
詳細解讀雲資料庫Redis高性能分布式鎖

基于Jedis的示例代碼

  • 定義CAS/CAD指令
enum TairCommand implements ProtocolCommand {
    CAD("CAD"), CAS("CAS");

    private final byte[] raw;

    TairCommand(String alt) {
      raw = SafeEncoder.encode(alt);
    }

    @Override
    public byte[] getRaw() {
      return raw;
    }
}           
public boolean acquireDistributedLock(Jedis jedis,String resourceKey, String randomValue, int expireTime) {
    SetParams setParams = new SetParams();
    setParams.nx().ex(expireTime);
    String result = jedis.set(resourceKey,randomValue,setParams);
    return "OK".equals(result);
}           
public boolean releaseDistributedLock(Jedis jedis,String resourceKey, String randomValue) {
    jedis.getClient().sendCommand(TairCommand.CAD,resourceKey,randomValue);
    Long ret = jedis.getClient().getIntegerReply();
    return 1 == ret;
}           
public boolean renewDistributedLock(Jedis jedis,String resourceKey, String randomValue, int expireTime) {
    jedis.getClient().sendCommand(TairCommand.CAS,resourceKey,randomValue,randomValue,"EX",String.valueOf(expireTime));
    Long ret = jedis.getClient().getIntegerReply();
    return 1 == ret;
}           

如何保障一緻性

Redis的主從同步(replication)是異步進行的,如果向master發送請求修改了資料後master突然出現異常,發生高可用切換,緩沖區的資料可能無法同步到新的master(原replica)上,導緻資料不一緻。如果丢失的資料跟分布式鎖有關,則會導緻鎖的機制出現問題,進而引起業務異常。下文介紹三種保障一緻性的方法。

  • 使用 紅鎖(RedLock) 紅鎖是Redis作者提出的一緻性解決方案。紅鎖的本質是一個機率問題:如果一個主從架構的Redis在高可用切換期間丢失鎖的機率是k%,那麼互相獨立的N個Redis同時丢失鎖的機率是多少?如果用紅鎖來實作分布式鎖,那麼丢鎖的機率是(1-k%)^N。鑒于Redis極高的穩定性,此時的機率已經完全能滿足産品的需求。
詳細解讀雲資料庫Redis高性能分布式鎖

紅鎖的問題在于:

(1).加鎖和解鎖的延遲較大。

(2).難以在叢集版或者标準版(主從架構)的Redis執行個體中實作。

(3).占用的資源過多,為了實作紅鎖,需要建立多個互不相關的雲Redis執行個體或者自建Redis。

  • WAIT

    指令。

    Redis的WAIT指令會阻塞目前用戶端,直到這條指令之前的所有寫入指令都成功從master同步到指定數量的replica,指令中可以設定機關為毫秒的等待逾時時間。在雲Redis版中使用WAIT指令提高分布式鎖一緻性的示例如下:

SET resource_1 random_value NX EX 5
WAIT 1 5000           

使用以上代碼,用戶端在加鎖後會等待資料成功同步到replica才繼續進行其它操作,最大等待時間為5000毫秒。執行WAIT指令後如果傳回結果是1則表示同步成功,無需擔心資料不一緻。相比紅鎖,這種實作方法極大地降低了成本。

需要注意的是:

(1).WAIT隻會阻塞發送它的用戶端,不影響其它用戶端。

(2).WAIT傳回正确的值表示設定的鎖成功同步到了replica,但如果在正常傳回前發生高可用切換,資料還是可能丢失,此時WAIT隻能用來提示同步可能失敗,無法保證資料不丢失。您可以在WAIT傳回異常值後重新加鎖或者進行資料校驗。

(3).解鎖不一定需要使用WAIT,因為鎖隻要存在就能保持互斥,延遲删除不會導緻邏輯問題。

  • 使用阿裡雲資料庫Redis企業版

在不考慮組合方案的情況下:

(1).使用紅鎖最大優勢是Redis節點越多則一緻性越強。

(2).使用WAIT指令最大優勢是實作成本低。

如果使用阿裡雲資料庫Redis企業版:

(1).其特有的高可用HA和資料持久化機制能夠有效保護資料安全、確定服務的穩定性,不使用多Redis節點或WAIT指令也能提供較高的一緻性。

(2).性能增強型執行個體的CAS/CAD指令可以極大降低分布式鎖的開發和管理成本,提升鎖的性能。

(3).性能增強型執行個體的

多線程性能增強

特性使其能夠提供三倍于原生Redis的性能,即使是大并發的分布式鎖也不會影響正常的Redis服務。

本文來自 阿裡雲文檔中心

雲資料庫Redis 高性能分布式鎖

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/zhibo

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK