騰訊的Tendis這麼牛皮,可否取代Redis的地位?

前言

Redis 做爲高性能緩存被普遍應用到各個業務, 好比遊戲的排行榜, 分佈式鎖等場景。通過在 IEG 的長期運營, 咱們也遇到 Redis 一些痛點問題, 好比內存佔用高, 數據可靠性差, 業務維護緩存和存儲的一致性繁瑣。由 騰訊互娛 CROS DBA 團隊 & 騰訊雲數據庫團隊聯合研發的 Tendis 推出了: 緩存版 、 混合存儲版 和 存儲版 三種不一樣產品形態, 針對不一樣的業務需求, 本文主要介紹 混合存儲版 的總體架構, 而且詳細揭祕內部的原理。面試

導語

本文首先介紹騰訊 IEG 運營 Redis 遇到的一些痛點問題, 而後介紹由 騰訊互娛 CROS DBA 團隊 & 騰訊雲數據庫團隊聯合研發的 Tendis 的三種不一樣的產品形態。最後重點介紹冷熱混合存儲版的架構, 而且重點介紹各個組件的功能特性。redis

背景介紹

Redis 有哪些痛點 ?

在使用的過程當中, 主要遇到如下一些痛點問題|Redis實戰學習筆記算法

  • 內存成本高
  • 業務不一樣階段對 QPS 要求不一樣 好比遊戲業務, 剛上線的新遊戲特別火爆, 爲了支持上千萬同時在線, 須要不斷地進行擴容增長機器。運營一段時間後, 遊戲玩家可能變少, 訪問頻率(QPS)沒那麼高, 依然佔用大量機器, 維護成本很高。
  • 須要爲 Fork 預留內存 Redis 保存全量數據時, 須要 Fork 一個進程。Linux 的 fork 系統調用基於 Copy On Write 機制, 若是在此期間 Redis 有大量的寫操做, 父子進程就須要各自維護一分內存。所以部署 Redis 的機器每每須要預留一半的內存。
  • 緩存一致性的問題 對於 Redis + MySQL 的架構須要業務方花費大量的精力來維護緩存和數據庫的一致性。
  • 數據可靠性 Redis 本質上是一個內存數據庫, 用戶雖然可使用 AOF 的 Always 來羅盤保證數據可靠性, 可是會帶來性能的大幅降低, 所以生產環境不多有使用。另外 不支持 回檔, Master 故障後, 異步複製會形成數據的丟失。
  • 異步複製 Redis 主備使用異步複製, 這個是異步複製固有的問題。主備使用異步複製, 響應延遲低, 性能高, 可是 Master 故障後, 會形成數據丟失。

Tendis 是什麼 ?

Tendis 是集騰訊衆多海量 KV 存儲優點於一身的 Redis 存儲解決方案, 並 100% 兼容 Redis 協議和 Redis4.0 全部數據模型。做爲一個高可用、高性能的分佈式 KV 存儲數據庫, 從訪問時延、持久化需求、總體成本等不一樣維度的考量, Tendis 推出了 緩存版混合存儲版存儲版 三種不一樣產品形態,並將存儲版開源。感興趣的小夥伴 能夠去 Github 關注咱們的項目: Tencent/Tendis數據庫

Tendis 緩存版適用於對延遲要求特別敏感, 而且對 QPS 要求很高的業務。基於社區 Redis 4.0 版本進行定製開發。緩存

Tendis 存儲版適用於大容量, 延遲不敏感型業務, 數據所有存儲在 磁盤, 適合溫冷數據的存儲。Tendis 存儲版是騰訊互娛 CROS DBA 團隊 & 騰訊雲數據庫團隊 自主設計和研發的開源分佈式高性能 KV 存儲系統。另外在 可靠性、複製機制、併發控制、gossip 實現以及數據搬遷等作了大量的優化, 而且解決了一些 Redis cluster 比較棘手的問題。徹底兼容 Redis 協議, 並使用 RocksDB 做爲底層存儲引擎。網絡

