小 T 導讀:為了在數(shu)(shu)據采集項頻繁變動(dong)的情況下保證用戶仍然能夠順利地完成數(shu)(shu)據記錄(lu)工(gong)作(zuo),TDengine 提供了三(san)種(zhong)無模式(shi)寫(xie)入(ru)協(xie)(xie)議(yi)(yi),分別是 InfluxDB Line 協(xie)(xie)議(yi)(yi)、OpenTSDB Telnet 協(xie)(xie)議(yi)(yi)和 OpenTSDB JSON 格式(shi)協(xie)(xie)議(yi)(yi)。本文將對無模式(shi)寫(xie)入(ru)方(fang)式(shi)的主要處(chu)(chu)理邏輯(ji)、映射(she)規(gui)則與變更處(chu)(chu)理等進行分析(xi),便于用戶理解(jie)與使用。
通常來說,物聯網應用常會采集比較多的數據項,用于實現智能控制、業務分析、設備監控等功能。但在此過程中,由于應用邏輯的版本升級,或者設備自身的硬件調整等原因,數據采集項可能較為頻繁地出現變動。這也是時序數據庫(Time Series Database,TSDB)需(xu)要應對的一個(ge)挑戰。
為了在這種情況下順利完成數據記錄工作,TDengine 時序數據庫 提供了 Schemaless 寫(xie)入方式,讓用(yong)戶(hu)(hu)可以省略(lve)掉預先創(chuang)建超級表/子表的(de)步驟,憑借數(shu)據寫(xie)入接口能夠自動創(chuang)建與數(shu)據對應的(de)存儲結構。在必要時,Schemaless 將自動增加必要的(de)數(shu)據列,保證(zheng)用(yong)戶(hu)(hu)寫(xie)入的(de)數(shu)據能夠被正(zheng)確存儲。
值得一提(ti)的(de)(de)是,通(tong)過(guo)無(wu)模式(shi)寫入(ru)(ru)方(fang)式(shi)建立的(de)(de)超級表(biao)(biao)及(ji)其(qi)對應的(de)(de)子(zi)表(biao)(biao),與通(tong)過(guo) SQL 直接(jie)(jie)建立的(de)(de)超級表(biao)(biao)和(he)子(zi)表(biao)(biao)完(wan)全沒有區別,你也可以通(tong)過(guo) SQL 語句直接(jie)(jie)向(xiang)其(qi)中(zhong)寫入(ru)(ru)數據。但需(xu)要(yao)注意(yi),通(tong)過(guo)無(wu)模式(shi)寫入(ru)(ru)方(fang)式(shi)建立的(de)(de)表(biao)(biao),其(qi)表(biao)(biao)名是基(ji)于標簽值按照固(gu)定的(de)(de)映(ying)射規則(ze)生成的(de)(de),所(suo)以無(wu)法(fa)明確地進行表(biao)(biao)意(yi),缺乏可讀性。
無模式寫入行協議
TDengine 的(de)(de)(de)無(wu)模式寫(xie)入行協(xie)議兼容 InfluxDB 的(de)(de)(de)行協(xie)議(Line Protocol)、OpenTSDB 的(de)(de)(de) telnet 行協(xie)議、OpenTSDB 的(de)(de)(de) JSON 格(ge)式協(xie)議。但(dan)是(shi)使用這三種(zhong)協(xie)議的(de)(de)(de)時候,需(xu)要在(zai) API 中指定輸入內容使用解析協(xie)議的(de)(de)(de)標準。
對于 InfluxDB、OpenTSDB 的標準寫入協議請參考《在 TDengine 中如何高效寫入?四種寫入方式提效大全》。下(xia)面首先(xian)以 InfluxDB 的行協議為基礎,介紹(shao) TDengine 擴展的協議內容,允許用戶采用更加(jia)精細(xi)的方(fang)式控(kong)制(超級表)模式。
Schemaless 采用一(yi)個字(zi)符(fu)串來表達一(yi)個數(shu)據行(可以向寫入(ru) API 中一(yi)次傳入(ru)多行字(zi)符(fu)串來實現多個數(shu)據行的(de)批(pi)量寫入(ru)),其格式約(yue)定如下:
measurement,tag_set field_set timestamp
其中:
- measurement 將作為數據表名。它與 tag_set 之間使用一個英文逗號來分隔。
- tag_set 將作為標簽數據,其格式形如
<tag_key>=<tag_value>,<tag_key>=<tag_value>,也即可以使用英文逗號來分隔多個標簽數據。它與 field_set 之間使用一個半角空格來分隔。 - field_set 將作為普通列數據,其格式形如
<field_key>=<field_value>,<field_key>=<field_value>,同樣是使用英文逗號來分隔多個普通列的數據。它與 timestamp 之間使用一個半角空格來分隔。 - timestamp 即本行數據對應的主鍵時間戳。
tag_set 中所有的(de)數(shu)據(ju)(ju)自(zi)動轉化為 nchar 數(shu)據(ju)(ju)類型,并不需要使用雙引(yin)號(”)。在無模式寫入數(shu)據(ju)(ju)行協議中,field_set 中的(de)每個數(shu)據(ju)(ju)項都需要對自(zi)身的(de)數(shu)據(ju)(ju)類型進行描述。具體來(lai)說:
- 如果兩邊有英文雙引號,表示 BINARY(32) 類型。例如
"abc"。 - 如果兩邊有英文雙引號而且帶有 L 前綴,表示 NCHAR(32) 類型。例如
L"報錯信息"。 - 對空格、等號(=)、逗號(,)、雙引號(”),前面需要使用反斜杠(\)進行轉義(都指的是英文半角符號)。
- 數值類型將通過后綴來區分數據類型,如后綴為無或 f64,映射的數據類型為 double;后綴為 f32,映射為 float,可移步 了解更多的后綴及映射類型。
- t, T, true, True, TRUE, f, F, false, False 將直接作為 BOOL 型來處理。
例(li)如如下數據(ju)(ju)行表示(shi):向名為(wei) st 的超級(ji)表下的 t1 標(biao)簽(qian)為(wei) “3”(NCHAR)、t2 標(biao)簽(qian)為(wei) “4”(NCHAR)、t3 標(biao)簽(qian)為(wei) “t3″(NCHAR)的數據(ju)(ju)子表,寫入 c1 列(lie)為(wei) 3(BIGINT)、c2 列(lie)為(wei) false(BOOL)、c3 列(lie)為(wei) “passit”(BINARY)、c4 列(lie)為(wei) 4(DOUBLE)、主鍵時(shi)間戳為(wei) 1626006833639000000 的一(yi)行數據(ju)(ju)。
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4f64 1626006833639000000
需要注意的是(shi),如果描述數據類型(xing)后綴時使用了錯(cuo)誤的大(da)小寫,或者為數據指定的數據類型(xing)有(you)誤,均可能引發報錯(cuo)提(ti)示而導致數據寫入失敗。
無模式寫入的主要處理邏輯
無模式寫入使用如(ru)下規(gui)則來生成子表名(ming):首先將 measurement 的(de)名(ming)稱(cheng)和(he)標簽的(de) key 和(he) value 組合成為如(ru)下的(de)字符串——
"measurement,tag_key1=tag_value1,tag_key2=tag_value2"
需要注意的(de)(de)是,這(zhe)里的(de)(de) tagkey1,tag_key2 并不(bu)是用戶輸入的(de)(de)標(biao)簽的(de)(de)原始順序,而是使用了標(biao)簽名(ming)稱按照字(zi)符(fu)串升序排列(lie)后的(de)(de)結(jie)果,所以(yi),tag_key1 并不(bu)是在行協議(yi)中(zhong)輸入的(de)(de)第(di)一個標(biao)簽。 排列(lie)完成(cheng)以(yi)后計(ji)算該字(zi)符(fu)串的(de)(de) MD5 散列(lie)值為 “md5_val”,我們將計(ji)算的(de)(de)結(jie)果與字(zi)符(fu)串組合生成(cheng)表(biao)名(ming):“t_md5_val”,其中(zhong)的(de)(de) “t” 是固定(ding)的(de)(de)前綴,每(mei)個通過該映射(she)關系自動生成(cheng)的(de)(de)表(biao)都具(ju)有該前綴。
用(yong)(yong)戶可以通過在 taos.cfg 里配(pei)置 smlChildTableName 參數來指定生成的(de)表名(ming)。 舉例(li)如下:配(pei)置 smlChildTableName=tname 插(cha)入數據(ju)為(wei) st,tname=cpu1,t1=4 c1=3 1626006833639000000,則創建的(de)表名(ming)為(wei) cpu1,注(zhu)意如果多行(xing)數據(ju) tname 相同,但是后(hou)面(mian)的(de) tag_set 不同,則使用(yong)(yong)第一行(xing)自動建表時(shi)指定的(de) tag_set,其他(ta)的(de)行(xing)會忽略。
此(ci)外,在處理行數據(ju)時(shi),其他原(yuan)則如下所(suo)示:
- 如果解析行協議獲得的超級表不存在,則會自動創建這個超級表(不建議手動創建超級表,不然插入數據可能異常)。
- 如果解析行協議獲得子表不存在,則 Schemaless 會按照步驟 1 或 2 確定的子表名來創建子表。
- 如果數據行中指定的標簽列或普通列不存在,則在超級表中增加對應的標簽列或普通列(只增不減)。
- 如果超級表中存在一些標簽列或普通列未在一個數據行中被指定取值,那么這些列的值在這一行中會被置為 NULL。
- 對 BINARY 或 NCHAR 列,如果數據行中所提供值的長度超出了列類型的限制,自動增加該列允許存儲的字符長度上限(只增不減),以保證數據的完整保存。
- 整個處理過程中遇到的錯誤會中斷寫入過程,并返回錯誤代碼。
- 為了提高寫入的效率,默認假設同一個超級表中 field_set 的順序是一樣的(第一條數據包含所有的 field,后面的數據按照這個順序),如果順序不一樣,需要配置參數 smlDataFormat 為 false,否則,數據寫入按照相同順序寫入,庫中數據會異常。
注意,無(wu)模式所有的處理邏輯仍會遵循 TDengine 對數據結構(gou)的底層限(xian)制,例如每行數據的總長度不能超過 16KB。這(zhe)方面的具(ju)體限(xian)制約(yue)束(shu)請參見 TDengine SQL 邊界限(xian)制()。
數據模式映射規則與變更處理
以 InfluxDB 行(xing)協議(yi)為(wei)(wei)例(li),其數(shu)(shu)據(ju)(ju)(ju)如(ru)何映射(she)成為(wei)(wei)具有模(mo)式(shi)的數(shu)(shu)據(ju)(ju)(ju)?從上(shang)文中(zhong)我(wo)們了解到,每個行(xing)協議(yi)中(zhong)數(shu)(shu)據(ju)(ju)(ju) measurement 映射(she)為(wei)(wei)超(chao)級表名(ming)稱(cheng)(cheng),tag_set 中(zhong)的標簽(qian)名(ming)稱(cheng)(cheng)為(wei)(wei)數(shu)(shu)據(ju)(ju)(ju)模(mo)式(shi)中(zhong)的標簽(qian)名(ming),field_set 中(zhong)的名(ming)稱(cheng)(cheng)為(wei)(wei)列名(ming)稱(cheng)(cheng)。以如(ru)下數(shu)(shu)據(ju)(ju)(ju)為(wei)(wei)例(li),說明映射(she)規則:
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4f64 1626006833639000000
該(gai)行數(shu)據映(ying)(ying)射生成(cheng)一個(ge)(ge)超級(ji)表:st, 其包含了 3 個(ge)(ge)類型為(wei) nchar 的(de)標簽,分別(bie)是:t1, t2, t3;五(wu)個(ge)(ge)數(shu)據列,分別(bie)是 ts(timestamp),c1 (bigint),c3(binary),c2 (bool), c4 (bigint)。映(ying)(ying)射成(cheng)為(wei)如下 SQL 語句:
create stable st (_ts timestamp, c1 bigint, c2 bool, c3 binary(6), c4 bigint) tags(t1 nchar(1), t2 nchar(1), t3 nchar(2))
在數(shu)據(ju)模(mo)式(shi)變(bian)更處理中,不(bu)同行(xing)數(shu)據(ju)寫(xie)入情況下(xia),對于(yu)數(shu)據(ju)模(mo)式(shi)的(de)(de)影響也不(bu)同。在使用行(xing)協(xie)議寫(xie)入一(yi)個(ge)明確(que)標識(shi)的(de)(de)字(zi)段類(lei)(lei)型(xing)時,后續再(zai)更改該字(zi)段的(de)(de)類(lei)(lei)型(xing)定義,會出現明確(que)的(de)(de)數(shu)據(ju)模(mo)式(shi)錯誤(wu)(wu),即會觸發寫(xie)入 API 報告錯誤(wu)(wu)。如(ru)下(xia)所示(shi):
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4 1626006833639000000
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4i 1626006833640000000
第(di)一(yi)行(xing)(xing)的數據類型(xing)映射將 c4 列(lie)定義為(wei) Double, 但是第(di)二行(xing)(xing)的數據又通過數值后(hou)綴(zhui)方式(shi)聲明(ming)(ming)該(gai)列(lie)為(wei) BigInt, 由此會(hui)(hui)觸發(fa)無(wu)模式(shi)寫(xie)入(ru)的解析(xi)錯誤。如果列(lie)前面(mian)的行(xing)(xing)協議將數據列(lie)聲明(ming)(ming)為(wei)了 binary, 后(hou)續(xu)的要求長(chang)度(du)更長(chang)的 binary 長(chang)度(du),此時(shi)會(hui)(hui)觸發(fa)超級(ji)表模式(shi)的變(bian)更。
st,t1=3,t2=4,t3=t3 c1=3i64,c5="pass" 1626006833639000000
st,t1=3,t2=4,t3=t3 c1=3i64,c5="passit" 1626006833640000000
第一行(xing)(xing)中行(xing)(xing)協議解析會聲明 c5 列是(shi)一個 binary(4)的(de)字(zi)段,第二次行(xing)(xing)數據(ju)寫入會提(ti)取列 c5 仍然(ran)是(shi) binary 列,但是(shi)其寬(kuan)(kuan)度(du)(du)為 6,此時需要(yao)將 binary 的(de)寬(kuan)(kuan)度(du)(du)增加(jia)到(dao)能夠(gou)容納新字(zi)符串(chuan)的(de)寬(kuan)(kuan)度(du)(du)。
st,t1=3,t2=4,t3=t3 c1=3i64 1626006833639000000
st,t1=3,t2=4,t3=t3 c1=3i64,c6="passit" 1626006833640000000
第(di)二行(xing)數據相對于第(di)一行(xing)來(lai)說增(zeng)加了一個(ge)列 c6,類型(xing)為(wei) binary(6)。那么此(ci)時會自動增(zeng)加一個(ge)列 c6, 類型(xing)為(wei) binary(6)。
寫在最后
受(shou)篇幅所限,關于無模式寫(xie)入的(de)時(shi)間分辨率識別、寫(xie)入完整性、錯誤碼等內容(rong)請(qing)參(can)考(kao) 。如有更(geng)多問(wen)題,歡迎加入 TDengine 用戶交流群,屆時(shi)將會有專業的(de)技術人員為你解惑(huo)。



























