高频时序数据的储存与统计方案

news2024/12/23 19:25:27

文章目录

    • 问题背景
    • 解决办法
      • 第一步,梳理数据和计算要求
      • 第二步,确定存储和计算方案
      • 第三步,确定技术选型和方案
      • 第四步,实施优化方案
    • 后记

问题背景

发电设备中常常会放置传感器(DCS)来采集数据以监控设备运转的状况,某集团设计的电力监控统计系统,需要实时采集传感器的数据后保存,然后提供按时段的实时查询统计功能。

系统设计规模将支持20万个传感器(以下称为测点),采集频率为每秒一个数据,即每秒总共会有20万条数据,总时间跨度在1年以上。在这个基础上实现任意指定时段的多个测点数据统计,包括最大、最小、平均、方差、中位数等。

系统原结构图为:

系统中,用户期望的统计响应延迟为:从20万个测点中任取100个测点,统计频率最高可能每隔若干秒调用一次,从总时间跨度中统计任意一天的数据,预期执行时间在1分钟内,另外还会有少许离线任务,最长的时间段跨度长达一年。

现有的数据中台中没有计算能力,仅存储数据,计算时需要通过RESTful接口取出数据再统计。经测试,通过RESTful接口从数据中台取数,取出100个测点一天的数据量就需要10分钟时间,还没有开始计算,取数的时间已经远远超出了完成计算的预期时间。

基于现有结构,完成上述统计任务,性能上无法达到预期要求,需要将数据重新存储。

解决办法

第一步,梳理数据和计算要求

数据结构如下:

字段名类型中文名
TagFullName字符串测点名
Time长整型时间戳
Type数值数据类型
Qualitie数值质量码
Value浮点数数值

计算要求为:在每秒生成20万条记录的时序数据中,任意时间段内,从20万个测点中任取100个测点的数据,分别基于每个测点的数值序列统计最大、最小、方差、中位数等结果。

第二步,确定存储和计算方案

20万测点一天的数据,仅Value字段,就要200000*86400*4字节,至少64g内存,当总时间跨度为1年时,数据量会有数十T,单台服务器内存显然装不下。多台服务器集群,又会带来很高的管理和采购成本。

简单按时间为序存储的数据,可以迅速找到相应时间区间,但即使是这样,单个测点一天也有86400条记录,20万个测点共17.28亿条,每次统计都要遍历这个规模的数据,也很难满足性能要求。

那么测点号上建立索引是否可行?

索引只能快速定位数据,但这些数据如果在外存中不是连续存储的,硬盘有最小读取单位,会导致大量无用数据量读出,使得计算变得很慢,同样也无法满足性能要求。此外,索引占用空间会随着数据量增大而增大,并且插入数据的维护开销也更大。

如果数据可以按测点号物理有序存储,并在测点号上建立索引,相比时序物理有序存储,查找时,待查找的测点记录变得紧凑了,需要读入的块也就少了。100个测点的数据存成文本约300m不到,这样即使使用外存也可以满足性能要求。

只有历史冷数据时,处理起来比较简单,定时将数据按指定字段排序即可。但实际上,每秒都会有20万个测点的新数据,因为历史数据规模巨大,如果每次获取几秒热数据都与历史数据整体按测点号、时间排序,即使不算排序,仅是重写一遍的时间成本上都无法接受。

这时,需要将冷热数据区分对待。不再变化的冷数据可以按测点次序准备好。这里有一点变通,因为要将非常早期的数据删除(比如一年前的),如果所有冷数据都按测点排序时,会导致数据维护比较麻烦,删除早期数据会导致重写一遍所有数据。因此,可以设计为先按时间分段,每段时间内的数据按测点、时间有序,整体数据还是按时间有序。任务需求是按天计算,这里按天分段就比较合适,更长跨度的离线计算性能损失也不是很大。每当一天过去时,将昨天数据按上述规则排序后存储,当天的数据作为热数据处理。但是,当天内的数据量还是太大了,依然无法全部装入内存,还需要再分。

经过一些测试后确认,我们发现将数据按热度分为三层可以满足要求。第一层,十分钟内的热数据通过接口读入内存;第二层,每过10分钟,将过去10分钟的内存数据按测点、时间有序保存到外存;第三层,每过一天,将过去24小时内的所有每10分钟的数据按测点、时间有序归并。总数据为:一年的数据由365段每天数据,加144段当天数据和一段内存数据。

