欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

帶你輕松掌握Redis分布式鎖

 更新時間:2021年11月09日 11:20:48   作者:布道  
這篇文章主要介紹了帶你輕松掌握Redis分布式鎖,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

目前很多大型網站及應用都是分布式部署的,分布式場景中的數(shù)據一致性問題一直是一個比較重要的話題。

基于 CAP理論,任何一個分布式系統(tǒng)都無法同時滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯性(Partition tolerance),最多只能同時滿足兩項。

我們?yōu)榱吮WC數(shù)據的最終一致性,需要很多的技術方案來支持,比如分布式事務、分布式鎖等。通常大家都會采redis做分布式鎖,但這樣就可以高枕無憂了嗎?

1. 什么是分布式鎖

分布式與單機情況下最大的不同在于其不是多線程而是多進程,而數(shù)據只有一份(或有限制),也就是說單機的共享內存已解決不了一致性寫問題,此時需要利用鎖的技術控制某一時刻修改數(shù)據的進程數(shù)。

當在分布式模型下,分布式鎖還是可以將標記存在內存,只是該內存不是某個進程分配的內存而是公共內存(Redis、Memcache)。至于利用數(shù)據庫、文件等做鎖與單機的實現(xiàn)是一樣的,只要保證標記能互斥就行。

2. 分布式鎖該具備的特性

  • 最好是可重入鎖(避免死鎖)
  • 最好是一把阻塞鎖(根據業(yè)務需求決定)
  • 最好是一把公平鎖(根據業(yè)務需求決定)
  • 有高可用、高性能的獲取鎖和釋放鎖功能

3. 基于數(shù)據庫做分布式鎖

  • 基于樂觀鎖,CAS,但如果是insert的情況采用主鍵沖突防重,在大并發(fā)情況下有可能會造成鎖表現(xiàn)象
  • 基于悲觀鎖,也就是排他鎖,會有各種各樣的問題(操作數(shù)據庫需要一定的開銷,使用數(shù)據庫的行級鎖并不一定靠譜,性能不靠譜)

如果按分布式該具備的特性來逐條匹配,特別是高可用(存在單點)、高性能是硬傷

4. 基于Redis做分布式鎖

一般都使用 setnx(set if not exists) 指令,只允許被一個客戶端占有,先來先得, 用完后再通過 del 指令釋放。

如果中間邏輯執(zhí)行時發(fā)生異常,可能會導致 del 指令沒有被執(zhí)行,這樣就會陷入死鎖,怎么破?

對,給鎖加個過期時間(即使出現(xiàn)異常也可以保證幾秒之后鎖會自動釋放)!

但setnx 和 expire 之間redis服務器突然掛掉,怎么破?

其實該問題的根源就在于 setnx 和 expire 是兩條指令而不是原子指令。為了解決這個疑難,Redis 開源社區(qū)涌現(xiàn)了一堆分布式鎖的 解決方案。為了治理這個亂象,Redis 2.8 版本中加入了 set 指令的擴展參數(shù),使得 setnx 和 expire 指令可以一起執(zhí)行,徹底解決了分布式鎖的亂象。

總之,setnx 和 expire 組合就是分布式鎖的奧義所在。

4.1 超時問題

如果在加鎖和釋放鎖之間的邏輯執(zhí)行的太長,超出了超時限制,怎么破?

也就是說第一個線程持有的鎖過期了但臨界區(qū)的邏輯還沒有執(zhí)行完,這個時候第二個線程就提前重新持有了這把鎖,導致每個請求執(zhí)行臨界區(qū)代碼時不能嚴格的串行執(zhí)行。

Redis 的分布式鎖不能解決超時問題,建議分布式鎖不要用于較長時間的任務。

稍微安全一點的方案是為 set 指令的 value 參數(shù)設置為一個隨機數(shù),釋放鎖時先匹配隨機數(shù)是否一致,一致的話再刪除 key,這是可以確保當前線程占有的鎖不會被其它線程釋放,但是并不能解決鎖被redis服務器自動釋放的。

int tag = random.nextint()//隨機數(shù)
boolean nx=true;
int ex=5;
if(redis.set(key, tag, nx, ex)){
    do_something()
    redis.delifequals(key, tag)//不存在這樣的命令
}

但是匹配 value 和刪除 key 不是一個原子操作,怎么破?

需要使用 Lua 腳本來處理了,因為 Lua 腳本可以保證連續(xù)多個指令的原子性執(zhí)行。

#delifequals.lua文件,下面的是社區(qū)熱門代碼
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end
//java調用
public void delifequals(){
    String script = readScript("delifequals.lua");
    int tag = 5;
    String key = "key";
    Object eval = jedis.eval(script, Lists.newArrayList(key), Lists.newArrayList(tag));
    System.out.println(eval);
}

4.2 可重入鎖

redis有類似Java 語言里有個 ReentrantLock 就是可重入鎖嗎?

要支持可重入,需要對jedis 的 set 方法進行包裝,思路是:使用 Threadlocal 存儲當前持有鎖的計數(shù)??芍厝腈i加重了客戶端的復雜性,精確一點還需要考慮內存鎖計數(shù)的過期時間,代碼復雜度將會繼續(xù)升高。

public class JedisWithReentrantLock {
    private Jedis jedis;
    /**
     * 當前線程的鎖及計數(shù)
     */
    private ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>();
    public JedisWithReentrantLock(Jedis jedis) {
        this.jedis = jedis;
    }
    private boolean set(String key) {
        return jedis.set(key, "", "nx", "ex", 5L) != null;
    }
    private void del(String key) {
        jedis.del(key);
    }
    private Map<String, Integer> getLockers() {
        Map<String, Integer> refs = lockers.get();
        if (refs != null) {
            return refs;
        }
        lockers.set(Maps.newHashMap());
        return lockers.get();
    }
 
