" />

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

Redis解決緩存一致性問(wèn)題

 更新時(shí)間:2023年10月26日 09:02:40   作者:紫電清霜  
本文主要介紹了Redis?解決緩存一致性問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

背景

這是我校招剛?cè)肼?Shopee 時(shí)遇到的一個(gè)問(wèn)題。Shopee 私有云上 WAF 給內(nèi)部用戶提供了設(shè)置 IP 黑白名單規(guī)則的能力,所有規(guī)則存儲(chǔ)在 MySQL 中。我校招剛?cè)肼殨r(shí)從已離職前輩的手中接過(guò)了這套系統(tǒng)。但很快發(fā)現(xiàn)每次修改規(guī)則后的 5min 內(nèi)讀到的數(shù)據(jù)不穩(wěn)定——新規(guī)則時(shí)而查得到,時(shí)而查不到,也經(jīng)常有用戶反饋這個(gè)問(wèn)題。排查發(fā)現(xiàn)原因是服務(wù)代碼中使用了內(nèi)存緩存,而這個(gè)服務(wù)部署了兩個(gè)實(shí)例,實(shí)例之間沒(méi)有同步寫(xiě)請(qǐng)求。如果寫(xiě)后讀的讀寫(xiě)請(qǐng)求被路由到不同的實(shí)例上,就無(wú)法讀到最新數(shù)據(jù)。而內(nèi)存緩存的過(guò)期時(shí)間被設(shè)置為 5min。

查了下這個(gè)服務(wù)的運(yùn)維記錄,在我入職之前做過(guò)一次擴(kuò)容,從單實(shí)例擴(kuò)容到雙實(shí)例。之前的研發(fā)同事維護(hù) WAF 時(shí)一直是單實(shí)例運(yùn)行,所以沒(méi)出過(guò)問(wèn)題。后來(lái)他離職了,別的同事擴(kuò)容時(shí)可能也沒(méi)意識(shí)到會(huì)造成不一致的問(wèn)題。于是問(wèn)題就到了我這兒。

引入 Redis

我首先想到的解決辦法是把內(nèi)存緩存換成了 Redis,但上線灰度階段 Redis 帶寬被打滿,排查發(fā)現(xiàn)是因?yàn)橛行┮?guī)則的封禁 IP 列表很長(zhǎng),導(dǎo)致傳輸數(shù)據(jù)量非常大。

最終方案

由于 WAF 規(guī)則讀多寫(xiě)少,絕大多數(shù)時(shí)候從 Redis 讀到的數(shù)據(jù)不會(huì)有變化。有經(jīng)驗(yàn)的老同事建議用 Redis 維護(hù)版本號(hào),規(guī)則數(shù)據(jù)仍然存在內(nèi)存緩存中。經(jīng)過(guò)反復(fù)推敲,最終的設(shè)計(jì)的架構(gòu)如下。

讀寫(xiě)邏輯:

  • 寫(xiě)操作比較簡(jiǎn)單,使用當(dāng)前微秒時(shí)間戳作為新的版本號(hào),做如下四件事:寫(xiě) DB,更新 redis 版本號(hào),更新本地內(nèi)存緩存中的數(shù)據(jù)和版本號(hào),四件事的順序可交換
  • 讀操作稍微復(fù)雜一點(diǎn):先讀 redis 中的版本號(hào),如果本地版本號(hào)沒(méi)有過(guò)期(絕大多數(shù)情況)就直接從本地內(nèi)存緩存中讀數(shù)據(jù)。對(duì)于 redis 與內(nèi)存中版本號(hào)不一致和 redis 沒(méi)讀到(expired)的情況要單獨(dú)處理,處理邏輯如偽代碼所示
  • 如果一微秒內(nèi)有多個(gè)寫(xiě)請(qǐng)求,仍然可能出現(xiàn)不一致。不過(guò) Shopee WAF 的實(shí)際使用場(chǎng)景不太會(huì)有如此頻繁的更新,所以我就沒(méi)做處理了。不過(guò)時(shí)間戳在這里只用來(lái)判等,不會(huì)比較大小,因此可以用任何一種分布式唯一 ID 解決方案替換時(shí)間戳
  • 版本號(hào)不對(duì)用戶暴露,事實(shí)上同一版本號(hào)可能會(huì)讀到不同的規(guī)則數(shù)據(jù),但這并不會(huì)破壞最終一致性
func Set(key, data) {
    newVer := time()
    localCacheVer.Set(newVer)
    localCacheData.Set(data)

    WriteMySQL(key, data)
    redis.Set(key, newVer, exprire=5min)
}

func Read(key) Data {
    ver := redis.Get(key)
    if ver != nil {
        if localCacheVer.Load() == ver {
            // Local cache is up-to-date, just use it
            return localCacheData.Load()
        }
    } else {    // This version has expired
        ver := time()
        res := redis.SetNX(key, ver, expire=5min)
        if res == false {
            // Another instance has proceded, use that version
            ver = redis.Get(key)
        }
    }
    
    data := ReadFromMySQL(key)
    localCacheVer.Store(ver)
    localCacheData.Store(data)
    return data
}

