MongoDB——渐进式开发光伏云系统实践(二)

767 阅读8分钟
原文链接: zhuanlan.zhihu.com

万物生长——渐进式开发光伏云系统实践(一)

MongoDB——渐进式开发光伏云系统实践(二)

数据库选型

传统工业监控系统中,往往采用SQL型数据库。

数据库中存有“实时”与“历史”两类数据表,设备定时采集的数据首先送入实时表中,实时表只存放最近时刻数据,新数据入库会覆盖之前的数据。表中保持只有一个时刻的数据,以提高读写速度。历史表通过专门的存储过程,定时读取实时表中的数据,并加上时间字段作为一条历史记录。根据数据量的不同,历史表可能会按年、月或日分割为多个表。

在初代光伏云系统的开发中,我们采用SQL型数据库,表的设计照搬传统现场监控系统,但很快发现了问题:

  1. 数据入库的频率与历史记录的频率一样都是5分钟,所以实时表中的记录就是历史表中最顶端的一条记录,而且数据分析的功能往往需要的是一段时间的数据,所以实时表虽然在不停被修改,但功能几乎用不到实时表。
  2. 历史表按月按年的固定划分不一定符合功能设计的需要,一些“灵活”的查询功能实现起来很繁琐。

光伏云系统的设备原始数据被我们称为“核数据”,以上的问题让我们开始重新审视核数据的本质。

传统监控系统部署在现场,主要功能为实时监控。光伏云系统由于采用了基于HTTP协议的Web技术栈,且常常需要通过互联网传输数据,并不适合承担实时监控的任务,而主要实现分析、管理等功能。我们认为,核数据更类似于某种“定时log信息”:

  1. 核数据入库之后,主要供查询使用,几乎不存在修改的需求。
  2. 核数据作为大数据分析的原料,往往在事后使用,且单个数据对结果影响较小,因此相对于现场系统的数据,核数据对实时性和完整性的要求并不高。
  3. 核数据因设备型号的不同,数据内容千差万别,不会像现场系统拥有统一的固定的点表。
  4. 由于分布于各处的电站常常需通过GPRS上送数据,流量有限,采集间隔不能太密集,一般不低于5分钟。

总之,核数据的特点就是注重非结构化的记录与查询,事务性要求不高,是不是和服务器的log信息很像。

那么数据库的选型就顺理成章了,首先事务性要求不高,关注大数据量的查询,我们选择NoSQL型数据库而不是SQL型数据库。在主流的NoSQL型数据库中,我们不关注实时读写性能,所以排除Redis;我们不是部署在Hadoop上,且数据也不是列结构的,所以不用HBase;而log类型的数据,我们很自然的想到了基于文档的MongoDB。

此外,MongoDB的操作语言为JavaScript,这也与我们系统的整体技术栈统一;文档型存储便于我们用反范式化的方式设计档案数据集合,以档案数据量的微弱增加换取查询上的极大便利。

MongoDB中不再有表、字段和记录的概念,数据库中按集合(Collection)组织数据,每条数据称为一个文档(Document)。对于核数据,我们采取每个实际设备对应一个集合的方式。相对于同型号的所有设备数据放在同一集合的方式,这样单个集合的数据量较小,便于检索,而且也文档中也省去了设备识别号字段。

这样做的负面影响,就是随着新电站新设备的接入,数据库中集合的数量会增长。在“--nssize”参数的支持下,MongoDB的命名空间文件(.ns 文件)最大可以为 2G,也就是说最大可以支持约 340 万个命名,核数据集合中,集合名、”_id”索引、时间索引共需3个命名,即最多接入100万个左右的设备,这是能满足业务量要求的。

核数据集合按以下方式命名:

data.model_xxxx.id_0001
data.model_xxxx.id_0002
data.model_yyyy.id_0003
data.model_yyyy.id_0004

其中“xxxx”表示设备型号,“0001”表示设备唯一识别号,这样查询时利用JavaScript模板字符串可以很方便的拼接对应的集合名。