分层后的冷热数据属于不同的数据源,需要独立计算同源数据的结果后,再将结果合并起来,算出最终的统计结果。即使计算方差、中位数这种需要全内存统计的情况,100个测点一天的数据量,也只需要64m内存。

第三步,确定技术选型和方案

从上述的存储方案中得知,需要将实时数据按时间分段,段内按测点号、时间物理有序存储,常规数据库显然没办法做到这点。此外,拆分数据需要可以支持按自定义时间段灵活地拆分;数据存储时要具备高性能索引;冷热数据属于不同层(不在同一个数据源),计算时需要分别计算后再合并。

完成该任务,用Java硬编码工作量巨大,Spark写起来也很麻烦。开源的集算器SPL语言提供上述所有的算法支持,包括高性能文件、物理有序存储、文件索引等机制,能够让我们用较少的代码量快速实现这种个性化的计算。

取数不能再用原系统的RESTful接口,也不合适直接通过API从DCS获取数据。用户方商定后引入kafka缓冲数据,屏蔽DCS层,同时还可以将DCS的数据提供给不同的消费者使用。变更后的系统结构图如下:

说明:

  1. DCS系统每秒推送20万个测点数据至Kafka MQ。
  2. Kafka MQ到SPL:使用SPL基于Kafka API封装的Kafka函数,连接Kafka、消费数据。
  3. 内存缓冲:循环从Kafka消费数据(kafka_poll),每轮循环确保10秒以上的数据量,将每轮前10秒的数据补全后,按测点、时间序,保存成文件并读入内存。
  4. 分层数据文件:按不同时间段将冷热数据文件分层。
  5. 统计时将冷热数据混合计算。
  6. 支持每个测点名对应一个CSV文件作为数据源计算。
  7. 统计接口以HTTP服务方式供外部应用调用并将统计结果通过回调接口返回给外部应用。

第四步,实施优化方案

现有的RESTful接口取数太慢了,接口变为从kafka消费数据。存储数据时,将字符串类型的测点名数字化后保存,以获得更小的存储量和更好的运算性能。

在第二步中已经提到,数据量较大时,无法将数据都放在内存中计算,所以考虑采用冷热分层方案,将数据分为三层,每天的冷数据按测点号、时间有序(下文中的所有外存文件存储均采用该序,不再重复说明),用组表存储,因为大表对性能的影响很大,存储成组表有利于提升系统整体性能;当天的每10分钟的冷数据用,集文件存,因为集文件创建和使用都更简单,用来存储小表会很便捷,也不会因为索引块而降低存储效率;10分钟内的热数据从kafka直接读到内存,因为数据本身是通过kafka接口获取的,另外数据可能有一定的延迟,不适合每秒取数即写出。

测试后发现,10分钟内的热数据,从kafka获取后再解析json,不但需要消耗大量内存,而且解析json也需要花费很长的时间。这样在内存中直接加载热数据是没办法用来统计计算的,所以将热数据改为每10秒存成一个集文件。

接下来开始实现统计计算部分。每天组表中的冷数据计算较快,但是当天的144个集文件计算很慢。通过计算可以知道,每10分钟的数据量约1.2亿条记录,这个规模的数据可以用组表来存储,另外还可以再加一层每2小时一个组表文件,来减少当天总文件数的数量(从144个变成了24个)。实际上,计算时采用的二分查找是对单个文件内有序的测点号使用的,减少了文件个数,也就是减少了总查找次数。

最终,我们把数据分成了4层。第一层:延迟10秒的集文件热数据;第二层,每10分钟的组表冷数据;第三层,每2小时的组表冷数据;第四层,每天的组表冷数据。由于每层数据都按测点号、时间有序,所以每一层都可以用归并,快速生成下一层数据文件。

这时的冷数据计算已经很快了,可以满足实际使用,但是热数据的计算相比冷数据还是很慢。观察发现,热数据的所有集文件都加起来大约3G,不算很大,内存可以装下。实际测试,把文件读到内存中再查找相比直接外存文件查找可以快出好几倍。

已知的统计计算,分为最大值、最小值、中位数、方差、平均值等,不尽相同,但是之前的数据查找是一样的。都用二分法,找出对应测点号组的数据,再用时间过滤,即可得到相应的value值。

