極光筆記丨百億級數據的實時存取優化與實踐

作者:極光高級工程師—包利

摘要

極光推送後臺標籤/別名系統存儲超過百億條數據, 高峯期 QPS 超過 50 萬, 且隨着業務的發展,存儲容量和訪問量還在不斷增加。之前系統存在的一些瓶頸也逐漸顯現,所以近一兩年持續做了很多的優化工作,最終達到非常不錯的效果。近期,經過積累和沉澱後,將這一部分的工作進行總結。

背景

當前的舊系統中主要存儲標籤/別名與註冊 ID 的相互映射關係, 使用 Key-Value 結構存儲, 考慮到一個註冊 ID 可能有多個標籤, 同時一個標籤也存在多個不同的註冊 ID, 這部分數據使用 Redis 存儲中的 Set 數據結構; 而一個註冊 ID 只有一個別名, 同時一個別名也存在多個不同的註冊 ID, 這部分數據使用 String/Set 數據結構。由於此部分數據量過大, 考慮到存儲成本, Redis 使用的單 Master 模式, 而最終數據的落地使用 Pika 存儲(一種類 Redis 的文件存儲)。Pika 與 Redis 中的數據由業務方保持一致, Redis 正常可用時, 讀 Redis; 當 Redis 不可用時讀 Pika, Redis 恢復可用後, 從 Pika 恢復數據到 Redis, 重新讀 Redis。舊系統的存儲架構如下:

從上面的架構圖可以看到, Redis/Pika 均採用主從模式, 其中 Redis 只有 Master, 配置管理模塊用來維護 Redis/Pika 集羣的主從關係, 配置寫入 ZooKeeper 中, 業務模塊從 ZooKeeper 中讀取配置, 不做配置變更。所有的配置變更由配置管理模塊負責. 當需要遷移, 擴容, 縮容的時候, 必須通過配置管理模塊操作。這個舊系統的優缺點如下:

優點:
配置集中管理, 業務模塊不需要分開單獨配置
讀取 Redis 中數據, 保證了高併發查詢效率
Pika 主從模式, 保證了數據落地, 不丟失
配置管理模塊維護分片 slot 與實例的映射關係, 根據 Key 的 slot 值路由到指定的實例

缺點:
Redis 與 Pika 中存儲的數據結構不一致, 增加了代碼複雜度
Redis 單 Master 模式, Redis 某個節點不可用時, 讀請求穿透到 Pika, 而 Pika 不能保證查詢效率, 會造成讀請求耗時增加甚至超時
Redis 故障恢復後, 需要從 Pika 重新同步數據, 增加了系統不可用持續時間, 且數據一致性需要更加複雜的計算來保證
當遷移/擴容/縮容時需要手動操作配置管理模塊, 步驟繁瑣且容易出錯
Redis 中存儲了與 Pika 同樣多的數據, 佔用了大量的內存存儲空間, 資源成本很高
整個系統的可用性還有提升空間, 故障恢復時間可以儘量縮短
配置管理模塊爲單點, 非高可用, 當此服務 down 掉時, 整個集羣不是高可用, 無法感知 Redis/Pika 的心跳狀態
超大 Key 打散操作需要手動觸發. 系統中存在個別標籤下的註冊 ID 過多, 存儲在同一個實例上, 容易超過實例的存儲上限, 而且單個實例限制了該 Key 的讀性能

舊系統缺點分析

考慮到舊系統存在以上的缺點, 主要從以下幾個方向解決:

Redis 與 Pika 中存儲的數據結構不一致, 增加了代碼複雜度
分析: 舊系統中 Redis 與 Pika 數據不一致主要是 Pika 早期版本 Set 數據結構操作效率不高, String 數據結構操作的效率比較高, 但獲取標籤/別名下的所有註冊 ID 時需要遍歷所有 Pika 實例, 這個操作非常耗時, 考慮到最新版本 Pika 已經優化 Set 數據結構, 提高了 Set 數據結構的讀寫性能, 應該保持 Redis 與 Pika 數據結構的一致性。

Redis 單 Master 模式, Redis 某個節點不可用時, 讀請求穿透到 Pika, 而 Pika 不能保證查詢效率, 會造成讀請求耗時增加甚至超時
分析: Redis 單 Master 模式風險極大。需要優化爲主從模式, 這樣能夠在某個 Master 故障時能夠進行主從切換, 不再從 Pika 中恢復數據, 減少故障恢復時間, 減少數據不一致的可能性。

Redis 故障恢復後, 需要從 Pika 重新同步數據, 增加了系統不可用持續時間, 且數據一致性需要更加複雜的計算來保證
分析: 這個系統恢復時間過長是由於 Redis 是單 Master 模式, 且沒有持久化, 需要把 Redis 優化成主從模式且必須開啓持久化, 從而幾乎不需要從 Pika 恢復數據了, 除非 Redis 的主從實例全部同時不可用。不需要從 Pika 恢復數據後, 那麼 Redis 中的數據在 Redis 主從實例發生故障時, 就和 Pika 中的數據一致了。

