@


前言

我们可以将设备上行数据存储到关系型数据库中,我们需要两张带有时间戳的表(最新数据表历史数据表),历史数据表存储所有设备上报的数据,最新数据表需要存储设备最新一条上报数据,这条最新数据相当于设备的当前状态。然后展示的时候只展示最新一条数据的状态,报表查询可以按照设备id和时间从历史数据表查询汇总。

这样是可以的,但是我们的最新数据表需要被频繁的更新,数据量少的时候没问题。但数据量大,并发高的时候就会出现问题。

1、存储成本:数据不会被压缩,导致占用存储资源。

2、维护成本:单表数据量太大时,需要人工分库分表。

3、写入性能:单机写入吞吐量难以满足大量上行数据的写入需求,数据库存在性能瓶颈。

4、查询性能:数据量太大导致查询性能受到影响。

分析

我们可以采用时序库来解决上述问题,首先来了解一下什么是时序数据。时序数据是按照时间维度进行索引的数据,它记录了某个被测量实体在一定时间范围内,每个时间点上的一组测试值。传感器上传的室内PM2.5和甲醛数据、净水器传感器当前的TDS值、计算机系统的监控数据等,都属于时序数据,时序数据有如下特点:

1、数据量较大,写入操作是持续且平稳的,而且写多读少。

2、只有写入操作,几乎没有更新操作,比如去修改传感器的历史数据,是没有意义的。

3、没有随机删除,即使删除也是按照时间范围进行删除。删除某一个时间点的数据没有意义,但是删除2年前的数据是有意义的。

4、数据实时性和时效性强,数据随着时间的推移不断追加,旧数据很快失去意义。

5、大部分以时间和实体为维度进行查询,很少以测试值为维度查询,比如用户会查询某个时间段的温度数据,但是很少会去查询温度高于多少度的数据记录。

显然IoT的业务是符合使用时序库的场景的。

序数据库就是用来存储时序数据的数据库,时序数据库相较于传统的关系型数据和非关系型数据库而言,专门优化了对时序数据的存储,开源的时序数据库有InfluxDB OpenTSDB、TimeScaleDB 等。本文以InfluxDB数据库进行演示。

时序数据库有如下几个概念。

1.Metric:度量,相当于关系型数据库中的表(table)。

2.Data Point:数据点,相当于关系型数据库的中的行(row)。

3.Timestamp:时间戳,数据点生成时的时间戳。

4.Field:测量值,比如温度和湿度、PM2.5等。

5.Tag:标签,用于标识数据点,通常用来标识数据点的来源,比如温度和湿度数据来自哪个房间,哪个设备,可以当作关系型数据库表的主键。

如下图,度量为 Wind,每一个数据点都具有一个 timestamp,两个 field:direction 和 speed,两个 tag:sensor、city。它的第一行和第三行,存放的都是 sensor 号码为 95D8-7913 的设备,属性城市是上海。随着时间的变化,风向和风速都发生了改变,风向从 23.4 变成 23.2;而风速从 3.4 变成了 3.3。



图片来自网络

实施步骤

时序库的安装

安装参考官方文档,为了方便,我这里采用docker安装

docker run --name influxdb -p 8086:8086 influxdb:2.7.0

https://docs.influxdata.com/influxdb/v2.7/install/

我们打开 服务器ip:8086 可以看到它自带的管理界面,我们首先创建用户名密码,组织、以及Bucket的名称。

这里的bucket "IoTDemos" 相当于数据库的名称



我们记录一下这个Token,一会连接influxdb需要,相当于账号密码

解决playload没有时间戳问题

对于时序库来讲,时间戳是非常重要的,但是我们拿到的playload并没有时间戳(MQTTNet包我没有找到拿时间戳的方法)。

所以我们需要在mqtt上想办法,让设备上报数据的时候,mqtt自动添加时间戳到playload中。

1、我们在数据集成->规则中新建一条规则名称为"Add_Ts"。SQL编写如下

SELECT
*,
now_timestamp('millisecond') as payload.Ts
FROM
"topic/#"

topic/# 代表消息发布到"topic/#"主题的事件

now_timestamp函数返回当前时间的 Unix 时间戳,我们将时间戳写入到payload的Ts属性中,关于更多内置SQL函数,请参考官方文档

