<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          騰訊云超火開源數(shù)據庫產品架構揭秘

          共 6940字,需瀏覽 14分鐘

           ·

          2021-04-13 21:37

          Redis 作為高性能緩存經常被廣泛應用到各個業(yè)務——如游戲的排行榜、分布式鎖等場景。

          但Redis也并非萬能的,在長期的使用過程中,我們也遇到 Redis 一些痛點問題, 比如內存占用高, 數(shù)據可靠性差, 業(yè)務維護緩存和存儲的一致性繁瑣等。

          因此,騰訊云數(shù)據庫Tendis誕生了,今天,我們就結合視頻,一起回顧騰訊云數(shù)據庫Tendis混合存儲版的整體架構, 并且詳細揭秘其內部的原理。

          進入“騰訊云數(shù)據庫”公眾號,后臺回復“0331李景軍”,即可下載分享PPT。


          Redis&Tendis



          使用 Redis 有哪些痛點?我大致分類為以下三種:

          一、內存成本高

          首先,在產品的不同階段,業(yè)務對 QPS 的要求不同,以游戲業(yè)務為例, 剛上線的新游戲通常來說都特別火爆, 為了支持上千萬同時在線, 需要不斷的進行擴容增加機器。而在運營一段時間后, 游戲玩家逐漸變少到一個正常的用戶量級, 訪問頻率(QPS)沒那么高, 依然占用大量機器, 維護成本很高。

          其次,Redis 保存全量數(shù)據時, 需要 Fork 一個進程。Linux 的 fork 系統(tǒng)調用基于 Copy On Write 機制, 如果在此期間 Redis 有大量的寫操作, 父子進程就需要各自維護一份內存。因此部署 Redis 的機器往往需要預留一半的內存。

          二、緩存一致性的問題

          對于 Redis + MySQL 的架構需要業(yè)務方花費大量的精力來維護緩存和數(shù)據庫的一致性。

          三、數(shù)據可靠性

          Redis 本質上是一個內存數(shù)據庫, 用戶雖然可以使用 AOF 的 Always 來落盤保證數(shù)據可靠性, 但是會帶來性能的大幅下降, 因此生產環(huán)境很少有使用。另外 不支持 回檔, Master 故障后, 異步復制會造成數(shù)據的丟失。

          四、異步復制

          Redis 主備使用異步復制, 這個是異步復制固有的問題。主備使用異步復制, 響應延遲低, 性能高, 但是 Master 故障后, 會造成數(shù)據丟失。

          關于 Tendis,我們已經做了很多介紹(開源一周star上千,什么產品這么香?)在此不再贅述。接下來我們對 Tendis 混合存儲版的整體架構進行詳細的解讀。


          Tendis混合存儲版整體架構



          Tendis 冷熱混合存儲版主要由 Proxy 、緩存層 Redis、 存儲層 Tendis 存儲版和同步層 Redis-sync 組成, 其中每個組件的功能如下:

          一、Proxy組件

          負責對客戶端請求進行路由分發(fā),將不同的Key的命令分發(fā)到正確的分片,同時Proxy還負責了部分監(jiān)控數(shù)據的采集,以及高危命令在線禁用等功能。

          二、緩存層 Redis Cluster

          緩存層 Redis 基于 社區(qū) Redis 4.0 進行開發(fā)。Redis 具有以下功能: 

          版本控制;自動將冷數(shù)據從緩存層中淘汰,將熱數(shù)據從存儲層加載到緩存層;使用 Cuckoo Filter 表示全量 Keys, 防止緩存穿透;基于 RDB+AOF 擴縮容方式,擴縮容更加高效便捷。

          三、存儲層 Tendis Cluster

          Tendis 存儲版是騰訊基于 RocksDB 自研的兼容 Redis 協(xié)議的KV存儲引擎,該引擎已經在騰訊內部運營多年,性能和穩(wěn)定性得到了充分的驗證。在混合存儲系統(tǒng)中主要負責全量數(shù)據的存儲和讀取,以及數(shù)據備份, 增量日志備份等功能。

          四、同步層 Redis-sync

          并行數(shù)據導入存儲層 Tendis;服務無狀態(tài),故障重新拉起;數(shù)據自動路由。


          Tendis 冷熱混合存儲的一些重要特性總結:

          緩存層 Redis Cluster 和 存儲層 Tendis Cluster 分別進行擴縮容, 集群自治管理等;

          冷數(shù)據自動降冷, 降低內存成本; 熱數(shù)據自動緩存, 降低訪問延遲。


          緩存層Redis Cluster



          一、版本控制


          首先基于社區(qū)版 Redis 改動是版本控制。我們?yōu)槊總€ Key 和 每條 Aof 增加一個 Version , 并且 Version 是單調遞增的。在每次更新/新增一個 Key 后, 將當前節(jié)點的 Version 賦值給 Key 和 Value, 然后對全局的 Version++;

          如下所示, 在 redisObject 中添加 64bits, 其中 48bits 用于版本控制。

          typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */int refcount;
          /* for hybrid storage */unsigned flag:4; /* OBJ_FLAG_... */unsigned reserved:4;unsigned counter:8; /* for cold-data-cache-policy */unsigned long long revision:REVISION_BITS; /* for value version */
          void *ptr;} robj;
          引入版本控制主要帶來以下優(yōu)勢:

          1. 增量 RDB

          社區(qū)版 Redis 主備在斷線重連后, 如果 slave 發(fā)送的 psync_offset 對應的數(shù)據不在當前的 Master 的 repl_backlog 中, 則主備需要重新進行全量同步。

          再引入 Version 之后, slave 斷線重連, 給 Master 發(fā)送 帶 Version 的 PSYNC replid psync_offset version命令。如果出現(xiàn)上述情況, Master 將大于等于 Version 的數(shù)據生成增量 RDB, 發(fā)給 Slave, 進而解決需要增量, 同步比較慢的問題。

          2. Aof 的冪等

          如果同步層 Redis-sync 出現(xiàn)網絡瞬斷(短暫的和緩存層或者存儲層斷開), 作為一個無狀態(tài)的同步組件, Redis-sync 會重新拉取未同步到 Tendis 的增量數(shù)據, 重新發(fā)送給 Tendis。每條 Aof 都具有一個 Version, Tendis 在執(zhí)行的時候僅會執(zhí)行比當前 Version 大的 Aof, 避免 aof 執(zhí)行多次導致的數(shù)據不一致。

          二、冷熱數(shù)據交互

          冷數(shù)據的恢復指當用戶訪問的 Key 不在緩存層, 需要將數(shù)據從存儲層重新加載到緩存層。數(shù)據恢復這里是緩存層直接和存儲層直接交互, 當冷 Keys 訪問的請求比較大, 數(shù)據恢復很容易成為瓶頸, 因此為每個 Tendis 節(jié)點建立一個連接池, 專門負責與這個 Tendis 節(jié)點進行冷熱數(shù)據恢復。

          用戶訪問一個 Key 的具體流程如下:

          首先判斷 Key 是否在緩存層, 如果緩存層存在, 則執(zhí)行命令; 如果緩存層不存在, 查詢 Cuckoo Filter, 判斷 Key 是否有可能在存儲層;

          如果 Key 可能在存儲層, 則向存儲層發(fā)送 dumpx dbid key withttl 命令嘗試從存儲層獲取數(shù)據, 并且阻塞當前請求的客戶端;

          存儲層收到 dumpx , 如果 Key 在存儲層, 則向緩存層返回 RESTOREEX dbid key ttl value; 如果Key 不在存儲層(Cuckoo Filter 的誤判), 則向緩存層返回 DUMPXERROR key;

          存儲層收到 RESTOREEX 或者 DUMPXERROR 后, 將冷數(shù)據恢復。然后就可以喚醒阻塞的客戶端, 執(zhí)行客戶端的請求。

          三、Key 降冷 與 Cuckoo Filter

          這里主要講解混合存儲從 1:1 版的緩存層緩存全量 Keys, 到 N:M 版的緩存層將Key 和 Value 同時驅逐的演進, 以及我們引入 Cuckoo Filter 避免緩存穿透, 同時節(jié)省大量內存。

          1. Key 降冷的背景介紹

          2020 年 6 月份上線的 1:1 版的冷熱混合存儲, 緩存層 Redis 存儲全量的 Keys 和熱 Values(All Keys + Hot values), 存儲層 Tendis 存儲全量的 Keys 和 Values(All Keys + All values)。在上線運行了一段時間后, 發(fā)現(xiàn)全量 Keys 的內存開銷特別大, 冷熱混合的收益并不明顯。為了進一步釋放內存空間, 提高緩存的效率, 我們放棄了 Redis 緩存全量 Keys 的方案, 驅逐的時候將 key 和 Value 都從緩存層淘汰。

          2. Cuckoo Filter 解決緩存擊穿和緩存穿透

          如果緩存層不存儲全量的 Keys, 就會出現(xiàn)緩存擊穿和緩存穿透的問題。為了解決這一問題, 緩存層引入 Cuckoo Filter 表示全量的 keys 。我們需要一個支持刪除、可動態(tài)伸縮并且空間利用率高的 Membership Query 結構, 經過我們的調研和對比,最終選擇 Dynamic Cuckoo Filter。

          3. Dynamic Cuckoo Filter 實現(xiàn)

          項目初期參考了 RedisBloom 中 Cuckoo Filter 的實現(xiàn), 在開發(fā)的過程中也遇到了一些坑,RedisBloom 實現(xiàn)的 Cuckoo Filter 在刪除的時候會出現(xiàn)誤刪, 最終給 RedisBloom 提 PR 修復了問題。

          4. Key 降冷的收益

          最終采用將 Key 和 Value 同時從緩存層淘汰, 降低內存的收益很大。比如現(xiàn)網的一個業(yè)務, 總共有 6620 W 個 Keys , 在緩存全量 Keys 的時候 占用 18408 MB的內存, 在 Key 降冷后 僅僅占用 593MB 。

          四、智能淘汰/加載策略

          作為冷熱混合存儲系統(tǒng), 熱數(shù)據在緩存層, 全量數(shù)據在存儲層。關鍵的問題是淘汰和加載策略, 這里直接影響緩存的效率, 細分主要有兩點:當緩存層內存滿時, 選擇哪些數(shù)據淘汰?當用戶訪問存儲層的數(shù)據時, 是否需要將其放入緩存層?

          首先介紹混合存儲的淘汰策略, 主要有以下兩個淘汰策略:

          1. maxmemory-policy

          當緩存層 Redis 內存使用到達 maxmemory, 系統(tǒng)將按照 maxmemory-policy 的內存策略將 Key/Value 從緩存層驅逐, 釋放內存空間。(驅逐是指將 Key/Value 從緩存層中淘汰掉, 存儲層 和 緩存層的 Cuckoo Filter 依然存在該 Key;

          2. value-eviction-policy
          如果配置 value-eviction-policy, 后臺會定期將用戶 N 天未訪問的 Key/Value 被驅逐出內存;

          其次講一下緩存加載策略,為了避免緩存污染的問題(比如類似 Scan 的訪問, 遍歷存儲層的數(shù)據, 將緩存層真正的熱數(shù)據淘汰, 從而造成了緩存效率低下) 。我們實現(xiàn)緩存加載策略: 僅僅將規(guī)定時間內訪問頻率超過某個閾值的數(shù)據加載到緩存中, 這里的時間和閾值都是可配置的。

          五、基于 RDB+AOF 擴縮容

          社區(qū)版 Redis 的擴容流程如下所示:


          而社區(qū)版 Redis 擴容也存在以下三個問題:

          1. importing 和 migrating 的設置不是原子的

          先設置目標節(jié)點 slot 為 importing 狀態(tài), 再設置源節(jié)點的 slot 為 migrating 狀態(tài)。如果反過來, 由于兩次操作非原子: 源節(jié)點設置為 migrating , 目標節(jié)點還未設置 migrating 狀態(tài), 請求在這兩個節(jié)點間反復 Move 。

          2. 搬遷以 Key 為粒度, 效率較低

          Migrate 命令每次搬遷一個或者多個 Keys, 將整個 Slot 搬遷到目標節(jié)點需要多次網絡交互。

          3. 大 Key 問題

          由于 Migrate 命令是同步命令, 在搬遷過程中是不能處理其他用戶請求的, 因此可能會影響業(yè)務(延遲時間波動較大)。

          由于社區(qū)版 Redis 存在的上述問題, 我們實現(xiàn)了基于 RDB+Aof 的擴縮容方式, 大致流程如下:

          1) 管控添加新節(jié)點, 規(guī)劃待搬遷 slots;
          2) 管控端向目標節(jié)點下發(fā)slot同步命令: cluster slotsync beginSlot endSlot [[beginSlot endSlot]...]
          3) 目標節(jié)點向源節(jié)點發(fā)送 sync [slot ...], 命令請求同步slot數(shù)據
          4) 源節(jié)點生成指定 slot 數(shù)據的一致性快照全量數(shù)據(RDB), 并將其發(fā)送給目標節(jié)點
          5) 源節(jié)點開始持續(xù)發(fā)送增量數(shù)據(Aof)
          6) 管控端定位獲取源節(jié)點和目標節(jié)點的落后值 (diff_bytes), 如果落后值在指定的閾值內, 管控端向目標節(jié)點發(fā)送 cluster slotfailover (流程類似 Redis 的 cluster failover, 首先阻塞源節(jié)點寫入, 然后等待目標節(jié)點和源節(jié)點的落后值為 0, 最后將 搬遷的 slots 歸屬目標節(jié)點)



          同步層 Redis-sync



          同步層 Redis-sync 模擬 Redis Slave 的行為, 接收 RDB 和 Aof, 然后并行地導入到存儲層 Tendis。同步層主要需要解決以下問題:

          1) 并發(fā)地導入到存儲層 Tendis, 如何保證時序正確 ?
          2) 特殊命令的處理, 比如 FLUSHALL/FLUSHDB/SWAPDB/SELECT/MULTI 等 ?
          3) 作為一個無狀態(tài)的同步組件, 如何保證故障后, 數(shù)據斷點續(xù)傳 ?
          4) 緩存層和存儲層 分別進行擴縮容, 如何將請求路由到正確的 Tendis 節(jié)點 ?


          為了解決上述問題, 我們實現(xiàn)了下面的功能:

          1. Slot 內串行, Slot 間并行

          針對問題 1, Redis-sync 中采用與 Redis 相同的計算 Slot 的算法, 解析到具體的命令后, 根據 Key 所屬的 slot, 將其放到對應的 隊列中( slot%QueueSize )。因此同一個 Slot 的數(shù)據是串行寫入, 不同 slot 的數(shù)據可以并行寫入, 不會出現(xiàn)時序錯亂的行為。

          2. 串并轉換

          針對問題 2, Redis-sync 會在并行和串行模式之間進行轉換。比如收到 FLUSHDB 命令, 這是需要將 FLUSHDB 命令 前的命令都執(zhí)行完, 再執(zhí)行 FLUSHDB 命令。


          3. 定期上報

          針對問題 3, Redis-sync 會定期將已發(fā)送給存儲層的 aof 的 Version 持久化到 存儲層。如何 Redis-sync 故障, 首先從 存儲層獲取上次已發(fā)送的位置, 然后向對應的 Redis 節(jié)點發(fā)送 psync, 請求同步。

          4. 數(shù)據自動路由

          針對問題 4, Redis-sync 會定期從存儲層獲取 Slot 到 Tendis 節(jié)點的映射關系, 并且維護這些 Tendis 節(jié)點的連接池。請求從 緩存層到達, 然后計算請求所屬的 slot, 然后發(fā)送到正確的 Tendis 節(jié)點。


          存儲層 Tendis Cluster



          Tendis 是兼容 Redis 核心數(shù)據結構與協(xié)議的分布式高性能 KV 數(shù)據庫, 主要具有以下特性:


          1. 兼容Redis協(xié)議

          完全兼容redis協(xié)議,支持redis主要數(shù)據結構和接口,兼容大部分原生Redis命令。

          2. 持久化存儲
          使用rocksdb作為存儲引擎,所有數(shù)據以特定格式存儲在rocksdb中,最大支持PB級存儲。

          3. 去中心化架構

          類似于redis cluster的分布式實現(xiàn),所有節(jié)點通過gossip協(xié)議通訊,可指定hashtag來控制數(shù)據分布和訪問,使用和運維成本極低。

          4. 水平擴展

          集群支持增刪節(jié)點,并且數(shù)據可以按照slot在任意兩節(jié)點之間遷移,擴容和縮容過程中對應用運維人員透明,支持擴展至1000個節(jié)點。

          5. 故障自動切換

          自動檢測故障節(jié)點,當故障發(fā)生后,slave會自動提升為master繼續(xù)對外提供服務。


          最后,想要更深入了解 Tendis 的特性和使用可以掃碼關注或加入交流群。

          歡迎點擊「閱讀原文」,在開源社區(qū)中提出您寶貴的Issue和Pull Requests!
          瀏覽 64
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色猫咪AV在线 | 爱AV操| 午夜家庭影院 | 伊人色色影院 | 日韩高清免费AV一区二区三区 |