# 存储策略选择

从1.5.0开始,平台支持在产品中配置数据存储策略。 不同的策略使用不同的数据存储方式来保存设备数据。

# 企业版与社区版存储策略支持说明

存储策略 企业版 社区版
ElasticSearch-行式存储
ElasticSearch-列式存储
TDEngine-行式存储
TDEngine-列式存储
InfluxDB-行式存储
InfluxDB-列式存储
ClickHouse-行式存储
ClickHouse-列式存储
Cassandra-行式存储
Cassandra-列式存储

# 存储策略说明

# ElasticSearch-行式存储

这是系统默认情况下使用的存储方案,使用elasticsearch存储设备数据。 每一个属性值都保存为一条索引记录。典型应用场景:设备每次只会上报一部分属性, 以及支持读取部分属性数据的时候。

优点: 灵活,几乎满足任意场景下的属性数据存储。

缺点: 设备属性个数较多时,数据量指数增长,可能性能较低。

警告

  1. 在确定好存储方案后,尽量不要跨类型进行修改,如将行式存储修改为列式存储,可能会导致数据结构错乱。
  2. 在创建物模型时,请根据存储策略判断好是否支持此类型以及ID格式。
# 索引结构
  1. 属性索引模版: properties_{productId}_template

mappings:

{
 "properties" : {
          "geoValue" : { // 地理位置值,物模型配置的类型为地理位置时,此字段有值
            "type" : "geo_point"
          },
          "productId" : { // 产品ID
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "objectValue" : { // 结构体值,物模型配置的类型为结构体时,此字段有值
            "type" : "nested"
          },
          "type" : { // 物模型中配置的属性类型
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "timeValue" : { //时间类型的值
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          },
          "deviceId" : { //设备ID
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "createTime" : { //平台记录数据的时间
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          },
          "property" : { //属性ID,与物模型属性ID一致
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "numberValue" : { //物模型中属性类型为数字相关类型时,此字段有值
            "type" : "double"
          },
          "id" : {
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "value" : { //通用值,所有类型都转为字符串
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "timestamp" : { //设备消息中的时间戳
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          }
        }
}
  1. 事件索引模版: event_{productId}_{eventId}_template

mappings:

{
   "properties" : {
          "productId" : {
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "createTime" : { //创建时间
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          },
        
          "id" : {
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "value" : {   //如果物模型中类型不是结构体(对象类型),则会有此字段并且类型与物模型类型相匹配
            "ignore_above" : 512,
            "type" : "keyword"
          },
          //如果物模型中类型是结构体(对象类型),会根据结构体配置的字段添加到索引中.


          "deviceId" : {
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "timestamp" : { //设备消息中的时间戳
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          }
        } 
}
  1. 设备日志索引模版: device_log_{productId}_template

mappings:

{
    "properties" : {
          "productId" : { //产品ID
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "createTime" : { //创建时间
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          },
          "id" : {
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "type" : { //日志类型
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "deviceId" : { //设备ID
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "content" : { //日志内容
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "timestamp" : { //设备消息中的时间戳
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          }
        }
}

# ElasticSearch-列式存储

使用elasticsearch存储设备数据。 一个属性作为一列,一条属性消息作为一条索引记录进行存储,适合设备每次都上报所有的属性值的场景。

优点: 在属性个数较多,并且设备每次都会上报全部属性时,性能更高。

缺点: 设备必须上报全部属性。

说明

默认情况下:读取属性、修改属性指令都必须返回全部属性数据。 如果无法返回全部数据,可以在消息中设置头partialProperties。如: message.addHeader("partialProperties",true), 用来标记是返回的部分数据。平台在保存数据前,将获取之前保存的其他属性然后一起保存。(使用此功能将降低系统的吞吐量)。

索引结构:

  1. 属性索引模版: properties_{productId}_template

mappings:

{
 "properties" : {
          "productId" : { // 产品ID
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "deviceId" : { //设备ID
            "ignore_above" : 512,
            "type" : "keyword"
          },
          "createTime" : { //平台记录数据的时间
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          },
          "id" : {
            "ignore_above" : 512,
            "type" : "keyword"
          },
          //根据物模型中配置的属性,一个属性对应一列
          //....

          "timestamp" : { //设备消息中的时间戳
            "format" : "epoch_millis||strict_date_hour_minute_second||strict_date_time||strict_date",
            "type" : "date"
          }
        }
}
  1. 事件和日志与默认-行式存储一致

# InfluxDB-行式存储

默认-行式存储行为一致,使用influxdb进行数据存储。

measurement说明:

  1. 属性: properties_{productId}
  2. 事件: event_{productId}_{eventId}
  3. 日志: device_log_{productId}

# InfluxDB-列式存储

默认-列式存储行为一致,使用influxdb进行数据存储。

measurement说明:

  1. 属性: properties_{productId}
  2. 事件: event_{productId}_{eventId}
  3. 日志: device_log_{productId}

# TDEngine-列式存储

默认-列式存储行为一致,使用TDEngine进行数据存储。

超级表说明:

  1. 属性: properties_{productId}
  2. 事件: event_{productId}_{eventId}
  3. 日志: device_log_{productId}

# ClickHouse-行式存储

默认-行式存储行为一致,使用ClickHouse进行数据存储。

说明

ClickHouse不支持大量并发查询,请根据实际情况选择。

# Cassandra-行式存储

默认-行式存储行为一致,使用Cassandra进行数据存储。

分区说明:

  1. 设备属性: (deviceId,property,partition)
  2. 设备事件: (deviceId,partition)
  3. 设备日志: (deviceId,partition)

partition规则:

通过jetlinks.device.storage.cassandra.partition-interval=MONTHS进行配置, 支持:MINUTESHOURSDAYSWEEKSMONTHSYEARSFOREVER

注意

  1. 在查询条件没有设置时间的时候,默认只会查询最近2个分区周期内的数据。 前端在查询时,建议默认指定时间范围,并且时间间隔不能与配置的分区周期相差太多,否则会影响查询性能.
  2. Cassandra仅支持使用滚动分页,跳转分页深度过大时会降低查询速度。执行count的性能也较低,建议查询时,使用滚动分页

# 自定义存储策略

在后台代码中实现org.jetlinks.pro.things.data.ThingsDataRepositoryStrategy接口,然后注入到Spring即可。

添加的存储策略将被注册到org.jetlinks.pro.things.data.DefaultThingsDataRepository中,并使用org.jetlinks.pro.things.data.ThingsDataCustomizer自定义表名逻辑。

# 记录设备最新数据到数据库

1.5.0企业版本中增加了记录最新设备数据到数据库中,以便进行更丰富的统计查询等操作。

通过配置文件开启:

jetlinks:
  device:
     storage:
         enable-last-data-in-db: true #是否将设备最新到数据存储到数据库

警告

开启后,设备的属性、事件的最新值将记录到表dev_lst_{productId}中。主键id为设备ID。 开启此功能可能降低系统的吞吐量。频繁写入数据库会加大IO的负荷。

# 物模型与数据库表字段说明

  1. 属性id即字段
  2. 当事件类型为结构体类型时,字段为:{eventId}_{结构体属性ID},否则为:{eventId}_value
  3. 在发布产品时,会自动创建或者修改表结构

# 数据类型说明

  1. 数字类型统一为number(32,物模型中配置的精度)类型
  2. 时间类型为timestamp
  3. 结构体(object)和array为Clob
  4. 设置了字符长度expands.maxLength大于2048时,为Clob
  5. 其他为varchar

危险

请勿频繁修改物模型类型,可能导致数据格式错误。

# 根据最新的设备数据查询设备数量等操作

在任何设备相关等动态查询条件中,使用自定义条件dev-latest来进行查询。例如:

{
"terms":[
   {
    "column":"id$dev-latest$demo-device" 
    ,"value":"temp1 > 10"  //查询表达式
   }
 ]
}

id$dev-latest$demo-device说明: id: 表示使用查询主表的id作为设备id dev-latest: 表示使用自定义条件dev-latest,此处为固定值 demo-device: 产品ID

temp1 > 10说明:

temp1最新值大于10。条件支持=><>=<=inbtwisnull等通用条件,支持嵌套查询。如: temp1 > 10 and (temp2 >30 or state = 1)

说明

所有参数和查询条件都经过了处理,没有SQL注入问题。请放心使用。