當遷移/擴容/縮容時需要手動操作配置管理模塊, 步驟繁瑣且容易出錯
分析: 配置管理模塊手動干預操作過多, 非常容易出錯, 這部分應儘量減少手動操作, 考慮引入 Redis 哨兵, 能夠替換大部分的手動操作步驟。

Redis 中存儲了與 Pika 同樣多的數據, 佔用了大量的內存存儲空間, 資源成本很高
分析: 通過對 Redis 中的各個不同維度數據進行數據量和訪問量以及訪問來源分析(如下圖)。外部請求量(估算) 這欄的數據反應了各個不同 Key 的單位時間內訪問量情況。

Redis 的存儲數據主要分爲標籤/別名到註冊 ID 和註冊 ID 到標籤/別名兩部分數據. 通過分析得知, 標籤/別名到註冊 ID 的數據約佔 1/3 左右的存儲空間, 訪問量佔到 80%; 註冊 ID 到標籤/別名的數據約佔 2/3 左右的存儲空間, 訪問量佔到 20%。可以看到, 紅色數字部分爲訪問的 Pika, 黑色部分訪問的 Redis, 3.7%這項的數據可以優化成訪問 Redis, 那麼可以得出結論, 紅色的數據在 Redis 中是永遠訪問不到的。所以可以考慮將 Redis 中註冊 ID 到標籤/別名這部分數據刪掉, 訪問此部分數據請求到 Pika, 能夠節省約 2/3 的存儲空間, 同時還能保證整個系統的讀性能。

整個系統的可用性還有提升空間, 故障恢復時間可以儘量縮短
分析: 這部分主要由於其中一項服務爲非高可用, 而且整個系統架構的複雜性較高, 以及數據一致性相對比較難保證, 導致故障恢復時間長, 考慮應將所有服務均優化爲高可用, 同時簡化整個系統的架構。

配置管理模塊爲單點, 非高可用, 當此服務 down 掉時, 整個集羣不是高可用, 無法感知 Redis/Pika 的心跳狀態
分析: 配置手動管理風險也非常大, Pika 主從關係通過配置文件手動指定, 配錯後將導致數據錯亂, 產生髒數據. 需要使用 Redis 哨兵模式, 用哨兵管理 Redis/Pika, 配置管理模塊不再直接管理所有 Redis/Pika 節點, 而是通過哨兵管理, 同時再發生主從切換或者節點故障時通知配置管理模塊, 自動更新配置到 Zookeeper 中, 遷移/擴容/縮容時也基本不用手動干預。

超大 Key 打散操作需要手動觸發。系統中存在個別標籤下的註冊 ID 過多, 存儲在同一個實例上, 容易超過實例的存儲上限, 而且單個實例限制了該 Key 的讀性能
分析: 這部分手動操作, 應該優化爲自動觸發, 自動完成遷移, 減少人工干預, 節省人力成本。

Redis 哨兵模式

Redis 哨兵爲 Redis/Pika 提供了高可用性, 可以在無需人工干預的情況下抵抗某些類型的故障, 還支持監視, 通知, 自動故障轉移, 配置管理等功能:
監視: 哨兵會不斷檢查主實例和從實例是否按預期工作
通知: 哨兵可以將出現問題的實例以 Redis 的 Pub/Sub 方式通知到應用程序
自動故障轉移: 如果主實例出現問題, 可以啓動故障轉移, 將其中一個從實例升級爲主, 並將其他從實例重新配置爲新主實例的從實例, 並通知應用程序要使用新的主實例
配置管理: 創建新的從實例或者主實例不可用時等都會通知給應用程序

同時, 哨兵還具有分佈式性質, 哨兵本身被設計爲可以多個哨兵進程協同工作, 當多個哨兵就給定的主機不再可用這一事實達成共識時, 將執行故障檢測, 這降低了誤報的可能性。 即使不是所有的哨兵進程都在工作, 哨兵仍能正常工作, 從而使系統能夠應對故障。
Redis 哨兵+主從模式能夠在 Redis/Pika 發生故障時及時反饋實例的健康狀態, 並在必要時進行自動主從切換, 同時會通過 Redis 的 sub/pub 機制通知到訂閱此消息的應用程序。從而應用程序感知這個主從切換, 能夠短時間將鏈接切換到健康的實例, 保障服務的正常運行。

沒有采用 Redis 的集羣模式, 主要有以下幾點原因:
當前的存儲規模較大, 集羣模式在故障時, 恢復時間可能很長
集羣模式的主從複製通過異步方式, 故障恢復期間不保證數據的一致性
集羣模式中從實例不能對外提供查詢, 只是主實例的備份
無法全局模糊匹配 Key, 即無法遍歷所有實例來查詢一個模糊匹配的 Key