实测效果
经过几天时间的SPL编码、测试,优化的效果非常明显。优化之后的测试结果如下(耗时为毫秒):

测点数
时间段
1050100
10分钟467586854
1小时173938854545
6小时2599748913138
1天49231626430254

说明:测试环境使用的机械硬盘,对并发计算不友好,更换为固态硬盘后,测试结果还会有较大的提升。

后记

解决性能优化难题,最重要的是设计出高性能的计算方案,有效降低计算复杂度,最终把速度提上去。因此,一方面要充分理解计算和数据的特征,另一方面也要熟知常见的高性能算法和存储方案,才能因地制宜地设计出合理的优化方案。本次工作中用到的基本高性能算法和存储方案,都可以从下面这门课程中找到:点击这里学习性能优化课程,有兴趣的同学可以参考。

传统数据库的功能比较单一,只能解决一个环节的问题,比如内存数据库解决热数据问题,大数据平台解决冷数据。而当前问题需要多种技术组合,如果运用多种产品混合实现,又会带来架构的复杂性,增加系统的风险。而且业界中的大数据库产品的架构也较为死板,对存储层基本不提供可编程能力,很难基于这些产品实现某些特殊设计的方案。

相比之下,集算器则拥有开放的技术架构和强大的编程能力(SPL语言),可以被深度控制,从而实现各种因地制宜设计的方案。

SPL资料

  • SPL下载
  • SPL源代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115180.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

河海大学软件工程学硕考研复试经验贴

一、写在前面 想必看到这篇文章的学弟学妹都已经考完初试了,考得如何每个人心中各有千秋。无论如何,坚持将考研整个过程走下来的你们就已经是最棒的了,现在可以好好休息一下,静待考研成绩的公布了。 我写下这篇文章的目的主要是…

4.3.1、IPv4 地址概述

1、基本介绍 在 TCP/IP 体系中,IP 地址是一个最基本的概念,我们必须把它弄清楚。 IPv4 地址就是给因特网(Internet)上的每一台主机(或路由器)的每一个接口分配一个在全世界范围内是唯一的 32 比特的标识符。 IP 地址由因特网名…

5.8 什么是学习博主?看两个博主案例【玩赚小红书】

先看大家看两个博主案例 ​ 学习博主,就是专门为用户提供学习方法的人。 学习方法在小红书的内容中属于干货价值,也就是用户们需要的东西,能为他们解决问题的内容,所以是比较受欢迎的,换言之,就是笔记数据…

Spark 3.0 - 15.ML PIC 快速迭代聚类理论与实战

目录 一.引言 二.PIC 理论 1.谱聚类 2.快速迭代聚类 三.PIC 实战 1.数据准备 2.构建 PIC 3.预测与展示 四.总结 一.引言 前面介绍了 K-means 聚类与高斯混合聚类,本文介绍另外一种聚类方法 Power Iteration Cluster 快速迭代聚类,简称 PIC。快…

【架构设计】你的类足够“专一”吗

前言 软件设计SOLID原则中有一个最基础的原则就是单一职责原则,我想绝大部分的程序员都知道,而且都理解它的意思,甚至觉得很简单。但是往往“看懂”和“会用”是两回事,而“用好”更是难上加难。好比我们项目,一开始一…

取代OpenFeign:Spring Framework 6全新声明式客户端@HttpExchange

本文已被https://yourbatman.cn收录;女娲Knife-Initializr工程可公开访问啦;程序员专用网盘https://wangpan.yourbatman.cn;技术专栏源代码大本营:https://github.com/yourbatman/tech-column-learning;公号后台回复“…

【几种可调动对象,Function和bind;线程的调动方式举例】

1.可调动对象的调动方式 方法 1、函数指针调动 方法2 、类类型的括号的重载 调动可调动对象 #include<iostream> #include<functional> using namespace std; struct Foo {void operator()(int x){cout<<"Foo operator "<<x<<endl;}…

CSS3【定位的基本使用[静态定位\相对定位\绝对定位]、子绝父相、固定定位、元素的层级关系】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录一、定位1.1定位的基本介绍1.1.1 网页常见布局方式1.1.2 定位的常见应用场景1.2 定位的基本使用1.2.1 定位初体验1.2.2 使用定位的步骤1.2.3 静态定位1.2.4 小结1.2.…

使用 Docker Hub 完美地存储 Helm 图表实战