TLA+ 形式化驗(yàn)證

恰好當(dāng)時(shí)自學(xué)了 TLA+,順手寫(xiě)了下這個(gè)設(shè)計(jì)對(duì)應(yīng)的 TLA+ 公式,果然成功通過(guò)了最終一致性的驗(yàn)證。寫(xiě)這篇總結(jié)的時(shí)候感覺(jué)應(yīng)該是線性一致的,但沒(méi)有驗(yàn)證。
最開(kāi)始的持續(xù) 5min 的接口返回?cái)?shù)據(jù)不一致問(wèn)題成功得到了解決。

// ================ tla file ================

---- MODULE waf ----
EXTENDS Integers, TLC

VARIABLE redisVer, localVer, pc, threadVer, DBData, localData, threadData
CONSTANTS DataDomain, ProcSet, r1, r2, r3, t1, t2, t3

vars == << redisVer, localVer, pc, threadVer, localData, threadData, DBData>>

Init == /\ redisVer = -1 /\ localVer = -1 /\ localData = "" /\ DBData = ""
        /\ threadVer = [self \in ProcSet |-> -1]
        /\ pc = [self \in ProcSet |-> "A"]
        /\ threadData = [self \in ProcSet |-> ""]

RedisExpire == /\ threadData = [self \in ProcSet |-> DBData]
               /\ redisVer' = -1
               /\ DBData' \in DataDomain
               /\ UNCHANGED <<localVer, threadVer, localData, threadData, pc>>

ReadRedis(self) == /\ pc[self] = "A"
                   /\ threadVer' = [threadVer EXCEPT ![self] = redisVer]
                   /\ / /\ redisVer = -1
                         /\ pc' = [pc EXCEPT ![self] = "C"]
                      / /\ redisVer # -1
                         /\ pc' = [pc EXCEPT ![self] = "F"]
                   /\ UNCHANGED <<localVer, redisVer, localData, threadData, DBData>>

SetRedis(self) == /\ pc[self] = "C"
                  /\ / /\ redisVer # -1    * SetNX failed => use existing redis
                        /\ redisVer' = redisVer
                        /\ threadVer' = [threadVer EXCEPT ![self] = redisVer] * Not strictly the same!
                     / /\ redisVer = -1    * SetNX ok => change redis
                        /\ redisVer' \in 1600012345..1600012350
                        /\ threadVer' = [threadVer EXCEPT ![self] = redisVer']
                  /\ pc' = [pc EXCEPT ![self] = "I"]
                  /\ UNCHANGED <<localVer, localData, threadData, DBData>>

CheckLocal(self) == /\ pc[self] = "F"
                    /\ / /\ localVer = threadVer[self]    * Normal case
                          /\ threadData' = [threadData EXCEPT ![self] = localData]
                          /\ pc' = [pc EXCEPT ![self] = "H"]
                       / /\ localVer # threadVer[self]
                          /\ pc' = [pc EXCEPT ![self] = "I"]
                          /\ threadData' = threadData
                    /\ UNCHANGED <<redisVer, localVer, localData, threadVer, DBData>>

SetLocal(self) == /\ pc[self] = "I"
                  /\ localVer' = threadVer[self]
                  /\ localData' = DBData
                  /\ threadData' = [threadData EXCEPT ![self] = DBData]
                  /\ pc' = [pc EXCEPT ![self] = "H"]
                  /\ UNCHANGED <<redisVer, threadVer, DBData>>

ReturnResult(self) == /\ pc[self] = "H"
                      /\ pc' = [pc EXCEPT ![self] = "Done"]
                      /\ UNCHANGED <<redisVer, localVer, threadVer, localData, threadData, DBData>>

Again(self) == /\ pc[self] = "Done"
               /\ pc' = [pc EXCEPT ![self] = "A"]
               /\ UNCHANGED <<redisVer, localVer, threadVer, localData, threadData, DBData>>

Terminating == /\ \A self \in ProcSet: pc[self] = "Done"
               /\ UNCHANGED vars

Proceed(t) == ReadRedis(t) / SetRedis(t) / CheckLocal(t) / SetLocal(t) / ReturnResult(t) / Again(t)

Next == / RedisExpire
        / \E t \in ProcSet: Proceed(t)

FairForEveryone == \A t \in ProcSet: SF_vars(Proceed(t))

Spec == /\ Init /\ [][Next]_vars /\ FairForEveryone

symm == Permutations({r1, r2, r3}) \union Permutations({t1, t2, t3})

EventualCons == \A v \in DataDomain: DBData = v ~> threadData = [t \in ProcSet |-> v]
ECSpec == Spec /\ EventualCons


// ======= cfg file ========
SPECIFICATION Spec

CONSTANTS
    DataDomain = {r1, r2}
    r1 = r1
    r2 = r2
    r3 = r3
    ProcSet = {t1, t2, t3}
    t1 = t1
    t2 = t2
    t3 = t3

SYMMETRY symm
PROPERTIES EventualCons

