流是一個有方向感的漢字,并且給人輕便迅捷的感覺。TDengine( Time Series Database ,TSDB) 產(chǎn)品最初的靈感之一,便是和“流”字相關:一臺物聯(lián)網(wǎng)設備便是一條數(shù)據(jù)流,十萬臺設備便是十萬條數(shù)據(jù)流。它們像溪河匯聚一樣,每秒每分源源不斷地流向了數(shù)據(jù)處理平臺。
面對這樣規(guī)模的大數(shù)據(jù)挑戰(zhàn),TDengine 選擇充分地利用時序數(shù)據(jù)本身的特點(可參考:https://mp.weixin.qq.com/s?__biz=MzIzNzg5MTcxNA==&mid=2247483956&idx=1&sn=86c55c40e935acd18835fec764fd7767&chksm=e8c0fbd9dfb772cf4e102a62498b034c6407cd9ea2c47d209b3e737800676cea7a108bf1d9d1&scene=21#wechat_redirect),來針對性地設計存儲引擎。最終的目標其實就是:高效持續(xù)地吞吐、消化這些數(shù)據(jù)流,讓數(shù)據(jù)能夠無延遲地產(chǎn)生價值。
可以說,我們追求的便是“流”一般的產(chǎn)品能力。而關于文章標題的答案,本文將從 TDengine 的存儲引擎的變化史說起:
由于認為時間序列擁有天然遞增屬性,所以在最早期的 1.6 版本中,TDengine 是不支持對亂序數(shù)據(jù)的處理的,當時亂序數(shù)據(jù)寫入表中后,系統(tǒng)會做報錯處理。但經(jīng)過用戶實際場景的磨練后,我們不得不把注意力聚焦在這個“害群之馬”的身上。在生產(chǎn)環(huán)境中,由于設備損壞,網(wǎng)絡延遲等原因,數(shù)據(jù)亂序到達是難免的。更關鍵的是,不管數(shù)據(jù)亂序與否,用戶都有權(quán)利自行決定是否處理它,這是一個產(chǎn)品應該具有的靈活度。
它讓理想中的數(shù)據(jù)流“亂”了,但我們卻又不能放棄它。
于是,我們立刻在 2.0 版本中增加了數(shù)據(jù)在內(nèi)存中的排序和硬盤中的數(shù)據(jù)子塊來支持亂序數(shù)據(jù)的處理,以此維持了數(shù)據(jù)的完整性和有序性。
但是這對于 2.0 版本的流式計算和訂閱來說,仍然有類似的麻煩。
當時的訂閱/流計算(連續(xù)查詢)是基于查詢引擎的產(chǎn)物,它依靠連續(xù)不斷地執(zhí)行 SQL 查詢結(jié)果作出實時反饋。以訂閱為例,每條符合要求被消費掉的數(shù)據(jù)的時間戳,都會被記錄下來,從而作為下次 SQL 查詢中時間范圍的起始點。這時,即便是新數(shù)據(jù)的時間戳只比這個記錄早一秒,也不會被 SQL 輪詢到。因此可以說, 2.0 的流計算(連續(xù)查詢)/訂閱同 1.6 一樣,它只處理了有序部分的數(shù)據(jù)。本質(zhì)上來說,由于 SQL 取到的只能是已經(jīng)入庫的數(shù)據(jù),所以這個階段的查詢行為是滯后的。
因此,我們決定在 3.0 再次進行優(yōu)化重構(gòu):
雖然數(shù)據(jù)的時間戳有亂序,但是他們到達數(shù)據(jù)庫的時間永遠分有先后,這組序列相當于“數(shù)據(jù)入庫時間”。不過這個“數(shù)據(jù)入庫時間” 不是時間戳,而是一個從 0 開始的整型數(shù)字,我們稱之為“版本號”,代表的是數(shù)據(jù)庫概念是“第 x 次的數(shù)據(jù)變更”。熟悉 WAL 概念的伙伴們都知道,TDengine 利用 WAL 技術(shù)來提供基本的數(shù)據(jù)可靠性:每一條 WAL 信息代表的是一次數(shù)據(jù)庫的變更(增刪改),所以每一條 WAL 信息都會有唯一的版本號。通過“版本號”,我們可以明確地告訴流計算/訂閱該數(shù)據(jù)是否為新增數(shù)據(jù),再通過對 WAL 的這組“版本號”創(chuàng)建索引,我們就可以快速定位要消費的數(shù)據(jù)了。
以訂閱為例:3.0 的訂閱引擎為時間驅(qū)動,它會解析每一條新寫入的 WAL 的消息內(nèi)容,然后判斷是否匹配 topic 的 sql 條件,如果匹配則直接把 WAL 的消息轉(zhuǎn)化成數(shù)據(jù)返回給用戶 。
除了用于訂閱,這組版本號還有很多十分重要的作用:
- 它本身核心的職責是 raft 日志的編號,用于多副本的數(shù)據(jù)同步;
- 把版本號下發(fā)給每行數(shù)據(jù),以追加寫入的形式完成數(shù)據(jù)的更新與刪除。
比如:某條 wal 消息的內(nèi)容是寫入一行數(shù)據(jù):
insert into d1 values ("2022-03-10 08:00:00.000",100);
那么這行數(shù)據(jù)就也擁有了這條 wal 消息的版本號(假設為 1) ,并且永久性地存儲在數(shù)據(jù)文件中。
解下來的一條 wal 消息的內(nèi)容是以相同時間戳更新這行數(shù)據(jù):
insert into d1 values ("2022-03-10 08:00:00.000",200);
消息的版本號便是 “2”,這條數(shù)據(jù)會以寫入的形式追加存儲。查詢的時候,在時間戳相同的情況下,通過比對版本號大小來選擇最新的數(shù)據(jù),因此我們得到的數(shù)據(jù)便是:”2022-03-10 08:00:00.000″,200。
刪除同理,被刪除的數(shù)據(jù)是取版本號較大的空數(shù)據(jù),這樣便可在不破壞原有數(shù)據(jù)文件結(jié)構(gòu)的情況下實現(xiàn)高效的刪除。以上部分具體實現(xiàn)細節(jié)可參考:https://mp.weixin.qq.com/s/imECB9dIFxZKeoaF3uoOgg
3.另外,當 follower 副本的數(shù)據(jù)落后,但 leader 上的 WAL 日志已經(jīng)消失的情況下,版本號還可以做數(shù)據(jù)文件的快照。只把增量的數(shù)據(jù)傳 輸過去,大大節(jié)約 data recovery 的時間。
通過這些大刀闊斧的重構(gòu),TDengine 完美地把上面幾大模塊縫合了起來:
- 解決了流計算、訂閱對于亂序數(shù)據(jù)處理的不支持;
- 讓刪除操作為邏輯刪除,解決了刪除操作的性能問題;
- 基于版本號引入了快照機制,大幅優(yōu)化了特定場景下的數(shù)據(jù)恢復的速度。
回到標題上,為何 TDengine 用戶應盡快切至 3.0 版本?答案已經(jīng)都寫在上面了。通過下沉到海量用戶實際場景中反復迭代優(yōu)化,從 1.6 到 2.0 再到 3.0, TDengine 從底層結(jié)構(gòu)上解決了最初設計的瑕疵,各個模塊之間結(jié)構(gòu)變得更加清晰,銜接也更加自然,這樣扎實的底層設計對產(chǎn)品未來的穩(wěn)定和性能提升所帶來的幫助都是巨大的。



互聯(lián)網(wǎng).png)



-1.png)











伙伴.png)
伙伴.png)