使用 Docker Hub 完美地存储 Helm 图表实战 Helm 是 Kubernetes 的包管理器。它是一个开源容器编排系统。它通过提供一种简单的方法来定义、安装和升级复杂的 Kubernetes 应用程序&#xff0c;帮助您管理 Kubernetes 应用程序。 使用 Helm&#xff0c;您可以将您的应用程序打包…

git教程

教程目录Git 教程Git 与 SVN 区别Git 与 SVN 区别点&#xff1a;Git 安装配置Linux 平台上安装Debian/UbuntuCentos/RedHatWindows 平台上安装Mac 平台上安装Git 配置用户信息文本编辑器##差异分析工具查看配置信息Git 工作流程Git 工作区、暂存区和版本库基本概念Git 创建仓库…

semargl 软件使用方法简介

文章目录前言一、semargl 软件使用简介1.semargl 软件简介2.准备演示软件操作所需的数据3.使用 semargl 获取频谱关系4.使用 semargl 获取特定频率模式的空间分布5.使用 semargl 获取自旋波的色散关系二、笔记05第三节内容的补充1.优化多进程读取磁化数据文件的代码2.新增获取特…

【JavaSE】 常用类(447~515)

String 447.常用类-每天一考 1.画图说明线程的生命周期&#xff0c;以及各状态切换使用到的方法等 状态&#xff0c;方法 2.同步代码块中涉及到同步监视器和共享数据&#xff0c;谈谈你对同步监视器和共享数据的理解&#xff0c;以及注意点。 synchronized(同步监视器){//操…

Python手势识别与追踪

程序示例精选 Python手势识别与追踪 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<Python手势识别与追踪>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 应…

【日常】圣诞节、颜色⛄

2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 关于圣诞节&#x1f384;&#xff0c;大家想到什么颜色&#xff1f;⛄&#x1f98c;&#x1f381;&#x1f385;&#x1f525; demo online - https://codepen.io/adamlindqvist/pen/EaPeJg html <!-- Christ…

详细介绍关于自定义类型:结构体、枚举、联合【c语言】

文章目录结构体结构体的声名特殊的声明结构成员的类型结构的自引用结构体变量的定义和初始化结构体内存对齐修改默认对齐数结构体变量访问成员结构体传参结构体实现位段&#xff08;位段的填充&可移植性&#xff09;位段的内存分配位段的跨平台问题枚举枚举类型的定义枚举的…

【Linux】用户与用户组操作_补

文章目录一.用户1.1 用户与用户组概念1.2 与用户管理相关的系统文件1.3 查看用户组1.3.1用户组密码配置文件&#xff0f;etc&#xff0f;gshadow1.4用户管理创建用户修改用户添加密码一.用户 1.1 用户与用户组概念 用户和用户组的对应关系有&#xff1a;一对一、一对多、多对一…

【C语言进阶】指针练习题

写在前面 这是指有关指针的小题 正文 练习一 int main() {int a[5][5];int (*p)[4];pa;printf("%p,%d", &p[4][2]-&a[4][2], &p[4][2]-&a[4][2] );return 0; } 解析&#xff1a; a[4][2]为如图粉色部分&#xff0c;p[4][2]为如图蓝色部分。a的…

【ROS通信机制实战练习】通过话题发布实现turtlesim小乌龟圆周运动

本节记录下使用ROS中的话题机制&#xff0c;实现turtlesim中小乌龟的圆周运动。 如果想通过话题通信机制&#xff0c;实现小乌龟的圆周运动&#xff0c;需要首先明确小乌龟的运动情况&#xff0c;以及所涉及的指挥运动的参数&#xff0c;这里需要首先手动发布一个turtlesim的节…

springboot整合mybatis代码快速生成

特别说明&#xff1a;本次项目整合基于idea进行的&#xff0c;如果使用Eclipse可能操作会略有不同&#xff0c;不过总的来说不影响。 springboot整合之如何选择版本及项目搭建 springboot整合之版本号统一管理 springboot整合mybatis-plusdurid数据库连接池 springboot整合…

String 字符串

String 基本介绍 String 应该是 Java 中最常用的一个对象&#xff0c;他不是八种基本数据类型的其中之一&#xff0c;但是随便翻了一下项目代码&#xff0c;用 String 定义的变量超过百分之八十。 public final class Stringimplements java.io.Serializable, Comparable<…