

CREATE STABLE IF NOT EXISTS model_custom_property_00b_int (ts timestamp,param BIGINT) TAGS (product_id BINARY(50),device_name BINARY(50),property_type BINARY(50));


CREATE STABLE IF NOT EXISTS model_custom_property_00b_int (ts timestamp,param BIGINT) TAGS (product_id BINARY(50),device_name BINARY(50),property_type BINARY(50)); CREATE STABLE IF NOT EXISTS model_custom_property_00b_float (ts timestamp,param DOUBLE) TAGS (product_id BINARY(50),device_name BINARY(50),property_type BINARY(50)); CREATE STABLE IF NOT EXISTS model_custom_property_00b_enum (ts timestamp,param SMALLINT) TAGS (product_id BINARY(50),device_name BINARY(50),property_type BINARY(50)); CREATE STABLE IF NOT EXISTS model_custom_property_00b_timestamp (ts timestamp,param TIMESTAMP) TAGS (product_id BINARY(50),device_name BINARY(50),property_type BINARY(50)); CREATE STABLE IF NOT EXISTS model_custom_property_00b_bool (ts timestamp,param BOOL) TAGS (product_id BINARY(50),device_name BINARY(50),property_type BINARY(50)); CREATE STABLE IF NOT EXISTS model_custom_property_00b_string (ts timestamp,param BINARY(5000)) TAGS (product_id BINARY(50),device_name BINARY(50),property_type BINARY(50));
CREATE TABLE IF NOT EXISTS device_property_00b_device1_int USING model_custom_property_00b_int TAGS('00b','device1','int');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_enum USING model_custom_property_00b_enum TAGS('00b','device1','enum');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_bool USING model_custom_property_00b_bool TAGS('00b','device1','bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_string USING model_custom_property_00b_string TAGS('00b','device1','string');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_float USING model_custom_property_00b_float TAGS('00b','device1','float');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_timestamp USING model_custom_property_00b_timestamp TAGS('00b','device1','timestamp');
2.结构体类型:拥有多个字段并将物模型进行整体抽象,映射为多列模式的超级表。物模型定义示例如下图所示。

CREATE STABLE IF NOT EXISTS model_custom_property_00b_struct (ts timestamp, latitude DOUBLE,longitude DOUBLE) TAGS (product_id BINARY(50),device_name BINARY(50),property_type BINARY(50));
|
字段 |
类型 |
说明 |
|
model_custom_property_00b_struct |
超级表名 |
组成为 模型_物模型的类型(自定义物模型,通用物模型)_属性_产品ID_属性名称 |
|
ts |
表字段 |
时间戳,主键 |
|
latitude |
参数 |
结构体类型中的经度 |
|
longitude |
参数 |
结构体类型中的纬度 |
|
product_id |
产品id |
设备所属的产品ID |
|
device_name |
设备名 |
同时也叫sn |
|
property_type |
属性的类型 |
在获取数据的时候方便序列化数据 |
CREATE TABLE IF NOT EXISTS device_property_00b_device1_struct USING model_custom_property_00b_struct TAGS('00b','device1','struct');
3.数组类型: 数组类型在物联网平台中较为特殊,传统平台中的数组无法单独操作某一位。例如,如果想单独修改开关10的状态,必须传递完整的数组(如:[0,1,1,0,1,0,1,1,0,1])来进行控制,这种方式并不符合现实世界的需求。联犀则对数组进行了扩展,支持下角标访问。比如,要修改开关 10 的状态,只需传递
"switch.10": 1 即可。接下来,让我们来看一下联犀是如何处理这种数据结构的。物模型定义示例如下图所示。

CREATE STABLE IF NOT EXISTS model_custom_property_00b_switchg (ts timestamp,param BOOL) TAGS (product_id BINARY(50),device_name BINARY(50),_num BIGINT,property_type BINARY(50));
|
字段 |
类型 |
说明 |
|
model_custom_property_00b_switchg |
超级表名 |
组成为 模型_物模型的类型(自定义物模型,通用物模型)_属性_产品ID_属性名称 |
|
ts |
表字段 |
时间戳,主键 |
|
param |
参数 |
数组每一位的数据都存于此 |
|
_num |
数组的下角标 |
表示数据属于哪一位 |
|
product_id |
产品id |
设备所属的产品ID |
|
device_name |
设备名 |
同时也叫sn |
|
property_type |
属性的类型 |
在获取数据的时候方便序列化数据 |
_num 字段,用来标识数组的下标。这种设计的好处在于,即使数组长度达到 1000,依然只需定义一个超级表。虽然每个设备的普通表需要定义 1000 个,但由于是由一个超级表进行管理,整体管理变得更加简便。接下来,我们来看看每个设备创建的普通表是什么样的。CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_0 USING model_custom_property_00b_switchg TAGS('00b','device1',0,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_1 USING model_custom_property_00b_switchg TAGS('00b','device1',1,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_2 USING model_custom_property_00b_switchg TAGS('00b','device1',2,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_3 USING model_custom_property_00b_switchg TAGS('00b','device1',3,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_4 USING model_custom_property_00b_switchg TAGS('00b','device1',4,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_5 USING model_custom_property_00b_switchg TAGS('00b','device1',5,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_6 USING model_custom_property_00b_switchg TAGS('00b','device1',6,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_7 USING model_custom_property_00b_switchg TAGS('00b','device1',7,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_8 USING model_custom_property_00b_switchg TAGS('00b','device1',8,'bool');
CREATE TABLE IF NOT EXISTS device_property_00b_device1_switchg_9 USING model_custom_property_00b_switchg TAGS('00b','device1',9,'bool');
INSERT INTO tb_name [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)] [(field1_name, ...)] VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path [tb2_name [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)] [(field1_name, ...)] VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path ...]; INSERT INTO tb_name [(field1_name, ...)] subquery
INSERT INTO d21001 USING meters TAGS ('California.SanFrancisco', 2) VALUES ('2021-07-13 14:06:34.630', 10.2, 219, 0.32) ('2021-07-13 14:06:35.779', 10.15, 217, 0.33)
d21002 USING meters (groupId) TAGS (2) VALUES ('2021-07-13 14:06:34.255', 10.15, 217, 0.33)
d21003 USING meters (groupId) TAGS (2) (ts, current, phase) VALUES ('2021-07-13 14:06:34.255', 10.27, 0.31);
d21003 USING meters (groupId) TAGS (2) (ts, current, phase) VALUES ('2021-07-13 14:06:34.255', 10.27, 0.31) 格式的插入语句。

type Td struct {
*sql.DB
}
type ExecArgs struct {
Query string
Args []any
}
var (
td = Td{}
once = sync.Once{}
insertChan = make(chan ExecArgs, 1000)
)
const (
asyncExecMax = 200 //异步执行sql最大数量
asyncRunMax = 40
)
func NewTDengine(DataSource conf.TSDB) (TD *Td, err error) {
once.Do(func() {
if DataSource.Driver == "" {
DataSource.Driver = "taosWS"
}
td.DB, err = sql.Open(DataSource.Driver, DataSource.DSN)
if err != nil {
return
}
td.DB.SetMaxIdleConns(50)
td.DB.SetMaxOpenConns(50)
td.DB.SetConnMaxIdleTime(time.Hour)
td.DB.SetConnMaxLifetime(time.Hour)
_, err = td.Exec("create database if not exists ithings;")
if err != nil {
return
}
for i := 0; i < asyncRunMax; i++ {
utils.Go(context.Background(), func() {
td.asyncInsertRuntime()
})
}
})
if err != nil {
logx.Errorf("TDengine 初始化失败,err:%v", err)
}
return &td, err
}
func (t *Td) asyncInsertRuntime() {
r := rand.Intn(1000)
tick := time.Tick(time.Second/2 + time.Millisecond*time.Duration(r))
execCache := make([]ExecArgs, 0, asyncExecMax*2)
exec := func() {
if len(execCache) == 0 {
return
}
sql, args := t.genInsertSql(execCache...)
var err error
for i := 3; i > 0; i-- { //三次重试
_, err = t.Exec(sql, args...)
if err == nil {
break
}
}
if err != nil {
logx.Error(err)
}
execCache = execCache[0:0] //清空切片
}
for {
select {
case _ = <-tick:
exec()
case e := <-insertChan:
execCache = append(execCache, e)
if len(execCache) > asyncExecMax {
exec()
}
}
}
}
func (t *Td) AsyncInsert(query string, args ...any) {
insertChan <- ExecArgs{
Query: query,
Args: args,
}
}
func (t *Td) genInsertSql(eas ...ExecArgs) (query string, args []any) {
qs := make([]string, 0, len(eas))
as := make([]any, 0, len(eas))
for _, e := range eas {
qs = append(qs, e.Query)
as = append(as, e.Args...)
}
return fmt.Sprintf("insert into %s;", strings.Join(qs, " ")), as
}
func (d *DeviceDataRepo) getPropertyArgFuncSelect(
ctx context.Context,
filter msgThing.FilterOpt) (sq.SelectBuilder, error) {
schemaModel, err := d.getSchemaModel(ctx, filter.ProductID)
if err != nil {
return sq.SelectBuilder{}, err
}
p, ok := schemaModel.Property[filter.DataID]
if !ok {
return sq.SelectBuilder{}, errors.Parameter.AddMsgf("dataID:%s not find", filter.DataID)
}
var (
sql sq.SelectBuilder
)
if p.Define.Type == schema.DataTypeStruct {
sql = sq.Select("FIRST(ts) AS ts", d.GetSpecsColumnWithArgFunc(p.Define.Specs, filter.ArgFunc))
} else {
sql = sq.Select("FIRST(ts) AS ts", fmt.Sprintf("%s(param) as param", filter.ArgFunc))
}
if filter.Interval != 0 {
sql = sql.Interval("?a", filter.Interval) //TDengine特有语法
}
if len(filter.Fill) > 0 {
sql = sql.Fill(filter.Fill)//TDengine特有语法
}
return sql, nil
}
请求参数:


driver-go/taosWS/connection.go 文件中。联犀打印的日志如下:

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70014783/viewspace-3056894/ ,如需转载,请注明出处,否则将追究法律责任。