物联网的数据方案
一、前言
经常可以在科幻电影/CG中看到,某个指挥官,对着前面一个超大的数据大屏,指点江山。那个数据大屏,上面有着各项指标,以及汇总数据,通过各色各样的图形展示出来。
从产品角度,指标与汇总条目的确定,决定了该数据大屏的价值。当然也可以支持自定义指标管理等。这不是本次的重点。
从前端角度,如何利用最少的系统资源,将众多数据渲染出各个图表,则是重中之重。技术上,可以采用阿里AntV那一套等。这同样不是本次的重点。
从后端角度,如何在满足数据请求内容的前提下,保证数据请求的高性能(低延迟,大吞吐量、高频次等),就是该场景下的要点。这才是本次的重点。
二、背景
作为物联网公司,需要向多个电力公司,进行数据大屏展示。其中数据大屏需要展示电力公司上百台风力发电机的状态,以及各项监控指标。其中监控指标中包含倾斜值、震动波形、结构应力等。而其中震动属于高频采集,频次都是上千Hz的。即使用于计算倾斜值的倾斜传感器,最低也是10/s。而监控大屏的数据,要求保证实时性。产品的要求是最好实时同步,最慢的指标也要保证5s一次的刷新率。最坑爹的,各个监控指标都需要可以查看历史数据,以及实时数据。
如果按照震动数据来看,一条震动数据在数据库中的记录约为100字节,一个震动传感器每秒1000条记录,一台风机有4个震动传感器,一个电力公司当前最多有100台左右风机。这样一来,一家电力公司每秒有震动数据40W条记录,约为40M。震动是记录数最多的传感器,但不是带宽占用最大的类型,音视频占据的带宽更多。
当然,这是没有任何处理的情况,所以看起来非常糟糕,前端完全无法处理。光震动就得每秒渲染40W数据,前端开发会杀了你的。尤其在历史数据的情况下,简直无法想象。
别说这能不能渲染了。我都得怀疑数据库和带宽是否可以承受了。毕竟还有别的公司,别的场景,以及别的传感器。
三、数据写入
1.数据清洗
首先,将一些明显不合规的数据,给清理掉。比如由于人为触碰导致的倾斜值跳动,人为作业导致的震动波动等。这部分过滤比例是很低的,主要是为了降噪。
2.边缘计算
风机的某项指标,往往是由多个传感器计算得到的。比如倾斜值,往往是通过一组(至少三个)传感器,计算得到的。
所以,我们先需要在风机的边缘网关,进行协议解析,初始指标计算。
这部分的处理,往往会将多个数据,转为一个数据记录。过滤效果还是不错的。
PS:边缘计算,还应用在边缘网关的即时报警,以及机器关闭等操作。
3.采样上传
传感器的原始数据指标,是非常多的,一不留神就把硬盘打爆了,甚至是带宽。
而其实往往我们并不需要这么多的原始数据指标。毕竟如果风力发电机要倒塌,也不会前一秒很正常,后一秒突然倒塌了。
所以,我们需要对原始数据指标进行采样上传。并且需要对传感器采集频率进行确定。
经过和业务、算法多方沟通&协商后,倾斜传感器的采样率定在了5%,应力传感器的采样率定在1%等。
那么,传感器的采集频率和数据上传的采样率,有什么区别&联系呢?
前者表示数据采集了,而且可能落盘了。后者表示对采集到的数据,抽取一部分,上传到物联网平台。
那么,为什么不直接降低采集频率呢?
一方面,部分传感器的采集频率是有其上下限的,不一定满足上传需求。另一方面,合理频率的原始数据,便于在发现问题后,进一步确定问题。
所以,我们当时的处理,是原始采集数据直接本地磁盘顺序保存。并对原始数据进行采样,进行本地数据库保存,以及上传。
4.特征值提取
在某些垂直场景,我们需要计算出某些指标的特征值(平均值、方差),经过算法的计算,得出有关目标实体的结论。
比如根据十分钟内的倾斜值平均值和方差,我就可以知道当前风机倾斜状况,并且通过方差,可以确定倾斜数据的稳定性。
5.分级采集
上面这一系列操作下来,数据已经过滤了七七八八。那么还有没有节省资源(功耗、存储、带宽等)的办法呢?
正如上面提到的,风机就算要倒塌,也不会是一下子就倒塌了。所谓冰冻三尺,非一日之寒。咳咳,扯远了。也就是说,我们日常采集的监控数据大多数是无效的。所以为了提高资源利用率。在经过与业务、算法的沟通后,我这边提出采集等级概念。即风机所属传感器平时只保持低频采集状态,只有指标出现可疑情况,才会进入全功率状态。
比如,震动传感器,由于无法降频与连续采样(因为震动的信息隐藏在连续的高频数据中),故其采集成本最高,所以设定为每天随机时间段连续采集10分钟。倾斜传感器每小时采集1分钟数据。当检测到可疑情况,如倾斜值超出目标阈值,则全功率状态。直到连续监测1小时,未出现可疑状况,则重新回归低频状态。
其实,上述只有低频采集和全功率状态两个采集状态。其实可以扩展出多个采集级别。另外,还可以深入细化状态转换的触发条件等。
通过分级采集,可以大大降低系统资源的浪费,却又保证了系统目标。何乐而不为呢。
四、数据存储
数据存储方面,只要关注物联网数据的特性:量大、有序、越是最近的,访问频率越高。方案上,多多考虑数据异构即可。
1.存储方式
简单来说,云平台的一条指标记录,会出现在四类存储上:缓存、数据库、统计表、归档表
a.缓存
由于刚刚插入的数据,经常被监控大屏、网页等显示终端展示,所以访问频次还是挺高的。尤其各类算法,经常需要扫描这些刚进来的萌新数据。
首当其冲的就是缓存key的设计。我们当时的设计就是绑定关系(公司-场景-目标-指标)+时间戳。这样的查询,还是相当快捷的。
其次就是失效时间的设定。我们当时的设定是1小时-1天。决定的标准是对应指标的访问频次和所占存储空间。
最后就是一致性问题,由于这些数据都是不变数据,所以不存在一致性问题。
不过缓存这边需要注意一点,就是需要将指标缓存与其他业务数据隔离。我们当时的设计,是放在不同的Redis集群。
至于集群、可用性、持久化的问题,这里就不展开了。
b.数据库
数据库一方面是为了进行指标记录的持久化,另一方面是为了一天之前的数据查询。
这部分其实真的没什么说的。大家对mysql都是很熟悉的。
这部分会涉及字段设计、索引设计(尤其是联合索引和覆盖索引)、分库分表(路由规则设计)。虽然一般分库分表前都会有主从同步,但在我们的场景下,还真没太大必要,毕竟写多读少。而大多数读的压力,又放到了缓存上。我们的主从同步,也是为了服务分库分表,提高可用性。
字段设计
字段设计方面,一方面尽可能按照范式拆分。这不是电商场景,需要尽可能大宽表,这两个场景完全不同。比如,记录里面可能是id、创建时间、公司、场景、目标、指标、指标值。但完全可以id、创建时间、指标id、指标值,再加一张指标绑定表。这样可以节省非常多存储,也可以大幅提高查询性能(因为一个数据页可以容纳更多记录,从而降低整体IO成本)。
另外,真的需要需要在记录中保存类似公司这样的字段,最好转化为公司编码,进行保存。
c.统计表
我们在数据查询时,常会查看过去一个月、过去一年等数据,进而观察数据趋势。
而这个数据是不可能在每次查看时,从数据库中拉取的。那服务器会崩溃的。
所以,会在每天凌晨构建天、周、月、年这样的统计表,甚至可以结合其他维度各搜索条件,生成多个搜索结果。
具体实现,有三种途径:
- 应用程序拉取备库数据,进行统计,并将结果写入统计表中。
- 备用数据库通过计划任务,定时触发执行统计,并将结果写入统计表。
- 利用大数据技术,如ODPS等MapReduce技术,定时拉取&统计数据,将结果写入统计表。
d.归档表
超出一年多数据,查询量很低。即使有查询,往往也是类似平均值这样的聚合数据统计。但随着时间的推进,超出一年的数据,往往会越来越多。所以有的地方就直接拒绝这样数据的详细查询。而另外一种方案,就是将其放在一个归档数据库,不必性能很好,只要可以查询即可。
当时我们的方案,就是将数据以一年为单位放在一个新的数据库中。即每到新的一年,则将往年的数据写入到一个新的数据库中,作为归档表。而实时数据库则最多只保留最新十三个月的数据。
当然,归档表的数据也可以放在HBase这样的Bigtable中,尤其在达到一定体量后。其rowKey的kv获取,以及rowKey的scan获取都符合归档表的需求。至于HBase的全表扫描,就算了。。。
五、数据查询
其实数据存储部分,已经提到了很多数据查询的思路。
这里按照两个查询维度的视角,进行分析。
1.详细数据查询
详细数据查询,则是直接查询数据记录,而不是统计数据。
a.短期数据
短期数据,如一天内的数据,可以直接从缓存中获取。
当然也可以按照实际情况,将部分类型数据的失效时间,调整一下。
b.中期数据
中期数据,如一年内的数据,可以直接从数据库中获取。
同样,中期的数据,可以按照实际情况,调整为半年等。
c.长期数据
长期数据,如一年外的数据,可以从归档数据库中获取(实现基础,可以是独立的冷数据Mysql实例)。
d.小结
详细数据查询,必须确保各个range范围的数据,都可以得到有效处理。
如果一个数据查询范围为最近半年到最近一年半的数据,怎么办呢?一方面可以直接从归档数据库查询。另一方面可以进行查询范围的分解,如将上述范围分解为最近半年到最近一年(数据库),以及最近一年到最近一年半(归档数据库)。前者实现简单,后者用户体验会更好一些,具体需要根据业务需求来进行确定。
2.统计数据查询
这里,我给出当时我们多个方案,以及优缺点。
a.方案一
方案:应用服务器直接拉取目标范围的数据,然后对其中的数据,进行采样&平均。
例子:目标范围10W数据,就应用服务器直接拉取10W数据,然后采样其中1W数据,然后每十个数据平均一下,最终得到1k数据,交给前端。
优点:实现简单
缺点:随着目标数量的上升,查询效率线性下降。也就是目标数量上升一个数量级,查询时间就上升一个数量级。
PS:在日常生活&工作中,不可避免负面影响增长,需要杜绝指数、避免线性、追求对数。尤其技术中,很多都可以将线性转为对数。比如流程抽象等。
b.方案二
方案:应用服务器,获取开始时间的数据min_id,以及结束时间的数据max_id,进而获得count = max_id - min_id,以及步长pace = count / 100(100表示返回给前端的数据条数)。通过min_id和pace,计算目标数据的id集,进而通过mysql查询结果集。
例子:根据目标范围的开始时间,查询到min_id = 2000000,max_id = 3000000。进而获得count = 1000000,以及pace = 10000。所以目标id集为:2000000,2010000,2020000,2030000 ... ... 2990000。进而获得mysql中对应结果集。
优点:实现并不复杂,只涉及代码编写。并且不会随着数据量的增长,而查询性能下降。
缺点:无法添加时间以外的查询条件;无法处理非连续采集的数据(比如七天的时间范围,有六天是不采集的。但这样的方案,是无法满足条件的)
c.方案三
方案:方案三是在方案二的基础上,添加了对其他条件的过滤(有些类似mysql二级索引结果,回表验证其他条件)。
例子:根据目标范围的开始时间,查询到min_id = 2000000,max_id = 3000000。进而获得count = 1000000,以及pace = 10000。所以目标id集为:2000000,2010000,2020000,2030000 ... ... 2990000。进而获得mysql中对应结果集。然后再针对结果集,进行其他条件的过滤
优点:实现并不复杂,只涉及代码编写。并且不会随着数据量的增长,而查询性能下降。可以添加时间以外的查询条件
缺点:查询结果数量无法确定,存在返回结果数为0的可能(这个可以二次查找,调整min_id,拼概率);无法处理非连续采集的数据(比如七天的时间范围,有六天是不采集的。但这样的方案,是无法满足条件的)
d.方案四
方案:方案四是在方案二的基础上,将id改为了时间范围。
根据目标范围的开始时间min_time与结束时间max_time,获得时间差time_range = max_time - min_time,进而获得时间步长time_pace = time_range/100(100表示返回给前端的数据条数)。通过min_time和time_pace,获得目标时间范围集:min_time ~ min_time + time_pace、min_time + time_pace ~ min_time + time_pace * 2 ...
例子:略
优点:实现并不复杂,只涉及代码编写。并且不会随着数据量的增长,而查询性能下降。
缺点:无法添加时间以外的查询条件;性能较低
e.方案五
方案:建立统计表,每天凌晨,都会计算前一天各指标数据的,多个搜索条件下的平均值等特征值。便于后续进行“过去一个月”、“过去一年”等时间范围的数据查询。
优点:针对常见查询,可以快速返回结果。
缺点:部分查询条件无法进行统计,导致无法查询。统计表的计算,是存在较大资源损耗的。并且由于统计表的时长特性,可能导致展现层数据点数量不统一(有的时候数据点很多,有的时候数据点非常稀疏。但可以通过自动统数据维度升降击毙,进行较大的优化)。
六、总结
总结一下,数据架构的核心就是冷热隔离、分级处理。
在设计数据架构前,需要确认数据情况,如物联网场景写多读少,并且写入数据都是Insert,不存在一致性问题。那么在设计数据解决方案时,侧重点则会有所倾向。
其实该方案和业务、应用架构等场景下都是一致的。大家都听过什么管理杠杆率、二八定律、好钢要使在刀刃上这些语句。其实总结起来就一句话:
按照投入产出比,进行资源分配,从而在有限的资源下,追求获得最高整体产出。