Tendis 冷熱混合存儲版冷熱混合存儲 綜合了緩存版和存儲版的優勢, 緩存層存放熱數據, 全量數據存放在存儲層。這既保證了熱數據的訪問性能,同時保證了全量數據的可靠性,同時熱數據支持自動降冷。數據結構

Tendis 冷熱混合存儲版 總體架構

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

Proxy 組件: 負責對客戶端請求進行路由分發,將不一樣的 Key 的命令分發到正確的分片,同時 Proxy 還負責了部分監控數據的採集,以及高危命令在線禁用等功能。併發

緩存層 Redis Cluster: 緩存層 Redis 基於 社區 Redis 4.0 進行開發。Redis 具備如下功能: 1) 版本控制 2) 自動將 冷數據從緩存層中淘汰, 將熱數據從存儲層加載到緩存層; 3) 使用 Cuckoo Filter 表示全量 Keys, 防止緩存穿透; 4) 基於 RDB+AOF 擴縮容方式, 擴縮容更加高效便捷。運維

存儲層 Tendis Cluster: Tendis 存儲版 是騰訊基於 RocksDB 自研的 兼容 Redis 協議的 KV 存儲引擎, 該引擎已經在騰訊集團內部運營多年, 性能和穩定性獲得了充分的驗證。在混合存儲系統中主要負責全量數據的存儲和讀取, 以及數據備份, 增量日誌備份等功能。

同步層 Redis-sync: 1) 並行數據導入 存儲層 Tendis; 2) 服務無狀態, 故障從新拉起; 3) 數據自動路由。

Tendis 冷熱混合存儲的一些重要特性介紹:

  • 緩存層 Redis Cluster 和 存儲層 Tendis Cluster 分別進行擴縮容, 集羣自治管理等。
  • 冷數據自動降冷, 下降內存成本; 熱數據自動緩存, 下降訪問延遲

緩存層 Redis Cluster

冷熱混合存儲緩存層 Redis 在社區版的基礎上增長了如下功能:

  • 版本控制
  • 冷熱數據交互
  • Cuckoo Filter 避免緩存穿透
  • 智能淘汰算法
  • 基於 RDB+AOF 擴縮容

下面分別對這幾個特性進行詳細的講解。

版本控制

首先基於社區版 Redis 改動是版本控制。咱們爲每一個 Key 和 每條 Aof 增長一個 Version , 而且 Version 是單調遞增的。在每次更新/新增一個 Key 後, 將當前節點的 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;

引入版本控制主要帶來如下優點:

  1. 增量 RDB

社區版 Redis 主備在斷線重連後, 若是 slave 發送的 psync_offset 對應的數據不在當前的 Master 的 repl_backlog 中, 則主備須要從新進行全量同步。再引入 Version 以後, slave 斷線重連, 給 Master 發送 帶 Version 的 PSYNC replid psync_offset version命令。若是出現上述狀況, Master 將大於等於 Version 的數據生成增量 RDB, 發給 Slave, 進而解決須要增量, 同步比較慢的問題。

  1. Aof 的冪等

若是同步層 Redis-sync 出現網絡瞬斷(短暫的和緩存層或者存儲層斷開), 做爲一個無狀態的同步組件, Redis-sync 會從新拉取未同步到 Tendis 的增量數據, 從新發送給 Tendis。每條 Aof 都具備一個 Version, Tendis 在執行的時候僅會執行比當前 Version 大的 Aof, 避免 aof 執行屢次致使的數據不一致。

冷熱數據交互

