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

前言

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

Install InfluxDB | InfluxDB OSS v2 Documentation

我们打开 服务器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函数,请参考官方文档

内置 SQL 函数 | EMQX 5.0 文档

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查询语法

Flux Documentation

总结

本节我们简单介绍了开源时序数据库influxDB的安装。
我们借助InfluxDB.Client库完成设备从上报到时序库数据存储的全过程,下一节我们介绍从时序库查询数据。

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

上一篇2023年北京.NET线下技术沙龙圆满落幕!

下一篇使用MASA全家桶从零开始搭建IoT平台(六)使用规则引擎实现告警通知

本文作者:MASA技术团队

本文链接:https://www.cnblogs.com/MASA/p/17505493.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

<think>好的,用户在使用Z.Blazor.Diagrams和MasaBlazor时遇到了找不到AddBlazorDiagrams的问题,我需要帮他们解决。首先,我应该回想一下常见的可能原因。可能包括包版本不兼容、框架版本问题,或者命名空间引用错误。 用户提到使用的是.NET9,但.NET9可能还在预览阶段,而Z.Blazor.Diagrams可能尚未适配最新的框架版本。需要检查Z.Blazor.Diagrams的官方文档或NuGet页面,确认是否支持.NET9。如果官方不支持,用户可能需要降级到.NET8,或者等待更新。 接下来,检查是否正确安装了所有必要的包。用户可能只安装了Z.Blazor.Diagrams的核心包,但忽略了扩展包,比如Z.Blazor.Diagrams.MasaBlazor,或者需要额外的依赖项。例如,MasaBlazor可能要求特定版本的Blazor,或者需要先注册服务。 然后,命名空间是否正确导入?AddBlazorDiagrams可能属于不同的命名空间,比如需要在_Imports.razor中添加using语句。或者,MasaBlazor的集成方法可能不同,可能需要调用AddMasaBlazor()之后再调用特定的扩展方法。 另外,版本冲突也是一个常见问题。用户可能安装了不兼容的MasaBlazor和Z.Blazor.Diagrams版本,需要查看两者的依赖关系,确保版本匹配。可能用户需要更新到最新预览版或稳定版。 还有可能,用户没有正确配置服务。比如在Program.cs中,是否调用了builder.Services.AddBlazorDiagrams(),或者是否有其他必要的配置步骤。可能需要检查文档中的快速入门部分,确保每一步都正确执行。 最后,建议用户查看官方示例或GitHub仓的issue部分,看看是否有类似的问题和解决方案。如果以上步骤都尝试过,可能需要联系的作者或在社区寻求帮助。</think>以下是针对 **Z.Blazor.Diagrams** 与 **MasaBlazor** 集成时找不到 `AddBlazorDiagrams` 方法的解决方案: --- ### **1. 问题原因分析** - **包版本不兼容**:可能是 **Z.Blazor.Diagrams** 未适配 .NET 9 或 MasaBlazor 最新版本。 - **缺少依赖项**:未安装必要的扩展包或未正确引用命名空间。 - **服务注册方式变化**:某些在更新后修改了服务注册方法。 --- ### **2. 分步解决方案** #### **步骤1:确认包版本兼容性** - **检查当前环境**: - 确保项目基于 **.NET 9 Preview** 和 **MasaBlazor 最新预览版**。 - 在 `*.csproj` 中确认包版本: ```xml <PackageReference Include="Masa.Blazor" Version="1.0.0-preview.*" /> <PackageReference Include="Z.Blazor.Diagrams" Version="2.0.0" /> ``` - 若 `.NET 9` 导致兼容性问题,暂时降级至 `.NET 8`(生产推荐)。 #### **步骤2:安装必要依赖** - 若使用 **MasaBlazor 主题集成**,可能需要额外包: ```bash dotnet add package Z.Blazor.Diagrams.MasaBlazor ``` #### **步骤3:正确注册服务** - 在 `Program.cs` 中添加: ```csharp using Z.Blazor.Diagrams; // 确保引用命名空间 builder.Services.AddMasaBlazor(); // 先注册MasaBlazor builder.Services.AddBlazorDiagrams(); // 再注册Diagrams ``` #### **步骤4:检查命名空间** - 在 `_Imports.razor` 中添加: ```razor @using Z.Blazor.Diagrams @using Z.Blazor.Diagrams.Components ``` --- ### **3. 常见错误排查** | **错误现象** | **解决方案** | |---------------------------|-----------------------------------------------------------------------------| | `AddBlazorDiagrams` 不存在 | 检查是否安装了 `Z.Blazor.Diagrams` 核心包,而非仅主题包。 | | 样式丢失 | 在 `_Layout.cshtml` 或 `App.razor` 中引入 CSS:<br>`<link href="_content/Z.Blazor.Diagrams/style.min.css" rel="stylesheet" />` | | 组件无法渲染 | 确认已调用 `UseMasaBlazor()` 和 `UseBlazorDiagrams()` 的中间件(若需要)。 | --- ### **4. 完整代码示例** ```csharp // Program.cs using Z.Blazor.Diagrams; var builder = WebApplication.CreateBuilder(args); builder.Services.AddMasaBlazor(); builder.Services.AddBlazorDiagrams(); // 其他配置... ``` ```razor @* 页面中使用 *@ <MasaContainer> <Diagram Canvas="@_canvas" Nodes="@_nodes" Links="@_links" /> </MasaContainer> ``` --- ### **5. 资源参考** - **Z.Blazor.Diagrams 文档**:[GitHub 仓](https://github.com/zHaytam/Blazor.Diagrams) - **MasaBlazor 集成指南**:[官方文档](https://docs.masastack.com/blazor/getting-started) --- 如果问题仍未解决,建议提供 **具体错误信息** 或 **代码片段**,以便进一步分析!你目前使用.NET 9 是预览版吗? 😊
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值