在系统整体设计中提到,数据库中除了核数据集合,其他的业务信息都保存在档案集合中,按目前的开发需求,主要包含以下三类:

  1. 信息类,存放类似用户资料、电站坐标等信息。子集合名以“i_”开头,例如“file.i_plant_location”。
  2. 关系类,存放类似电站与设备从属关系等信息,子集合名以“r_”开头,例如“file.r_plant_vdev”。
  3. 映射类,存放数据模型转换过程中的键映射,子集合名以“m_开头”,例如“file.m_vdev_model”。

除此之外还可能遇到的诸如服务器日志、业务工单等类型的数据,按照渐进式的原则,等开发到这一步再设计。

反范式

我们知道,在SQL型数据库表的设计中,要遵循以下范式(Normalization):

  • 第一范式(1NF):所有的域都应该是原子性的,即数据库表的每一列都是不可分割的原子数据项。
  • 第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码。
  • 第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性。

数据库表按范式设计可以确保数据存储没有冗余信息,节约存储空间。但是这样也是的数据查询时需要进行多表的关联,使得查询逻辑复杂,性能降低。

在使用MongoDB等NoSQL型数据库时,往往采取反范式化(Denormalization)的理念,即不按范式的要求设计集合,以数据量的增加换取查询上的便利。档案数据集合中存储的是信息、关系等较为固定的数据,数据量相对较小且一般不会增长,而查询数据集合却十分频繁且要求繁多,故在档案数据集合设计中可适当减少范式的约束,如电站与虚逆变器从属关系,就可以如下文档存储:

{
  plantId: '0001',
  vinv: [
    {id: '00045', model: 'XXXX'},
    {id: '00046', model: 'XXXX'},
    {id: '00047', model: 'YYYY'},
    {id: '00048', model: 'YYYY'}
  ]
}

这样相信虽然信息有冗余,但一次就可取到该电站下所有虚逆变器的id与型号,十分便捷。

映射的持久化与模型动态创建

核数据需转换为虚设备数据才可供业务功能使用。虚设备数据的模型,是标准的JavaScript对象,其中的成员可以是对象,数据有层级关系,不遵循第一范式,核数据的结构反映实际设备的点表,一般是符合第一范式的。核数据与虚设备数据模型的映射关系持久化保存在数据库中而不是写在程序中,这样才能保证有新型号的设备接入系统后可登记新增。

保存映射关系的对象与虚设备数据模型的对象同构,通过虚设备类型与设备型号联合索引:

{
  vdev: 'vcom',
  model: 'XXXX'
  dataMapper: {
    pTotal: 'Ppv',
    uTotal: 'Ipv',
    strings: [
      {i: 'Istring1'},
      {i: 'Istring2'},
      {i: 'Istring3'},
      {i: 'Istring4'}
    ]
  }
}

在后端系统中,odm框架采用mongoose。mongoose通过Schema来反映集合的结构,并以Schema为模板创建与集合对应的Model。每个型号的设备都有独特的核数据集合结构,考虑到设备型号很多和后续增加新型号的情况,在程序中写固定的Schema不太合适,我们将构造函数new mongoose.Schema(definition)中的参数对象持久化保存在数据库中,需要操作某型号设备的核数据时,通过查找对应的definition对象动态创建Schema,然后通过函数mongoose.model(modelName, schema, collectionName)动态创建具体单台设备的集合Model。

数据库安全性

说道MongoDB,可能很多人会对安全性有所顾虑,2017年1月MongoDB曾遭到大规模的攻击,被删除数据勒索比特币。但这次攻击并不是MongoDB本身的漏洞造成的,而是因为很多人在使用MongoDB的过程中没有进行足够的安全设置。事后网上有很多总结,以下措施可有效提高数据库的安全性:

  1. 创建授权用户,并设置密码。
  2. 关闭数据库的互联网访问权限。
  3. 修改数据库默认端口。
  4. 定期备份数据。

这些措施在传统数据库运维中是很常见的,而由于很多人对MongoDB的部署并不熟悉,采用默认设置,开启MongoDB服务时若不添加任何参数,默认是没有权限验证的,用户可以远程访问数据库,通过默认端口无需密码对数据库任意操作(增、删、改、查高危动作)。实际搭建和运维数据库时有意识的进行安全设置就可避免这些攻击。 MongoDB数据库前端开发

本文对你有帮助?欢迎扫码加入前端学习小组微信群: