作者 | 王明明,濤思數據軟件工程師

小 T 導讀:在計算機系統中,緩存是一種常用的技術,既有硬件緩存,比如我們經常聽到的 CPU L2 高速緩存,也有軟件緩存,比如很多系統里把 Redis 當做數據庫的緩存。本文為根據 TDengine 線上 Meetup 第四期王明明的分享《TDengine 緩存技術解析》(視頻)整理而成。
TDengine 是一款高性能的物聯網大數據平台。為了高效處理時序數據,TDengine 中大量用到了緩存技術,自己實現了哈希表、緩存池等技術。今天我會為大家講解 TDengine 中用到的這些緩存技術。
首先我會介紹一下什么是緩存,常用的緩存技術,最后重點分享 TDengine 中的相關技術,最好講一下改進和優化的方向。下面我們正式開始。
什么是緩存?
凡是位於速度相差較大的兩種硬件之間,用於協調兩者數據傳輸速度差異的結構,均可稱之為緩存。
-
緩存最早是用來協調 CPU 和主內存之間的速度差異,進化出了目前的 L1/L2/L3 三層 CPU 內部的高速緩存;
-
在內存和硬盤之間也有 Cache,每次寫磁盤時並沒有立即刷到磁盤上,而是寫入到磁盤緩存中,由操作系統負責 flush 到磁盤;
-
此外,硬盤與網絡之間也有某種意義上的 Cache,比如 CDN 緩存,代理服務器的緩存等等。
緩存工作的原則主要是引用的局部性,包括空間局部性和時間局部性。
-
空間局部性是指 CPU 在某一時刻需要某個數據,那么很可能下一步就需要其附近的數據,例如加載讀磁盤數據的時候,雖然只需要一部分數據,但是每次都加載一個塊,那么當需要附近數據的時候就可以直接從內存獲取,避免再讀取磁盤。
-
時間局部性是指當某個數據被訪問過一次之后,過不了多久時間就會被再一次訪問。例如我們手機后台運行程序,會把最近打開的應用緩存在后台,很可能一會兒還會訪問相同的應用,這種情況下直接將其從后台調到前台即可。
在使用緩存時要根據系統的架構、性能的要求以及要解決的問題選擇合適的緩存位置,比如內存緩存、 磁盤緩存、分布式緩存等。
使用緩存有很多優點:
-
提高性能,將相應數據存儲起來以避免數據的重復創建、處理和傳輸,可有效提高性能。
-
提高穩定性,同一個應用中,對同一數據、邏輯功能的多次請求是經常發生的。當請求量很大時,如果每次請求都進行處理,消耗的資源是很大的浪費,也同時造成系統的不穩定。
-
提高可用性,有時,提供數據信息的服務可能會意外停止,如果使用了緩存技術,可以在一定時間內仍正常提供對最終用戶的支持,提高了系統的可用性。
緩存是有狀態的,包括時間狀態和空間狀態。
-
時間狀態:應用程序使用的永久數據; 只在進程周期內有效;和特定的用戶會話有關; 處理某個消息的時間內有效。
-
空間狀態:應用程序/進程/線程/單機/分布式/用戶/角色。
使用緩存時需要考慮的問題:
-
安全性:線程安全/權限安全
-
序列化
-
緩存數據優化
-
提前加載/動態加載
-
過期策略:FIFO/LRU/LFU
-
管理:效率監控,大小限制
緩存一致性問題:
-
當使用分布式的緩存時,需要考慮多個緩存的一致性問題,防止由於不一致出現問題。
-
處理一致性問題時需要根據實際的應用場景兼顧 CAP 原則。根據問題的場景不同,一致性要求也不同,可以強一致性或者弱一致性(最終一致性)。
a. 比如銀行轉賬場景需要強一致性,數據沒統一之前,不允許用戶進行操作,防止金額出錯。
b. 大多數互聯網產品為了保證可用性和分區容錯性,通常采用弱一致性,比如不同地區的用戶看到的同一個排行榜可能有非常短暫的不同,但數據同步成功后,排行榜就相同了,這個延遲通常在幾十 ms,對於用戶來說是可以接受的。
常用的緩存技術
-
使用硬件緩存 (CPU Cache)
-
使用本地內存緩存(雙緩沖/環形緩沖/緩沖池)
-
使用內存映射文件 (mmap)
-
使用數據庫緩存 (Redis/MySQL)
TDengine 中的緩存方案
首先我們來復習一下 TDengine 的整體架構。
-
數據節點(dnode):服務進程,可以包括多個 vnode 和 mnode,查詢數據時需要 dnode 的網絡位置來獲取數據。
-
虛擬節點(vnode):存儲、查詢的基本單位。多個 vnode 組成一個虛擬節點組(VGroup),分布在不同的機器上,起到備份的效果。同時 vnode 也便於水平擴展。
-
管理節點(mnode):存儲數據庫的元數據,起到管理集群的功能。