冷數據的恢復指當用戶訪問的 Key 不在緩存層, 須要將數據從存儲層從新加載到緩存層。數據恢復這裏是緩存層直接和存儲層直接交互, 當冷 Keys 訪問的請求比較大, 數據恢復很容易成爲瓶頸, 所以爲每一個 Tendis 節點創建一個鏈接池, 專門負責與這個 Tendis 節點進行冷熱數據恢復。

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

  1. 首先判斷 Key 是否在緩存層, 若是緩存層存在, 則執行命令; 若是緩存層不存在, 查詢 Cuckoo Filter, 判斷 Key 是否有可能在存儲層;
  2. 若是 Key 可能在存儲層, 則向存儲層發送 dumpx dbid key withttl 命令嘗試從存儲層獲取數據, 而且阻塞當前請求的客戶端;
  3. 存儲層收到 dumpx , 若是 Key 在存儲層, 則向緩存層返回 RESTOREEX dbid key ttl value; 若是 Key 不在存儲層(Cuckoo Filter 的誤判), 則向緩存層返回 DUMPXERROR key;
  4. 存儲層收到 RESTOREEX 或者 DUMPXERROR 後, 將冷數據恢復。而後就能夠喚醒阻塞的客戶端, 執行客戶端的請求。

Key 降冷 與 Cuckoo Filter

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

  1. Key 降冷的背景介紹2020 年 6 月份上線的 1:1 版的冷熱混合存儲, 緩存層 Redis 存儲全量的 Keys 火熱 Values(All Keys + Hot values), 存儲層 Tendis 存儲全量的 Keys 和 Values(All Keys + All values)。在上線運行了一段時間後, 發現全量 Keys 的內存開銷特別大, 冷熱混合的收益並不明顯。爲了進一步釋放內存空間, 提升緩存的效率, 咱們放棄了 Redis 緩存全量 Keys 的方案, 驅逐的時候將 key 和 Value 都從緩存層淘汰。
  2. Cuckoo Filter 解決緩存擊穿和緩存穿透若是緩存層不存儲全量的 Keys, 就會出現緩存擊穿和緩存穿透的問題。爲了解決這一問題, 緩存層引入 Cuckoo Filter 表示全量的 keys 。咱們須要一個支持刪除、可動態伸縮而且空間利用率高的 Membership Query 結構, 通過咱們的調研和對比分析, 最終選擇 Dynamic Cuckoo Filter。
  3. Dynamic Cuckoo Filter 實現項目初期參考了 RedisBloom 中 Cuckoo Filter 的實現, 在開發的過程當中也遇到了一些坑, RedisBloom 實現了 Cuckoo Filter 在刪除的時候會出現誤刪, 最終給 RedisBloom 題 PR(Fix Cuckoo filter compact cause deleted by mistake #260) 修復了問題。
  4. Key 降冷的收益最終採用將 Key 和 Value 同時從緩存層淘汰, 下降內存的收益很大。好比現網的一個業務, 總共有 6620 W 個 Keys , 在緩存全量 Keys 的時候 佔用 18408 MB 的內存, 在 Key 降冷後 僅僅佔用 593MB 。

智能淘汰/加載策略

做爲冷熱混合存儲系統, 熱數據在緩存層, 全量數據在存儲層。關鍵的問題是淘汰和加載策略, 這裏直接影響緩存的效率, 細分主要有兩點: 1) 當緩存層內存滿時, 選擇哪些數據淘汰; 2) 當用戶訪問存儲層的數據時, 是否須要將其放入緩存層

  1. 首先介紹混合存儲的淘汰策略, 主要有如下兩個淘汰策略:
  2. maxmemory-policy 當緩存層 Redis 內存使用到達 maxmemory, 系統將按照 maxmemory-policy 的內存策略將 Key/Value 從緩存層驅逐, 釋放內存空間。(驅逐是指將 Key/Value 從緩存層中淘汰掉, 存儲層 和 緩存層的 Cuckoo Filter 依然存在該 Key; )
  3. value-eviction-policy 若是配置 value-eviction-policy, 後臺會按期將用戶 N 天未訪問的 Key/Value 被驅逐出內存;
  4. 緩存加載策略 爲了不緩存污染的問題(好比相似 Scan 的訪問, 遍歷存儲層的數據, 將緩存層真正的熱數據淘汰, 從而形成了緩存效率低下) 。咱們實現緩存加載策略: 僅僅將規定時間內訪問頻率超過某個閾值的數據加載到緩存中, 這裏的時間和閾值都是可配置的。

