一、為什么是 TDengine
首先,TDengine Database 符合我的審美。盡管我一直在關(guān)注時序數(shù)據(jù)庫(Time-Series Database,TSDB)這個領(lǐng)域,但直到遇見 TDengine 才算找到符合預(yù)期的產(chǎn)品。理想中的產(chǎn)品所采用的語言應(yīng)該自帶運行時,屬于強類型的編譯語言,這樣打包編譯后對環(huán)境的依賴比較小,用起來比較方便;代碼工程應(yīng)該盡可能精巧,便于理解掌握;產(chǎn)品運行起來要快,資源消耗相對較小。如果一個產(chǎn)品像一輛卡車,載重 10 噸,自重 5 噸,它顯然不是理想選項。以上解釋都是先射箭后畫靶,當(dāng)然 C 語言才是決定項,這是我在 emacs 環(huán)境中配置最完善的語言。
TDengine Database 的每個 Dnode 節(jié)點既負(fù)責(zé)存儲也負(fù)責(zé)計算,在時興“計算與存儲分離”的當(dāng)下,這個技術(shù)方案顯得有些另類。不過實踐才是檢驗真理的標(biāo)準(zhǔn),目前還沒看到明顯問題,我們會在 K8s 的 CSI 方案中繼續(xù)測試。
二、Lua連接器
我為 TDengine 編寫了 Lua 版的連接器,主要面向兩個用戶群體,一是 OpenResty(Nginx+Lua),另一個是 Skynet。這兩個產(chǎn)品也是我格外欣賞的兩款開源產(chǎn)品。經(jīng)過測試,在我的個人筆記本電腦(SSD 硬盤、8G RAM)上,基于 TDengine 2.0.18,用我自己編寫的 Lua 連接器單線程寫入只有 3 個列的記錄(時間戳、整數(shù)、tag),平均每秒可寫入 1 萬條,見下圖。我已提交了 benchmark 測試代碼,大家可以在自己的工作環(huán)境下測試感興趣的條目。