到此這篇關(guān)于Redis 解決緩存一致性問(wèn)題的文章就介紹到這了,更多相關(guān)Redis 緩存一致性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Linux下安裝Redis 6.0.5的實(shí)現(xiàn)

    Linux下安裝Redis 6.0.5的實(shí)現(xiàn)

    本文詳細(xì)介紹了在Linux系統(tǒng)下安裝Redis 6.0.5的步驟,包括安裝準(zhǔn)備、編譯安裝、啟動(dòng)服務(wù)、設(shè)置密碼和配置文件修改等,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-02-02
  • 解析高可用Redis服務(wù)架構(gòu)分析與搭建方案

    解析高可用Redis服務(wù)架構(gòu)分析與搭建方案

    我們按照由簡(jiǎn)至繁的步驟,搭建一個(gè)最小型的高可用的Redis服務(wù)。 本文通過(guò)四種方案給大家介紹包含每種方案的優(yōu)缺點(diǎn)及詳細(xì)解說(shuō),具體內(nèi)容詳情跟隨小編一起看看吧
    2021-06-06
  • redis使用跳躍表而不是樹(shù)的原因解析

    redis使用跳躍表而不是樹(shù)的原因解析

    Redis中支持五種數(shù)據(jù)類(lèi)型中有序集合Sorted Set的底層數(shù)據(jù)結(jié)構(gòu)使用的跳躍表,為何不使用其他的如平衡二叉樹(shù)、b+樹(shù)等數(shù)據(jù)結(jié)構(gòu)呢?這篇文章主要介紹了redis使用跳躍表而不是樹(shù)的原因解析,需要的朋友可以參考下
    2024-02-02
  • Redis內(nèi)存空間占用及避免數(shù)據(jù)丟失的方法

    Redis內(nèi)存空間占用及避免數(shù)據(jù)丟失的方法

    在現(xiàn)代的互聯(lián)網(wǎng)應(yīng)用中,Redis作為一種高性能的內(nèi)存數(shù)據(jù)庫(kù),被廣泛應(yīng)用于緩存、會(huì)話管理和消息隊(duì)列等場(chǎng)景,然而,Redis的內(nèi)存資源是有限的,過(guò)多的內(nèi)存占用可能會(huì)導(dǎo)致數(shù)據(jù)丟失所以本文將給大家介紹一下Redis內(nèi)存空間占用及避免數(shù)據(jù)丟失的方法
    2023-08-08
  • 配置redis.conf遠(yuǎn)程訪問(wèn)的操作

    配置redis.conf遠(yuǎn)程訪問(wèn)的操作

    文章詳細(xì)介紹了Redis的配置文件位置、如何編輯配置文件以實(shí)現(xiàn)遠(yuǎn)程訪問(wèn),以及驗(yàn)證和監(jiān)控Redis配置的方法,感興趣的朋友一起看看吧
    2025-02-02
  • 安裝Redis就那么幾步,很簡(jiǎn)單

    安裝Redis就那么幾步,很簡(jiǎn)單

    Redis是一種非關(guān)系型數(shù)據(jù)庫(kù)(NoSQL),NoSQL是以key-value的形式存儲(chǔ),和傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)不一樣,不一定遵循傳統(tǒng)數(shù)據(jù)庫(kù)的一些基本要求,本文重點(diǎn)給大家介紹安裝Redis的步驟,需要的朋友參考下吧
    2018-11-11
  • Redis數(shù)據(jù)一致性問(wèn)題的三種解決方案

    Redis數(shù)據(jù)一致性問(wèn)題的三種解決方案

    Redis(Remote?Dictionary?Server?),是一個(gè)高性能的基于Key-Value結(jié)構(gòu)存儲(chǔ)的NoSQL開(kāi)源數(shù)據(jù)庫(kù),大部分公司采用Redis來(lái)實(shí)現(xiàn)分布式緩存,用來(lái)提高數(shù)據(jù)查詢效率,本文就給大家介紹三種Redis數(shù)據(jù)一致性問(wèn)題的解決方案,需要的朋友可以參考下
    2023-07-07
  • redis?setex使用方法示例代碼

    redis?setex使用方法示例代碼

    SETEX?是?Redis?中的一個(gè)命令,用于設(shè)置鍵的值以及過(guò)期時(shí)間(以秒為單位),這篇文章主要介紹了redis?setex使用方法,需要的朋友可以參考下
    2024-07-07
  • 高并發(fā)下Redis如何保持?jǐn)?shù)據(jù)一致性(避免讀后寫(xiě))

    高并發(fā)下Redis如何保持?jǐn)?shù)據(jù)一致性(避免讀后寫(xiě))

    本文主要介紹了高并發(fā)下Redis如何保持?jǐn)?shù)據(jù)一致性(避免讀后寫(xiě)),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • redis執(zhí)行l(wèi)ua腳本的實(shí)現(xiàn)方法

    redis執(zhí)行l(wèi)ua腳本的實(shí)現(xiàn)方法

    redis在2.6推出了腳本功能,允許開(kāi)發(fā)者使用Lua語(yǔ)言編寫(xiě)腳本傳到redis中執(zhí)行。本文就介紹了redis執(zhí)行l(wèi)ua腳本的實(shí)現(xiàn)方法,感興趣的可以了解一下
    2021-11-11

最新評(píng)論