基於 RDB+AOF 擴縮容

社區版 Redis 的擴容流程:

社區版 Redis 擴容存在的一些問題:

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

先設置目標節點 slot 爲 importing 狀態, 再設置源節點的 slot 爲 migrating 狀態。若是反過來, 因爲兩次操做非原子: 源節點設置爲 migrating , 目標節點還未設置 migrating 狀態, 請求在這兩個節點間反覆 Move 。

  1. 搬遷以 Key 爲粒度, 效率較低

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

  1. 大 Key 問題

因爲 Migrate 命令是同步命令, 在搬遷過程當中是不能處理其餘用戶請求的, 所以可能會影響業務。(延遲時間波動較大)

因爲社區版 Redis 存在的上述問題, 咱們實現了基於 RDB+Aof 的擴縮容方式, 大體流程以下:

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

同步層 Redis-sync

同步層 Redis-sync 模擬 Redis Slave 的行爲, 接收 RDB 和 Aof, 而後並行地導入到存儲層 Tendis。同步層主要須要解決如下問題:

  • 併發地導入到存儲層 Tendis, 如何保證時序正確 ?
  • 特殊命令的處理, 好比 FLUSHALL/FLUSHDB/SWAPDB/SELECT/MULTI 等 ?
  • 做爲一個無狀態的同步組件, 如何保證故障後, 數據斷點續傳 ?
  • 緩存層和存儲層 分別進行擴縮容, 如何將請求路由到正確的 Tendis 節點 ?

爲了解決上述的三個問題, 咱們實現了下面的功能:

  • Slot 內串行, Slot 間並行 針對問題 1, Redis-sync 中採用與 Redis 相同的計算 Slot 的算法, 解析到具體的命令後, 根據 Key 所屬的 slot, 將其放到對應的 隊列中( slot%QueueSize )。所以同一個 Slot 的數據是串行寫入, 不一樣 slot 的數據能夠並行寫入, 不會出現時序錯亂的行爲。
  • 串並轉換 針對問題 2, Redis-sync 會在並行和串行模式之間進行轉換。好比收到 FLUSHDB 命令, 這是須要將 FLUSHDB 命令 前的命令都執行完, 在執行 FLUSHDB 命令。
  • 按期上報 針對問題 3, Redis-sync 會按期將已發送給存儲層的 aof 的 Version 持久化到 存儲層。如何 Redis-sync 故障, 首先從 存儲層獲取上次已發送的位置, 而後向對應的 Redis 節點發送 psync, 請求同步。
  • 數據自動路由 針對問題 4, Redis-sync 會按期從存儲層獲取 Slot 到 Tendis 節點的映射關係, 而且維護這些 Tendis 節點的鏈接池。請求從 緩存層到達, 而後計算請求所屬的 slot, 而後發送到正確的 Tendis 節點。

存儲層 Tendis Cluster

Tendis 是兼容 Redis 核心數據結構與協議的分佈式高性能 KV 數據庫, 主要具備如下特性:

  • 兼容 Redis 協議 徹底兼容 redis 協議,支持 redis 主要數據結構和接口,兼容大部分原生 Redis 命令。
  • 持久化存儲 使用 rocksdb 做爲存儲引擎,全部數據以特定格式存儲在 rocksdb 中,最大支持 PB 級存儲。
  • 去中心化架構 相似於 redis cluster 的分佈式實現,全部節點經過 gossip 協議通信,可指定 hashtag 來控制數據分佈和訪問,使用和運維成本極低。
  • Redis核心面試真題,但願對您有幫助!
  • 水平擴展 集羣支持增刪節點,而且數據能夠按照 slot 在任意兩節點之間遷移,擴容和縮容過程當中對應用運維人員透明,支持擴展至 1000 個節點。
  • 故障自動切換 自動檢測故障節點,當故障發生後,slave 會自動提高爲 master 繼續對外提供服務。

相關文章
相關標籤/搜索