最終解決方案

綜上, 爲了保證整個存儲集羣的高可用, 減少故障恢復的時間, 甚至做到故障時對部分業務零影響, 我們採用了 Redis 哨兵+Redis/Pika 主從的模式, Redis 哨兵保證整個存儲集羣的高可用, Redis 主從用來提供查詢標籤/別名到註冊 ID 的數據, Pika 主從用來存儲全量數據和一些註冊 ID 到標籤/別名數據的查詢。需要業務保證所有 Redis 與 Pika 數據的全量同步。新方案架構如下:

從上面架構圖來看, 當前 Redis/Pika 都是多主從模式, 同時分別由不同的多個哨兵服務監視, 只要主從實例中任一個實例可用, 整個集羣就是可用的。Redis/Pika 集羣內包含多個主從實例, 由業務方根據 Key 計算 slot 值, 每個 slot 根據配置管理模塊指定的 slot 與實例映射關係。如果某個 slot 對應的 Redis 主從實例均不可用, 會查詢對應的 Pika, 這樣就保證整個系統讀請求的高可用。這個新方案的優缺點如下:

優點:
整個系統所有服務, 所有存儲組件均爲高可用, 整個系統可用性非常高
故障恢復時間快, 理論上當有 Redis/Pika 主實例故障, 只會影響寫入請求, 故障時間是哨兵檢測的間隔時間; 當 Redis/Pika 從實例故障, 讀寫請求都不受影響, 讀服務可以自動切換到主實例, 故障時間爲零, 當從實例恢復後自動切換回從實例
標籤/別名存儲隔離, 讀寫隔離, 不同業務隔離, 做到互不干擾, 根據不同場景擴縮容
減少 Redis 的內存佔用 2/3 左右, 只使用原有存儲空間的 1/3, 減少資源成本
配置管理模塊高可用, 其中一個服務 down 掉, 不影響整個集羣的高可用, 只要其中一臺服務可用, 那麼整個系統就是可用
可以平滑遷移/擴容/縮容, 可以做到遷移時無需業務方操作, 無需手動干預, 自動完成; 擴容/縮容時運維同步好數據, 修改配置管理模塊配置然後重啓服務即可
超大 Key 打散操作自動觸發, 整個操作對業務方無感知, 減少運維成本

缺點:
Redis 主從實例均可不用時, 整個系統寫入這個實例對應 slot 的數據均失敗, 考慮到從 Pika 實時同步 Redis 數據的難度, 並且主從實例均不可用的概率非常低, 選擇容忍這種情況
哨兵管理增加系統了的複雜度, 當 Redis/Pika 實例主從切換時通知業務模塊處理容易出錯, 這部分功能已經過嚴格的測試以及線上長時間的功能檢驗

其他優化

除了通過以上架構優化, 本次優化還包括以下方面:

通過 IO 複用, 由原來的每個線程一個實例鏈接, 改爲在同一個線程同時管理多個鏈接,提高了服務的 QPS, 降低高峯期的資源使用率(如負載等)
之前的舊系統存在多個模塊互相調用情況, 減少模塊間耦合, 減少部署及運維成本
之前的舊系統模塊間使用 MQ 交互, 效率較低, 改爲 RPC 調用, 提高了調用效率, 保證調用的成功率, 減少數據不一致, 方便定位問題
不再使用 Redis/Pika 定製化版本, 可根據需要, 升級到 Redis/Pika 官方的最新穩定版本
查詢模塊在查詢大 Key 時增加緩存, 緩存查詢結果, 此緩存過期前不再查詢 Redis/Pika, 提高了查詢效率

展望
未來此係統還可以從以下幾個方面繼續改進和優化:
大 Key 存儲狀態更加智能化管理, 增加設置時的大 Key 自動化遷移, 使存儲更加均衡
制定合理的 Redis 數據過期機制, 降低 Redis 的存儲量, 減少服務存儲成本
增加集合操作服務, 實現跨 Redis/Pika 實例的交集/並集等操作, 並添加緩存機制, 提高上游服務訪問效率, 提高推送消息下發效率

總結
本次系統優化在原有存儲組件的基礎上, 根據服務和數據的特點, 合理優化服務間調用方式, 優化數據存儲的空間, 將 Redis 當作緩存, 只存儲訪問量較大的數據, 降低了資源使用率。Pika 作爲數據落地並承載訪問量較小的請求, 根據不同存儲組件的優缺點, 合理分配請求方式。同時將所有服務設計爲高可用, 提高了系統可用性, 穩定性。最後通過增加緩存等設計, 提高了高峯期的查詢 QPS, 在不擴容的前提下, 保證系統高峯期的響應速度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章