再來看一下 TDengine 的數據模型。
-
一個采集點一張表(時間戳作為主鍵,順序存儲)
-
一張表的數據在文件中以塊的形式連續存放
-
文件中的數據塊大小可配
-
采用 Block Range Index(BRIN)索引塊數據

TDengine 中都有哪些數據需要緩存呢?
具體可以分為如下幾類:
-
元數據 (table meta/stable vgroup)
-
連接數據 (rpc/http session)
-
查詢緩存 (qinfo handle/ show info)
-
最新數據 (last 和 last_row)
-
時序數據 (buffer pool/ multilevel storage)
接下來我們就具體看一下 TDengine 中的緩存方案。
首先是通用的哈希緩存 (meta data/ rpcObj/ qinfo)。
-
哈希緩存,通過一個列表來管理,每個元素是一個緩存結構,里面包括緩存信息、 哈希表 、垃圾回收鏈表、統計信息、更新頻率、鎖等信息。此外,有一個刷新線程定時檢測緩存列表中過期的數據,將其刪除。

-
查詢計划 id (query handle),query handle 是數據庫查詢時,server 先生產一個執行計划,返回給 client,然后 client 拿着這個計划 id,分多次去 server 取數據,直到數據查詢完。這個緩存是消息時間范圍,整個進程內有效的,不需要更新,使用完即釋放。

-
元數據緩存(meta data), meta data 數據主要記錄數據表的 scheme,所在的節點地址。通過客戶端緩存 meta data 可以避免頻繁的向 mnode 取數據。但是 meta 數據需要考慮更新一致性問題。通過版本號來控制。

其次是 TSDB 內存塊緩存 (double buffer/buffer pool)。
-
TDengine 提供雙緩存/緩存池來優化數據寫入查詢的性能。預分配 16M*6 的 buffer pool,使用超過 1/3 容量落地,落地時 mem 轉化為 imei(不可變更),負責寫入磁盤。
-
直接將最近到達的數據保存在緩存中,可以更加快速地響應用戶針對最近數據的查詢分析,整體上提供更快的數據庫查詢響應能力。
-
TDengine 重啟以后系統的緩存將被清空,之前緩存的數據均會被批量寫入磁盤,之前緩存的數據不會重新加載到緩存中。
-
數據查詢時首先通過 time range 定位數據所在的位置,因為 MEM 和 IMEM 中都記錄有最新、最舊數據的時間戳。然后如果在 MEM 中,通過跳表來快速查詢數據位置。在磁盤中,通過磁盤塊文件索引查找數據,最后做結果融合返回。


再來看 last 和 last_row 緩存 (local storage)。
-
時序數據庫總是有對最新一行數據或者某列最新一條數據查詢的需求,因此設計了 last 和 last_row 緩存來快速響應用戶需求。防止每次都去磁盤查詢數據。
-
每個表開辟緩存區緩存該數據,服務啟動時會全量加載,插入時會更新,此外在配置更新的時候,也會更新緩存數據。比如,默認是關閉的。用戶使用命令開啟緩存功能時,就會加載數據,同理關閉開關時,會釋放之前的緩存區。


最后我們再來看一下多級存儲 (ssd/hdd/cloud)。
由於物聯網的數據量是巨大的,為了很好的平衡性能和成本,TDengine 還采用了分級存儲的思想,不同熱度數據存儲在不同的地方。分級存儲的這一思想也體現在計算機的體系結構里(寄存器、L1/L2 Cache、內存、硬盤)。

緩存對性能提升舉例
-
測試環境: 12 核 i7 3.2GHz 64GB 4T HDD
-
last_row 緩存性能對比 (select last_row(*) from stable 查詢語句 1000 次,統計查詢時間)

-
last 緩存性能對比 (select last(*) from stable 和 select last_row(*) from stable 查詢語句 1000 次,統計查詢時間)

-
開啟緩存性能比不開啟緩存提升將近 1 個數量級。緩存對系統性能提升還是很大的,所以,在使用 TDengine 時,可以根據自己的需求,打開或關閉開關
問題及改進優化方向
先來看問題,主要是兩點:
-
mnode 的 meta 數據全量加載,表數量很大時,內存占用大,啟動慢;
-
last 和 last_row 緩存啟動全量加載。
最后我們再來看一下優化方向:
-
全量加載改為動態加載;
-
預分配緩存大小,通過 LRU 等策略來更新數據;
-
qhandle 通過對象池管理,避免頻繁 calloc。
如果想了解更具體的實現細節,可以在GitHub上查看相關源代碼,也期待大家加入進來,一起改進TDengine!
關於作者
王明明,北京郵電大學畢業,主修方向為電子信息、模式識別和圖像處理。畢業后入職騰訊,先后在 TEG 魔王工作室卡牌游戲開發、騰訊地圖手圖后台開發、騰訊看點知識圖譜后台開發。對網絡編程、RPC 框架原理、Redis 緩存等技術有深入的研究。