    public boolean lock(String key) {
        Map<String, Integer> refs = getLockers();
        Integer refCount = refs.get(key);
        if (refCount != null) {
            refs.put(key, refCount + 1);
            return true;
        }
        if (!this.set(key)) {
            return false;
        }
        refs.put(key, 1);
        return true;
    }
 
    public boolean unlock(String key) {
        Map<String, Integer> refs = getLockers();
        Integer refCount = refs.get(key);
        if (refCount == null) {
            return false;
        }
        refCount -= 1;
        if (refCount > 0) {
            refs.put(key, refCount);
        } else {
            refs.remove(key);
            this.del(key);
        }
        return true;
    }
}
    @Test
    public void runJedisWithReentrantLock() {
        JedisWithReentrantLock redis = new JedisWithReentrantLock(jedis);
        System.out.println(redis.lock("alex"));
        System.out.println(redis.lock("alex"));
        System.out.println(redis.unlock("alex"));
        System.out.println(redis.unlock("alex"));
    }

4.3 集群環(huán)境的缺陷

在集群環(huán)境下,這種方式是有缺陷的(數(shù)據不一致的情況)。比如在 Sentinel 集群中,主節(jié)點掛掉時(原先第一個客戶端在主節(jié)點中申請成功了一把鎖),從節(jié)點A 會取而代之并晉升為主(但是這把鎖還沒有來得及同步),雖然客戶端上卻并沒有明顯感知,但是這時另一個客戶端過來請求 從節(jié)點A 可以成功加鎖,這樣就會導致系統(tǒng)中同樣一把鎖被兩個客戶端同時持有。

主從發(fā)生故障轉移,一般持續(xù)時間極短,數(shù)據不一致的情況基本上都是小概率事件。

4.4 Redlock

上面的集群同步問題導致的缺陷,難道就沒有解決方案嗎?

為此Antirez 發(fā)明了 Redlock 算法,它的流程比較復雜,不過已經有了很多開源的實現(xiàn)。

原理

使用 Redlock,需要提供多個 Redis 實例,這些實例之前相互獨立沒有主從關系。同很多分布式算法一樣,redlock 也使用少數(shù)服從多數(shù)。

加鎖時,它會向過半節(jié)點發(fā)送 set(key, value, nx, ex) 指令,只要過半節(jié)點 set 成功,那就認為加鎖成功。釋放鎖時,需要向所有節(jié)點發(fā)送 del 指令。缺陷:因為 Redlock 需要向多個節(jié)點進行讀寫,意味著相比單實例 Redis 性能會下降一些。

注:Redlock算法還需要考慮出錯重試、時鐘漂移等很多細節(jié)問題

使用場景

如果你很在乎高可用性,希望掛了一臺 redis 完全不受影響,那就應該考慮 redlock。

引用資料

How to do distributed locking

Redlock的實現(xiàn)

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • 詳解Redis中Lua腳本的應用和實踐

    詳解Redis中Lua腳本的應用和實踐

    這篇文章主要介紹了詳解Redis中Lua腳本的應用和實踐,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • Redis為什么默認有16個數(shù)據庫問題

    Redis為什么默認有16個數(shù)據庫問題

    這篇文章主要介紹了Redis為什么默認有16個數(shù)據庫問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 深入解析Redis的LRU與LFU算法實現(xiàn)

    深入解析Redis的LRU與LFU算法實現(xiàn)

    這篇文章主要重點介紹了Redis的LRU與LFU算法實現(xiàn),并分析總結了兩種算法的實現(xiàn)效果以及存在的問題,并闡述其優(yōu)劣特性,感興趣的小伙伴跟著小編一起來看看吧
    2023-07-07
  • Redis實現(xiàn)高并發(fā)計數(shù)器

    Redis實現(xiàn)高并發(fā)計數(shù)器

    這篇文章主要為大家詳細介紹了Redis實現(xiàn)高并發(fā)計數(shù)器,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • redis實現(xiàn)好友關注&消息推送的方法示例

    redis實現(xiàn)好友關注&消息推送的方法示例

    Redis作為一款開源的內存數(shù)據庫,具有可靠性、速度快、易用性等優(yōu)點,已經被廣泛應用于開發(fā)實際項目中,本文主要介紹了redis實現(xiàn)好友關注&消息推送的方法示例,感興趣的可以了解一下
    2023-10-10
  • Redis Cluster的圖文講解

    Redis Cluster的圖文講解

    今天小編就為大家分享一篇關于Redis Cluster的圖文講解,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • 如何高效地向Redis插入大量的數(shù)據(推薦)

    如何高效地向Redis插入大量的數(shù)據(推薦)

    本篇文章主要介紹了如何高效地向Redis插入大量的數(shù)據,現(xiàn)在分享給大家,感興趣的小伙伴們可以參考一下。
    2016-11-11
  • 銀河麒麟V10sp1服務器系統(tǒng)安裝redis不能使用的快速解決辦法

    銀河麒麟V10sp1服務器系統(tǒng)安裝redis不能使用的快速解決辦法

    這篇文章主要介紹了銀河麒麟V10sp1服務器系統(tǒng)安裝redis不能使用的快速解決辦法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • redis發(fā)布和訂閱_動力節(jié)點Java學院整理

    redis發(fā)布和訂閱_動力節(jié)點Java學院整理

    這篇文章主要為大家詳細介紹了redis發(fā)布和訂閱的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • redis批量操作pipeline管道操作方法

    redis批量操作pipeline管道操作方法

    Redis本身是基于一個Request一個Response方式的同步請求,正常情況下,客戶端發(fā)送一個命令,這篇文章主要介紹了redis批量操作pipeline管道,需要的朋友可以參考下
    2022-09-09

最新評論