本文的NewLife.IoT平台为商用产品,这里选择数据接收与存储部分,分享一下海量数据下的架构设计。
结果数据
早些天企业版客户对NewLife.IoT平台进行性能测试,积累了一些经验,这里分享一下。
该客户单项目大概有4万设备共10万点位,要求一分钟内每点位至少上报两次数据,也就是20万tpm。
先来看结果,星尘监控得知,每5分钟峰值1000万点位数据,折算200万tpm。
应用服务器配置:16核32G
数据库服务器配置:16核64G,装MySql 8.0、Redis 6.2
刚开始的时候,应用服务器是8C16G,数据库是4C32G,每5分钟大概处理200万点位数据。
基础知识
物联网平台服务端(以下简称IoTServer)共设计了属性上报和数据上报两个接口。
- 属性上报 PostProperty
客户端维护一个属性字典(点位表),记录每个属性的最新值,每60秒上报一次(周期可调)。
上报之前,不管采集多少次,都只更新本地属性字典。
因此,属性上报接口的吞吐要求,就是每分钟处理系统的设备点位总数。
- 数据上报 PostData
每次采集之后,判断属性点的数值是否发生改变,如有改变则放入上传队列,批量上传IoTServer。
采集间隔一般是1s~5s,实际上报数据量还取决于点位数据变化率。
IoT平台的这一项设计,既保证了点位数据发生改变后的及时上传,又把定时上报属性数据作为兜底方案,确保数据新鲜性和完整性。
例如烟雾传感器,没有触发报警时它的采集值永远是0,该类设备几乎只有定时上报数据,而没有上报数据。一旦触发报警,在1秒内可上报通知IoT平台,并触发规则事件通知各个业务系统。
架构设计
每5分钟1000万点位数据,意味着接口吞吐是1000w/5/60=33333tps,需要注明这里是单机而不是集群。
聚合上传
普通的WebApi接口也比较难达到这个吞吐。
因此我们首先需要改造接口,聚合一些数据,做成批量上传。批大小在10时,接口吞吐只要求3333tps,批大小在100时候,接口吞吐要求333tps,还是比较容易能够达到的。
存储架构
IoT平台在数据存储上分为三部分:
- 属性表。记录每个点位最新数值,PostProperty/PostData 两个接口都要Update这里的数据;
- 数据表。时序数据表,点位的每次上报都插入一行,PostProperty/PostData 两个接口都要Insert。使用MySql等关系型数据库时按天分表,使用TDengine时不用分表。XCode支持自动分表以及支持多种数据库;
- 数据队列。点位数据的消息队列,用于驱动下游各种业务逻辑,包括策略引擎、数据推送、数据分析统计等等。可选择Redis队列等消息队列中间件,如NewLife.Redis。
异步落库
一般的MySql数据库根本达不到这个吞吐,并且系统按照一比三放大了存储。
属性表和数据表可以使用XCode的SaveAsync异步保存,进入实体队列后批量落库。
数据队列可以借助延迟队列 DeferredQueue 凑批,然后批量写Redis队列。
对于NewLife.XCode用户来说,保存点位数据本来是 data.Save(),只需要改为 data.SaveAsync,吞吐立马就上去了。