目前社區(qū)里看到的 Lua 連接器分別基于 Lua5.1 和 Lua5.3,這主要是因為 OpenResty 采用的是基于 5.1 版本的 LuaJIT,而 skynet 雖然跟隨 Lua 社區(qū)升級到了 Lua5.4,但我并沒用遇到兼容問題,所以只在本地針對 Lua5.4 的兼容性進(jìn)行了驗證,并沒有提交。
從 Lua 角度來看,Lua5.1 和 Lua5.2 及以上版本屬于兩個世界。這主要是因為 Lua5.2 版本上的一個重要改進(jìn)——“yieldable pcall and metamethods”。Lua5.2 這個改進(jìn)允許調(diào)用 C 函數(shù)時不馬上返回,而這是異步操作必須的特性。所以大家在基于 Lua5.1 API 的 OpenResty 社區(qū)里經(jīng)常看到有人在問如何解決“attempt to yield across C-call boundary”問題。
我在實現(xiàn)連接器時也必須面對這個問題,當(dāng)然也有其他 API 差異,所以編寫了兩個版本的代碼,而不是用編譯開關(guān)控制。事實上,有別于 OpenResty 過度依賴 LuaJIT,我更贊同云風(fēng)的觀點,跟隨主流社區(qū)升級版本帶來的綜合收益應(yīng)該高于在某個版本上定制。
OpenResty 用戶可以直接在 http 請求處理中通過 Lua 連接器訪問 TDengine Database,如同使用 MySQL 的體驗一樣,不需要交給服務(wù)器處理,整體架構(gòu)非常簡潔。由于在此 Lua 版本上只能實現(xiàn)同步訪問數(shù)據(jù)庫,出于性能考慮,我試驗了一個連接池,避免頻繁地建立然后釋放數(shù)據(jù)庫連接。很遺憾的是,Lua 的非搶占特性導(dǎo)致一段代碼未執(zhí)行完時不會釋放 CPU,所以并沒有機會處理其他請求,觀察到的 WaterMark 也一直是 1,這個問題如何解決,暫時還沒有結(jié)論。如果在目前基礎(chǔ)上想盡可能地多榨出一點性能來,我的建議是盡可能推遲從連接池里申請連接,并盡可能提前歸還連接。
Skynet 用戶也可以仿照 MySQL 的使用方式來使用 Lua 連接器,不過我建議遵從 Skynet 的建議,在 simpledb 這樣的服務(wù)中保持連接,做具體的請求處理工作。因為 Skynet 本身實現(xiàn)了比較完善的 Actor 模型,所以我并不確定目前的方案有沒有帶來瓶頸。如果同步訪問帶來瓶頸,可以嘗試一下異步調(diào)用,但因為 TDengine 關(guān)于異步的設(shè)計中要求前一個訪問結(jié)果返回后才能執(zhí)行下一個訪問,所以異步調(diào)用帶來的瓶頸會向哪里轉(zhuǎn)移,目前也不清楚。
盡管可能性很小,但是連接器不能回避數(shù)據(jù)庫連接失效的問題。幸好 TDengine 提供了一個心跳機制,用來檢測連接是否有效。目前實現(xiàn)的連接器還沒有完善這個功能,所以如果通往數(shù)據(jù)庫的鏈路失效,需要應(yīng)用重建連接。
在實際生產(chǎn)環(huán)境中,我們發(fā)現(xiàn) TDengine Dnode 向 Client 端返回的字符串類型的數(shù)據(jù)并沒有附加結(jié)尾符,而出于性能考慮,在向連接器返回數(shù)據(jù)時也沒有重新申請內(nèi)存做一次拷貝以追加結(jié)束符,進(jìn)而導(dǎo)致往數(shù)據(jù)庫中存入網(wǎng)絡(luò)類型 4G 和 WIFI,在查詢時返回“4GFI”這樣的結(jié)果。這是一個隱蔽性很強的默認(rèn)規(guī)則,其他語言連接器的設(shè)計者注意提前規(guī)避潛在問題。
三、自定義函數(shù)
自定義函數(shù)(UDF)能降低應(yīng)用的復(fù)雜度,或者實現(xiàn)預(yù)置查詢函數(shù)無法實現(xiàn)的功能,我判斷 Lua 是最適合承擔(dān)這個使命的語言。因為 Lua 的設(shè)計初衷就是與 C 語言集成,兩者堪稱倚天屠龍。
目前 TDengine 官方已經(jīng)實現(xiàn)了用戶自定義函數(shù)的框架,基于 Lua5.1 實現(xiàn)了基礎(chǔ)模型,并集成了 Lua5.1。因為 Lua 社區(qū)的分裂狀態(tài),預(yù)置的 Lua 開發(fā)庫給我的開發(fā)工作帶來一些小麻煩,用戶肯定不能同時使用兩個 Lua 版本,最終還是要在 TDengine 中同時集成 Lua5.1 和一個高版本(即將發(fā)布版本為 Lua5.4.4),靠宏開關(guān)選擇一個 Lua 版本參與編譯。
具體實現(xiàn)起來,需要分別為兩個版本的 C API 設(shè)計接口,Lua 每個版本升級都會帶來一些功能上的變化和升級,因此能否抽象出一套通用接口對 Lua 用戶屏蔽差異,對于這個問題的答案我是持比較悲觀的態(tài)度的。
四、展望
以上就是我在使用 TDengine 時的經(jīng)驗匯總。目前,連接器已部署在我們的生產(chǎn)環(huán)境上,并經(jīng)歷了兩次較大規(guī)模的生產(chǎn)活動,輕松完成了使命。接下來會在項目應(yīng)用中繼續(xù)完善上面提到的問題,支持用 Lua 實現(xiàn)UDF是我的下一個工作重點,這將進(jìn)一步降低應(yīng)用的復(fù)雜度。



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



-1.png)







證.png)


伙伴.png)
伙伴.png)
伙伴.png)



