" />

最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis解決緩存一致性問題

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

背景

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

查了下這個服務的運維記錄,在我入職之前做過一次擴容,從單實例擴容到雙實例。之前的研發(fā)同事維護 WAF 時一直是單實例運行,所以沒出過問題。后來他離職了,別的同事擴容時可能也沒意識到會造成不一致的問題。于是問題就到了我這兒。

引入 Redis

我首先想到的解決辦法是把內存緩存換成了 Redis,但上線灰度階段 Redis 帶寬被打滿,排查發(fā)現是因為有些規(guī)則的封禁 IP 列表很長,導致傳輸數據量非常大。

最終方案

由于 WAF 規(guī)則讀多寫少,絕大多數時候從 Redis 讀到的數據不會有變化。有經驗的老同事建議用 Redis 維護版本號,規(guī)則數據仍然存在內存緩存中。經過反復推敲,最終的設計的架構如下。

讀寫邏輯:

  • 寫操作比較簡單,使用當前微秒時間戳作為新的版本號,做如下四件事:寫 DB,更新 redis 版本號,更新本地內存緩存中的數據和版本號,四件事的順序可交換
  • 讀操作稍微復雜一點:先讀 redis 中的版本號,如果本地版本號沒有過期(絕大多數情況)就直接從本地內存緩存中讀數據。對于 redis 與內存中版本號不一致和 redis 沒讀到(expired)的情況要單獨處理,處理邏輯如偽代碼所示
  • 如果一微秒內有多個寫請求,仍然可能出現不一致。不過 Shopee WAF 的實際使用場景不太會有如此頻繁的更新,所以我就沒做處理了。不過時間戳在這里只用來判等,不會比較大小,因此可以用任何一種分布式唯一 ID 解決方案替換時間戳
  • 版本號不對用戶暴露,事實上同一版本號可能會讀到不同的規(guī)則數據,但這并不會破壞最終一致性
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+ 形式化驗證

恰好當時自學了 TLA+,順手寫了下這個設計對應的 TLA+ 公式,果然成功通過了最終一致性的驗證。寫這篇總結的時候感覺應該是線性一致的,但沒有驗證。
最開始的持續(xù) 5min 的接口返回數據不一致問題成功得到了解決。

// ================ 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

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

相關文章

  • Redis中的慢日志

    Redis中的慢日志

    這篇文章主要介紹了Redis中的慢日志,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 基于Redis實現雙加密Token的示例代碼

    基于Redis實現雙加密Token的示例代碼

    在現代分布式系統(tǒng)中,Token管理是身份驗證和授權的核心部分,本文將深入分析一個基于Redis的Token管理實現,探討其設計思路、關鍵代碼邏輯以及實現細節(jié),通過對源碼的逐層剖析,幫助讀者更好地理解Token管理的實現原理,需要的朋友可以參考下
    2025-01-01
  • 批量導入txt數據到的redis過程

    批量導入txt數據到的redis過程

    用戶通過將Redis命令逐行寫入txt文件,利用管道模式運行客戶端,成功執(zhí)行批量刪除以"Product*"匹配的Key操作,提高了數據清理效率
    2025-08-08
  • Redis中序列化的兩種實現

    Redis中序列化的兩種實現

    本文主要介紹了Redis中序列化的兩種實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-07-07
  • Redis集群Lettuce主從切換問題解決方案

    Redis集群Lettuce主從切換問題解決方案

    這篇文章主要為大家介紹了Redis集群Lettuce主從切換問題解決方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • 微服務Spring Boot 整合 Redis 實現好友關注功能

    微服務Spring Boot 整合 Redis 實現好友關注功能

    這篇文章主要介紹了微服務Spring Boot 整合 Redis 實現 好友關注,本文結合示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-12-12
  • redis實現紅鎖的示例代碼

    redis實現紅鎖的示例代碼

    在分布式系統(tǒng)中,實現一個可靠的鎖機制是非常重要的,本文主要介紹了redis實現紅鎖的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2025-04-04
  • Redis安裝啟動及常見數據類型

    Redis安裝啟動及常見數據類型

    這篇文章主要介紹了Redis安裝啟動及常見數據類型,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • redis在spring boot中異常退出的問題解決方案

    redis在spring boot中異常退出的問題解決方案

    這篇文章主要介紹了redis在spring boot中異常退出的問題解決方案,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-05-05
  • redis?sentinel監(jiān)控高可用集群實現的配置步驟

    redis?sentinel監(jiān)控高可用集群實現的配置步驟

    這篇文章主要介紹了redis?sentinel監(jiān)控高可用集群實現的配置步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2022-04-04

最新評論

商南县| 凤山市| 雷州市| 镇原县| 元阳县| 康马县| 长泰县| 独山县| 门头沟区| 稻城县| 汝阳县| 大埔区| 大兴区| 渝北区| 临泽县| 佛冈县| 盘锦市| 昌吉市| 电白县| 叶城县| 环江| 通海县| 三亚市| 阳春市| 稻城县| 玛纳斯县| 阿尔山市| 长丰县| 上杭县| 玉环县| 大宁县| 介休市| 奇台县| 丽江市| 绍兴市| 普洱| 化州市| 茂名市| 高平市| 民乐县| 巴彦县|