https://www.emqx.io/docs/zh/v5.0/data-integration/rule-sql-builtin-functions.html

2、我们打开下面的调试,模拟设备上报一条数据,可以看到这条规则帮我们加入了时间戳。

3、然后我们还需要处理添加了时间戳的处理结果,我们在右侧添加一个动作,选择消息重发布,将刚刚添加了时间戳的消息重发到一个新的Topic上,我们使用topic/dp,并在playload中添加${payload},这样我们就修改了playload中的信息,添加了我们需要的时间戳,当然,我们Hub订阅的消息也需要对应修改,添加/dp后缀。



4、首先我们先修改MASA.IoT.Hub的配置文件,Topic添加"/dp"后缀

  "MqttSetting": {
...
"Topic": "$share/IotHub/topic/+/dp"
},

5、CallbackAsync中,因为我们设备名称是从Topic截取的,也要对应修改一下。

    private async Task CallbackAsync(MqttApplicationMessageReceivedEventArgs e)
{
var deviceDataPointStr = System.Text.Encoding.Default.GetString(e.ApplicationMessage.PayloadSegment); Console.WriteLine(deviceDataPointStr);
var pubSubOptions = new PubSubOptions
{
//修改一下获取设备名称的方式
DeviceName = e.ApplicationMessage.Topic[6..^3],
Msg = deviceDataPointStr,
PubTime = new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds(),
TrackId = Guid.NewGuid()
};
...
}

代码编写

解决完时间戳的问题,我们就可以编写代码向InfluxDB中写入数据了,我们首先在Infrastructure文件夹下创建ITimeSeriesDbClient接口和TimeSeriesDbClient类,使用接口也方便我们日后更换其他的时序库。

这里使用了InfluxDB.Client包。

ITimeSeriesDbClient.cs

namespace MASA.IoT.Core.Infrastructure
{
public interface ITimeSeriesDbClient
{
bool WriteMeasurement<T>(T measurement);
}
}

TimeSeriesDbClient.cs

