首先什么是时序数据库算法
工业领域大部分都是使用的实时数据库,对于物联网到来的今天,传统的实时数据库已经不能知足如今的需求。他们有不少共同点:数据库
- 都带有时间戳
- 按时间顺序生成的
- 大多为结构化数据
- 采集频率高
- 数据量大
实时数据库通常具有一下特定:缓存
高速写入的能力:工业实时数据库一般会对写入的速度有很高的要求。以流程工业的场景为例,每一个环节都会设置传感器,每一个传感器的采集频率都很高,因此写入的并发量会特别大,有时甚至会要求每秒上百万的测点。因此除了对软件的要求以外,也会选用一些高性能的服务器。服务器
快速查询的能力:查询的需求分为两块,一是要响应实时的查询请求,用于及时反映系统的状态;二是历史数据也要能快速被查询,因为历史数据的量很是大,在查询时须要对特定时间段的数据作聚合,须要作到即便是查一全年的数据状况,也能很快的反应出来。数据结构
超强数据压缩能力:上面提到监控数据会被存储很长时间,5年甚至是10年都是常有的事,在存储容量有限的状况下,就须要对数据作必定的压缩,一般压缩方式会分红无损压缩和有损压缩,相比而言,有损压缩的压缩比会更大一些,有时甚至会达到1:30-40,这就须要设计合理的算法来保留数据中的细节,使数据在还原后仍能保留重要的特征。架构
积累丰富的工具:传统的实时数据库的解决方案通常是从采集开始到直可视化的一整套系统,有多年积累造成的丰富的工具包,好比会积攒上百种的协议,或者各类场景的数据模型,这些都是工业软件的重要竞争力。并发
追求极致稳定:工业上对软件的稳定性要求特别高,除了用主备来保证高可用外,彻底由软件的质量来保证程序的持续运行,工程师会自豪地拍胸脯保证软件跑十年也不会出错。app
时序数据库特色:数据库设计
1. 单条数据不会很长,可是数据量很大分布式
2. 它们都带有时间戳,且按顺序生成
3. 数据大部分都是结构化的,用于描述某个参数在某个时间点的特征
4. 写入的频率会比查询的频率高不少
5. 已存储的数据不多有更新的需求
6. 用户会更关心一段时间的数据特征,而不是某一个时间点
7. 数据的查询分析大多基于某一个时间段或者某个数值范围
8. 须要进行统计和可视化的展现
随着IoT和工业互联网带来的新一波风口,一系列新的生产方式、组织方式和商业模式开始涌现。物联网技术逐步渗透工业,不断增加的传感器、飙升的数据量,以及更高的大数据分析需求对实时数据库传统的技术架构提出了挑战。有些问题是须要直面的:
扩展性遇到瓶颈。传统的技术架构虽然能保证单机具有极高的性能,也能够经过增长机器使性能线性扩展,可是不能像分布式系统那样实现动态灵活的扩容和缩容,须要提早进行规划。当业务升级须要系统扩容时,老架构的扩展性就很难知足需求了。
没法和大数据生态对接。数据采集的最终目的是被理解和使用,大数据产业中对于海量数据的存储分析已经有很成熟的方案,不管是hadoop仍是spark的生态圈,都面临着新老技术的对接。不少工业企业由于想使用新的大数据分析技术,不得不对现有的系统进行升级或是替换。
价格高昂。传统的工业实时数据库解决方案价格都十分昂贵,通常只有大型企业能忍痛接受。可是随着新技术新理念的普及,更多的中小企业也意识到数据的重要性,但考虑到资金投入,会倾向于寻找价格更低廉的方案。
这时候来自互联网你们庭的时序数据库方案就展示出了一些先天优点,好比:
分布式架构的自然优点:传统的实时数据库可能是主备的部署架构,一般要求有较高配置的机器,来追求单机极致的性能;同时,在稳定性方面,会对运行软件的稳定性作极高的要求,彻底由高质量的代码来保证运行的稳定;因为存储容量有限,也会要求超高的数据压缩比。可是时序数据库的分布式架构,使得系统可以轻松的进行水平扩展,让数据库再也不依赖昂贵的硬件和存储设备,以集群自然的优点来实现高可用,不会出现单点的瓶颈或故障,在普通的x86服务器甚至是虚拟机上均可以运行,大大下降了使用成本。
更灵活的数据模型:传统的实时数据库因为工业场景的特殊性,常使用的是单值模型,一个被监控的参数称为一个测点,在写入时会对每个测点建一个模型,好比一个风机的温度指标算一个测点,十个风机的十个指标就是100个测点,每一个测点会附带描述信息(名称、精度、数据类型、开关量/模拟量等)查询的时候就会针对每一个测点去查询数值。单值模型的写入效率会很高。
接下来就是我本身基于MongoDB设计时序数据库架构的思路:(没有一个架构设计能适合全部的应用场景规范。不管哪一种架构,都须要权衡利弊)
对每一个数据Tag,创建一个Collection 以PK命名 以下图:
数据机构设计:
_id:MongoDB Document pk
Count:Document 数据大小
Data:数据 s:时间戳(差值:能够有效减数据大小)v:数值
StartTimeStamp:起始时间
LastTimeStamp:结束时间
能够看到数据存储在Data里面 随着数据量不断扩大 Data 线性增加 能够对Data数据进行简单优化:
对 s v 数据类型进行设计可有效减小磁盘空间的占用 s 时间戳 利用差值存储 4byte 便可 根据须要存储的数据类型 v float类型 也是 4byte 知足大部分应用场景 这样 每一个数据 仅占8byte 磁盘空间 固然还能够进一步优化及压缩 之后再说。
按照数据数量分组分段(Document)
分段数据具备显着的优点。基于时间的分段将整整一段时间的数据存储到单个文档中。在诸如 IoT 的基于时间的应用中,传感器数据能够以不规则的间隔生成,而且一些传感器能够提供比其余传感器数据更多的数据。在这些场景中,基于时间的分段可能不是架构设计的最佳方法。另外一种策略是基于大小的分组。
经过基于大小的分组,咱们根据必定数量的发射传感器事件或一成天(以先到者为准)围绕一个文档设计咱们的模式。
要查看基于大小的存储分区,请考虑存储传感器数据并将存储区大小限制为每一个文档3600个事件或一天(以先到者为准)的方案。注意:3600限制是任意数字,能够根据须要进行更改,无需更改应用程序或模式迁移。
以下图:
数据库设计完毕,目前场景下可知足数据的存储需求。但 IoT场景下有大量的传感器及信息要存储对于吞吐量有很大的要求。通过测试MongoDB 写入数据速度并非很快 大体5000条/秒(没仔细测过,不一样硬件配置 数值也可能不一样),对于工业场景写入数据量也不算很大,这样须要对时序数据库增长缓存队列。能够采用Kafka,这里咱们本身设计一套简单的消息队列让其知足万级数据量的队列存储。
缓存队列设计以下:
00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | ||||
PK | 0000-0000 | FF | FF | FF | FF | FF | FF | FF | FF | ||
0000-03FF | |||||||||||
var0 | meta | 0000-0400 | head | tail | |||||||
DATA | 0000-0408 | data.k | data.v | ||||||||
0000-0418 | data.k | data.v | |||||||||
data.k | data.v | ||||||||||
0001-03FF | data.k | data.v | |||||||||
var1 | meta | 0001-0400 | head | tail | |||||||
DATA | 0000-0408 | data.k | data.v | ||||||||
data.k | data.v | ||||||||||
data.k | data.v | ||||||||||
0002-03FF | data.k | data.v | |||||||||
var2 | meta | 0002-0400 | head | tail | |||||||
DATA | 0000-0408 | data.k | data.v | ||||||||
data.k | data.v | ||||||||||
data.k | data.v | ||||||||||
0003-03FF | data.k | data.v | |||||||||
var1023 | meta | 03FF-0400 | head | tail | |||||||
DATA | 03FF-0408 | data.k | data.v | ||||||||
data.k | data.v | ||||||||||
data.k | data.v | ||||||||||
03FF-03FF | data.k | data.v |
利用MomeryMappedFile既能保证队列存储在内存中,又能防止数据丢失问题产生。打开一个文件,映射到内存一块区域。灰色区域用来存储Tag的PK,来标识数据。每一个数都有必定大小(可设置)的存储空间, 其中黄色区域是数据的mate 用来存储队列head(队列第一个的地址)tail(队列最后一个的地址),绿色区域DATA用来存储有效数据 根据以前设计的数据结构data.k 存储时间戳 data.v 存储数值。
队列(FIFO)的基本原理是:
入队时:把入队数据写到tail后面的地址 并更新meta中tail的地址。须要增长一个判断是否到达内存空间的最后一个地址若是当前tail是最后一个地址,则将数据写到第一个地址并把meta中tail更新到第一个地址。若是tail中的地址正好等于head中的地址,则表示数据空间已满,实现FIFO时head须要更新到下一个地址。
出队时:读取当前head地址中的数据,并更新head地址到下一个地址。须要增值一个判断head地址=tail地址 则队列为空,不作任何操做。
以上是内存映射队列的一个基本思路,还须要考虑锁和并发问题。目前用netcore2.1实现 并已经在项目中应用。
这样一个简单的时序数据库,就设计完成了。固然还有不少不足,也没有通过大量的测试。