流是一個有方向感的漢字,并且給人輕便迅捷的感覺。TDengine( Time Series Database ,TSDB) 產品最初(chu)的(de)靈(ling)感之一(yi),便(bian)是(shi)和“流”字相關:一(yi)臺物聯網(wang)設備便(bian)是(shi)一(yi)條數(shu)(shu)(shu)據流,十萬臺設備便(bian)是(shi)十萬條數(shu)(shu)(shu)據流。它(ta)們像溪河(he)匯聚(ju)一(yi)樣,每(mei)秒每(mei)分源源不(bu)斷(duan)地(di)流向了數(shu)(shu)(shu)據處理平(ping)臺。
面對這樣規模的(de)大(da)數(shu)據(ju)挑戰,TDengine 選(xuan)擇(ze)充分地利用時序數(shu)據(ju)本身的(de)特點(dian)(可參(can)考:),來針對性地設計存(cun)儲(chu)引(yin)擎。最終的(de)目(mu)標其實就是:高效持續地吞吐(tu)、消(xiao)化這些(xie)數(shu)據(ju)流,讓數(shu)據(ju)能夠無延遲(chi)地產生價值。
可(ke)以說,我們追求(qiu)的(de)便是“流”一般的(de)產(chan)品能(neng)力(li)。而關(guan)于(yu)文章標題的(de)答案,本文將(jiang)從(cong) TDengine 的(de)存儲引擎的(de)變化(hua)史說起:
由于認為時間序列擁有天然遞增屬性,所以在最早期的 1.6 版本中,TDengine 是不支持對亂序數據的處理的,當時亂序數據寫入表中后,系統會做報錯處理。但經過用戶實際場景的磨練后,我們不得不把注意力聚焦在這個“害群之馬”的身上。在生產環境中,由于設備損壞,網絡延(yan)遲(chi)等原因,數(shu)(shu)據亂序到達是難免(mian)的。更(geng)關鍵的是,不管(guan)數(shu)(shu)據亂序與否,用戶都有權利自行決定是否處理(li)它,這(zhe)是一個產品應該具有的靈活(huo)度。
它讓理想中的數據流“亂”了,但我們卻又不能放棄它。
于是,我們立(li)刻在 2.0 版(ban)本(ben)中增加了(le)數據(ju)在內存(cun)中的排序和硬盤中的數據(ju)子塊來支持亂(luan)序數據(ju)的處理,以(yi)此維持了(le)數據(ju)的完整性(xing)和有序性(xing)。
但是這對(dui)于 2.0 版本的流式計算(suan)和訂閱(yue)來說,仍然有類似的麻(ma)煩。
當(dang)時(shi)的(de)訂(ding)閱(yue)/流(liu)計算(連(lian)續查(cha)詢(xun)(xun))是(shi)(shi)基于查(cha)詢(xun)(xun)引擎(qing)的(de)產物,它依靠連(lian)續不(bu)斷地(di)執行(xing) SQL 查(cha)詢(xun)(xun)結果作(zuo)出實時(shi)反饋。以訂(ding)閱(yue)為(wei)例,每條符合(he)要求被消費掉的(de)數(shu)(shu)(shu)據(ju)(ju)的(de)時(shi)間戳(chuo)(chuo),都會(hui)被記錄下來(lai),從而作(zuo)為(wei)下次 SQL 查(cha)詢(xun)(xun)中時(shi)間范圍的(de)起始點。這時(shi),即(ji)便是(shi)(shi)新數(shu)(shu)(shu)據(ju)(ju)的(de)時(shi)間戳(chuo)(chuo)只(zhi)比這個(ge)(ge)記錄早一(yi)(yi)秒,也不(bu)會(hui)被 SQL 輪詢(xun)(xun)到(dao)。因此(ci)可以說, 2.0 的(de)流(liu)計算(連(lian)續查(cha)詢(xun)(xun))/訂(ding)閱(yue)同(tong) 1.6 一(yi)(yi)樣(yang),它只(zhi)處理(li)了有序部分的(de)數(shu)(shu)(shu)據(ju)(ju)。本質上來(lai)說,由(you)于 SQL 取(qu)到(dao)的(de)只(zhi)能(neng)是(shi)(shi)已經入(ru)庫(ku)的(de)數(shu)(shu)(shu)據(ju)(ju),所以這個(ge)(ge)階(jie)段的(de)查(cha)詢(xun)(xun)行(xing)為(wei)是(shi)(shi)滯后的(de)。
因此,我們決定在 3.0 再次進行優化重構:
雖(sui)然數據(ju)(ju)(ju)(ju)(ju)的(de)(de)時(shi)(shi)(shi)間(jian)戳有亂(luan)序(xu),但是他們(men)到達數據(ju)(ju)(ju)(ju)(ju)庫的(de)(de)時(shi)(shi)(shi)間(jian)永(yong)遠(yuan)分有先后,這組序(xu)列相當于“數據(ju)(ju)(ju)(ju)(ju)入(ru)庫時(shi)(shi)(shi)間(jian)”。不(bu)過這個(ge)“數據(ju)(ju)(ju)(ju)(ju)入(ru)庫時(shi)(shi)(shi)間(jian)” 不(bu)是時(shi)(shi)(shi)間(jian)戳,而(er)是一個(ge)從 0 開始的(de)(de)整(zheng)型數字,我(wo)(wo)們(men)稱(cheng)之(zhi)為(wei)“版(ban)本號(hao)”,代表(biao)的(de)(de)是數據(ju)(ju)(ju)(ju)(ju)庫概念(nian)是“第 x 次的(de)(de)數據(ju)(ju)(ju)(ju)(ju)變更”。熟(shu)悉 WAL 概念(nian)的(de)(de)伙(huo)伴們(men)都知道,TDengine 利用 WAL 技(ji)術來提供基本的(de)(de)數據(ju)(ju)(ju)(ju)(ju)可(ke)靠性:每一條(tiao) WAL 信息(xi)代表(biao)的(de)(de)是一次數據(ju)(ju)(ju)(ju)(ju)庫的(de)(de)變更(增刪改),所以每一條(tiao) WAL 信息(xi)都會有唯(wei)一的(de)(de)版(ban)本號(hao)。通過“版(ban)本號(hao)”,我(wo)(wo)們(men)可(ke)以明(ming)確地告(gao)訴流(liu)計算/訂(ding)閱(yue)該(gai)數據(ju)(ju)(ju)(ju)(ju)是否為(wei)新增數據(ju)(ju)(ju)(ju)(ju),再通過對(dui) WAL 的(de)(de)這組“版(ban)本號(hao)”創(chuang)建索引,我(wo)(wo)們(men)就(jiu)可(ke)以快(kuai)速(su)定位要(yao)消費的(de)(de)數據(ju)(ju)(ju)(ju)(ju)了。
以訂閱為(wei)(wei)例:3.0 的(de)訂閱引擎為(wei)(wei)時(shi)間驅(qu)動,它會解析每一條(tiao)新(xin)寫入的(de) WAL 的(de)消息內容,然(ran)后判斷是否匹配 topic 的(de) sql 條(tiao)件,如果匹配則直(zhi)接把 WAL 的(de)消息轉化成數據返(fan)回給用(yong)戶(hu) 。
除了用于訂閱,這(zhe)組版本號(hao)還有很多十(shi)分(fen)重要的作用:
- 它本身核心的職責是 raft 日志的編號,用于多副本的數據同步;
- 把版本號下發給每行數據,以追加寫入的形式完成數據的更新與刪除。
比如:某條 wal 消(xiao)息的內容是寫(xie)入一行數據:
insert into d1 values ("2022-03-10 08:00:00.000",100);
那(nei)么(me)這行數據(ju)就也擁有了這條 wal 消(xiao)息的版(ban)本號(假設為 1) ,并且永久(jiu)性地存儲在數據(ju)文件中。
解下來的(de)一條 wal 消(xiao)息的(de)內(nei)容是(shi)以相同時(shi)間(jian)戳更新這行數據:
insert into d1 values ("2022-03-10 08:00:00.000",200);
消息(xi)的(de)(de)版(ban)本號便是 “2”,這(zhe)條數(shu)據會以(yi)寫入的(de)(de)形式追加(jia)存儲(chu)。查詢的(de)(de)時候,在時間戳相同的(de)(de)情況(kuang)下,通過比對(dui)版(ban)本號大小(xiao)來選擇最新的(de)(de)數(shu)據,因此我們得(de)到的(de)(de)數(shu)據便是:”2022-03-10 08:00:00.000″,200。
刪除(chu)同理,被刪除(chu)的(de)數據是取(qu)版本號較大的(de)空(kong)數據,這樣便可(ke)在不破壞原有數據文件(jian)結構的(de)情況下實現高(gao)效的(de)刪除(chu)。以上(shang)部分具體實現細節可(ke)參(can)考:
3.另外,當 follower 副(fu)本(ben)的數據(ju)落后,但 leader 上的 WAL 日志已經消失的情況下,版本(ben)號還可以做(zuo)數據(ju)文(wen)件(jian)的快(kuai)照。只把增量(liang)的數據(ju)傳 輸過去(qu),大大節約(yue) data recovery 的時間。
通過這些大(da)刀闊斧的(de)重構,TDengine 完美(mei)地把上(shang)面幾(ji)大(da)模塊(kuai)縫合了起來:
- 解決了流計算、訂閱對于亂序數據處理的不支持;
- 讓刪除操作為邏輯刪除,解決了刪除操作的性能問題;
- 基于版本號引入了快照機制,大幅優化了特定場景下的數據恢復的速度。
回到(dao)標(biao)題上(shang),為何 TDengine 用戶(hu)應盡快切至 3.0 版本?答案已經都(dou)寫在上(shang)面(mian)了。通過下(xia)沉到(dao)海(hai)量用戶(hu)實際場景中反復(fu)迭(die)代優化,從 1.6 到(dao) 2.0 再到(dao) 3.0, TDengine 從底(di)層結構上(shang)解(jie)決(jue)了最初設(she)計的瑕疵,各個模塊之(zhi)間結構變得(de)更加(jia)清晰,銜(xian)接也更加(jia)自然,這樣扎實的底(di)層設(she)計對產品(pin)未(wei)來(lai)的穩定和性能提升(sheng)所帶來(lai)的幫助都(dou)是巨(ju)大(da)的。


