using InfluxDB.Client;
using InfluxDB.Client.Api.Domain;
using MASA.IoT.WebApi;
using Microsoft.Extensions.Options; namespace MASA.IoT.Core.Infrastructure
{
public class TimeSeriesDbClient : ITimeSeriesDbClient
{
private readonly InfluxDBClient _client;
private readonly string _bucket;
private readonly string _org;
private readonly AppSettings _appSettings; public TimeSeriesDbClient(IOptions<AppSettings> settings)
{
_appSettings = settings.Value;
_org = _appSettings.InfluxDBSetting.Org;
_bucket = _appSettings.InfluxDBSetting.Bucket;
_client = new InfluxDBClient(_appSettings.InfluxDBSetting.Url, _appSettings.InfluxDBSetting.Token);
} public bool WriteMeasurement<T>(T measurement)
{
try
{
using var writeApi = _client.GetWriteApi();
writeApi.WriteMeasurement<T>(measurement, WritePrecision.Ms, _bucket, _org);
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
}
}

这里使用new InfluxDBClient(_appSettings.InfluxDBSetting.Url, _appSettings.InfluxDBSetting.Token)来构造InfluxDBClient。

Token就是我们创建Bucket过程中保存的Token

Url是我们InfluxDB的访问地址:http://127.0.0.1:8086

写入的方法WriteMeasurement中我们通过_client.GetWriteApi创建一个写入的api然后直接将我们要写入的泛型实体写入,第二个可选参数代表写入精度,这里我们使用WritePrecision.Ms

我们在DeviceHandler.cs中注入ITimeSeriesDbClient 并添加一个WriteMeasurementAsync方法,在方法中我们先根据设备名称获取产品,如果识别产品ID为10001(空净产品),

那么我们就写入数据到Measurement:AirPurifierDataPoint

Measurement相当于数据库的表。

MeasurementColumn特性都是InfluxDB.Client.Core提供的,可以用来标识TagTimestamp

using InfluxDB.Client.Core;
using Newtonsoft.Json; namespace MASA.IoT.Core.Contract
{
[Measurement("AirPurifierDataPoint")]
public class AirPurifierDataPoint
{
/// <summary>
/// 设备名称
/// </summary>
[Column("DeviceName", IsTag = true)] public string DeviceName { get; set; } /// <summary>
/// 产品ID
/// </summary>
[Column("ProductId", IsTag = true)] public Guid ProductId { get; set; } /// <summary>
/// Pm2.5
/// </summary>
[Column("PM_25")] public double? Pm_25 { get; set; }
/// <summary>
/// 温度
/// </summary>
[Column("Temperature")] public double? Temperature { get; set; }
/// <summary>
/// 湿度
/// </summary>
[Column("Humidity")] public double? Humidity { get; set; }
/// <summary>
/// 时间戳
/// </summary>
[JsonProperty(propertyName: "Ts")]
[Column(IsTimestamp = true)] public long Timestamp { get; set; }
}
}
    public class DeviceHandler : IDeviceHandler
{
private readonly MASAIoTContext _ioTDbContext;
private readonly IMqttHandler _mqttHandler;
private readonly ITimeSeriesDbClient _timeSeriesDbClient; public DeviceHandler(MASAIoTContext ioTDbContext, IMqttHandler mqttHandler, ITimeSeriesDbClient timeSeriesDbClient)
{
_ioTDbContext = ioTDbContext;
_mqttHandler = mqttHandler;
_timeSeriesDbClient = timeSeriesDbClient;
} /// <summary>
/// 写入数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="pubSubOptions"></param>
/// <returns></returns>
public async Task<bool> WriteMeasurementAsync<T>(PubSubOptions pubSubOptions)
{
var device = await _ioTDbContext.IoTDeviceInfo.Include(o => o.ProductInfo).AsNoTracking()
.FirstOrDefaultAsync(o => o.DeviceName == pubSubOptions.DeviceName); if (device != null && device.ProductInfo.ProductCode == "10001") //空气净化器产品
{
var airPurifierDataPoint = JsonConvert.DeserializeObject<AirPurifierDataPoint>(pubSubOptions.Msg); airPurifierDataPoint.ProductId = device.ProductInfoId; return _timeSeriesDbClient.WriteMeasurement<AirPurifierDataPoint>(airPurifierDataPoint); }
return false;
}

除了WriteMeasurement方法之外,还提供了很多其他方法,如WritePoint,和批量写入的方法,可自行测试。

测试

我们启动项目,通过MQTTX向"topic/284202304230001"上报一条数据

{
"DeviceName":"284202304230001",
"Pm_25":100,
"Temperature":25,
"Humidity":50
}

我们在influxDB的管理工具中使用Data Explorer,使用如下的flux query查询语句,即可查出5分钟之内的数据,注意,这里的时间是UTC时间

如果想显示北京时区方便调试,可以在后面添加|> timeShift(duration: 8h)

from(bucket: "IoTDemos")
|> range(start:-5m)



关于flux查询语法

https://docs.influxdata.com/flux/v0.x/

总结

本节我们简单介绍了开源时序数据库influxDB的安装。

我们借助InfluxDB.Client库完成设备从上报到时序库数据存储的全过程,下一节我们介绍从时序库查询数据。

完整代码在这里:https://github.com/sunday866/MASA.IoT-Training-Demos

使用MASA Stack+.Net 从零开始搭建IoT平台 第五章 使用时序库存储上行数据的更多相关文章

  1. 【新阁教育】S7.NET+Log4Net+SQLSugar+MySQL搭建Iot平台

    1.搭建西门子S7仿真环境 新阁教育提醒您基于PLCSIM-Advanced搭建西门子S7仿真环境注意事项: 1.通过dotNet工控上位机公众号后台发送PLCSIM-Advanced获取软件 2.安 ...

  2. CentOS下用Tomcat+Zookeeper+Nginx+Solr完美搭建SolrCloud平台(五)

    六.修改 /etc/rc.d/rc.local 文件,设置开机自启动 1.nginx 主机的设置 [root@nginx 桌面]# vi /etc/rc.d/rc.local #!/bin/sh to ...

  3. Vue实战狗尾草博客管理平台第五章

    本章主要内容如下: 静态资源服务器的配置.学会如何使用静态资源服务器引入静态资源.并给大家推荐一个免费可使用的oss服务器~ 页面的开发由于近期做出的更改较大.就放在下一篇中. 静态资源服务器 静态资 ...

  4. 【HADOOP】| 环境搭建:从零开始搭建hadoop大数据平台(单机/伪分布式)-下

    因篇幅过长,故分为两节,上节主要说明hadoop运行环境和必须的基础软件,包括VMware虚拟机软件的说明安装.Xmanager5管理软件以及CentOS操作系统的安装和基本网络配置.具体请参看: [ ...

  5. 从零开始搭建一个PaaS平台 - 我们要做什么

    前言 从最开始的小公司做小网站,到现在进入现在的公司做项目,发现小公司里很多很多工作都是重复的劳动(增删改查),不过想想也是,业务软件最基础的东西不就是增删改查吗. 但是很多时候,这种业务逻辑其实没有 ...

  6. 从零开始搭建轻量级个人XSS平台

    一. 前言 决定搭建XSS平台是因为自己想深入学习一下XSS相关的知识,多多进行实践,上网搜索了一下XSS平台有很多,但是总觉得不是很安全,这个毕竟敏感信息要传输到陌生人的服务器上,而且服务器端测试代 ...

  7. 从零开始搭建ELK+GPE监控预警系统

    前言 本文可能不会详细记录每一步实现的过程,但一定程度上可以引领小伙伴走向更开阔的视野,串联每个环节,呈现予你不一样的效果. 业务规模 8个平台 100+台服务器 10+个集群分组 微服务600+ 用 ...

  8. AI应用开发实战 - 从零开始搭建macOS开发环境

    AI应用开发实战 - 从零开始搭建macOS开发环境 本视频配套的视频教程请访问:https://www.bilibili.com/video/av24368929/ 建议和反馈,请发送到 https ...

  9. 【从零开始搭建自己的.NET Core Api框架】(七)授权认证进阶篇

    系列目录 一.  创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...

  10. 【从零开始搭建自己的.NET Core Api框架】(四)实战!带你半个小时实现接口的JWT授权验证

    系列目录 一.  创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...

随机推荐

  1. 二进制安装Kubernetes(k8s) v1.24.1 IPv4/IPv6双栈

    二进制安装Kubernetes(k8s) v1.24.1 IPv4/IPv6双栈 Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 kubernetes二进制安装 后续尽可能第一时间更新 ...

  2. Gpssworld仿真(二):并排排队系统模拟

    4.3 某一个加油站能够配给三个级别的燃油:①家庭取暖用的燃油:②轻工业用的燃油:③运输用的燃油.每一级别的燃油都有一个对应的油泵.订单中燃油的数量在3000加仑和5000加仑中变化,每次增加10加仑 ...

  3. day01-Redis入门

    Redis入门 1.初始Redis 1.1认识NoSQL SQL(关系型数据库) NoSQL(非关系型数据库) 数据结构 结构化(Structured) 非结构化 数据关联 关联的(Relationa ...

  4. day02-Redis命令

    Redis命令 1.Redis数据结构介绍 Redis是一个key-value的数据库,key一般是String类型,value的类型多种多样,value常见的八种类型: Redis支持五种基本的数据 ...

  5. 特性介绍 | MySQL 测试框架 MTR 系列教程(一):入门篇

    作者:卢文双 资深数据库内核研发 去年年底通过微信公众号[数据库内核]设定了一个目标--2023 年要写一系列 特性介绍+内核解析 的文章(现阶段还是以 MySQL 为主). 虽然关注者很少,但本着& ...

  6. AI测试101:测试AI系统的实用技巧&ML和AI自动化工具

    基于人工智能的系统,也称为神经网络(NN Neural Networks),和其他应用程序一样是 "系统",因此需要测试.本文将指导你测试AI和基于NN的系统,并理解相关概念. 测 ...

  7. JAVA注解@Scheduled 不执行

    spring boot项目需要在启动类加上注解 @EnableScheduling 定义一个接口 StockTask.java 1 public interface StockTask { 2 pub ...

  8. java垃圾回收机制(面试)

    1.1堆空间结构 Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配.同时,Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收.Java 堆是垃圾收集器管理的主要区域,因此 ...

  9. react中受控组件与非受控组件--

    非受控组件:随用随取 1 render() { 2 return ( 3 <div> 4 <h1>非受控组件</h1> 5 <form action=&quo ...

  10. [C++核心编程] 3、函数提高

    文章目录 3 函数提高 3.1 函数默认参数 3.2 函数占位参数 3.3 函数重载 3.3.1 函数重载概述 3.3.2 函数重载注意事项 3 函数提高 3.1 函数默认参数 在C++中,函数的形参 ...