不论是结构化数据,还是半结构化数据,甚至是非结构化数据,导入鸿鹄之后,原始数据都将被存储在数据集当中。在查询数据的时候,通过读时建模计算引擎,通过 SQL 查询语句,从原始数据中提取需要使用的信息片段,进一步完成关联和聚类分析。
本文将说明鸿鹄是如何存储数据,介绍和阐述鸿鹄中的数据存储概念——数据集。以此为基础,会进一步讨论如何将数据合理组织存储到鸿鹄数据集的最佳实践建议。
如何理解鸿鹄的数据存储
数据集( EventSet )是用于存储各种异构数据的数据容器。在底层存储实现中,数据集中包含了存储原始数据的文件,以及针对数据构建的索引文件。
我们可以将不同格式的异构文本或者可以转换成为文本类型的数据快速的存入数据集。鸿鹄针对了时序的文本类型的数据做了相应的存储优化。存储于数据集当中的数据是不需要指定 Schema 的,这和普通的关系型数据库有本质的不同。
在鸿鹄中,我们把这样的存储方式叫做 “无模式存储”( schemaless storage )。鸿鹄是一个数据分析平台,在导入数据的时候,数据会被拆分成事件( Event )存储于数据集当中,每一个事件对应着一条数据记录,也可以将数据集看成是一个事件的集合。
为了理解数据集 Schemaless storage ,我们需要先熟悉以下概念:
-
事件( Event ) : 事件是鸿鹄中存储的一条数据记录,每一个事件当中存储的数据原始格式可以不同。事件的数据模型由如下字段信息构成。字段是指一个 key-value 键值对。
-
时间戳( _time ) :一个整数,代表 Unix epoch timestamp 。具体一个事件的时间戳是什么,是在数据注入的时候由业务逻辑来决定的,对于鸿鹄系统而言,就是一个整数,代表着从 Unix Epoch 开始的微秒数。
-
事件消息( _message ):一个字符串,用来存储事件的原始数据。
-
主机信息( _host ):一个字符串,用来标记事件生成或者存储的主机 ID 信息。
-
数据源类型( _datatype ):一个字符串,用来标记事件原始数据的格式等元信息。
-
事件源( _source ):导入事件的数据源相关的元信息,例如,文件名,数据库名,业务 app 名称等。
-
可选的自定义字段:可以自定义事件相关的字段标记,字段名称和字段值都是可以自定义的。
在事件模型中,需要注意这些细节:
时间戳的单位是微秒( Microseconds ),而不是秒。
事件消息是字符串格式,所以,事件的原始数据如果不是字符串类型,也会被格式化成字符串存储。
系统会对数据源类型、主机信息、事件源这三个元数据相关的字段构建索引,因此,如果查询的时候使用元数据字段过滤数据,会更加高效。如何利用好元数据字段,我们会在最佳实践当中详细讨论。
_time , _message , _datatype , _host , _source是一个事件必要的字段信息。这些下划线开头的字段都是系统保留字段。
事件的字段分为“索引时字段”( Ingestion Time Field )和“查询时字段”。索引时字段是指事件在导入鸿鹄的时候就带有的字段键值对,鸿鹄会针对索引时字段构建索引。
查询时字段是指在 SQL 查询运行时,通过函数计算变换得到的字段键值对,查询时字段不会存储于磁盘中,会在每次 SQL 查询任务执行过程当中动态计算得到。
时间戳,事件消息,主机信息,数据源类型和事件源都是索引时字段,都会存储于磁盘当中。
逻辑上可以把事件理解为一组字段构成的集合。可以类比 JSON 格式,一个事件是一个 JSON Object ,每个字段是这个 Object 中的一组属性( Property ),字段名是属性名,字段值是属性的值。可以动态的向事件中添加字段,就如同可以动态向 JSON 对象中添加属性一样。
所以,数据集不同于关系型数据库中的数据表( Table ),鸿鹄中的数据集不需要定义 Schema ,可以将任意多个包含不同字段的事件统一存储到一起,日后,可以从数据集中精确的筛选出所需要的事件。
可以做一个简单的类比,鸿鹄的数据集类似 MongoDB 的集合( Collection ),都不需要定义 Schema ,可以动态的扩展 Schema 。
鸿鹄的事件类似 MongoDB 的文档( Document ),每个记录上都可以包含任意多个属性字段,并且字段可以动态扩展。但是鸿鹄不同于 MongoDB ,鸿鹄是数据分析平台,提供高效能的数据分析计算功能,存储引擎也是为了分析任务优化的。
将日志存储到鸿鹄的数据集
下面以将日志存储到数据集为例,举例说明事件在数据集中的存储方式。
在将日志文件导入数据集的时候,数据导入模块会对日志文件进行日志拆分,把文件按照日志分行规则,拆分成若干条日志。每一条日志对应着一个事件。
_time 就是对应的日志生成的时间;
_message 就是整条日志的内容;
_datatype 根据不同的日志的格式和来源,可以使用不同的数据源类型加以区分,比如,如果是应用A输出的JSON格式的日志,建议将 _datatype 设置为 appA.json,如果是应用A的日志输出为 log4j 的格式,建议将 _datatype 设置为 appA.log4j ;
_source 是日志文件的文件名;
_host 是生成日志的主机名。
我们可以将不同主机,不同应用,不同格式的日志,都导入到同一个数据集当中,以方便后续进行统一的分析和管理。
结合 Schemaless SQL 理解数据集和事件
鸿鹄自研了 SQL 计算引擎,鸿鹄的 SQL 我们称为 Schemaless SQL 。因为鸿鹄的事件集并不需要定义 Schema ,所以当使用 SQL 从事件集当中查询数据的时候,也没有 Schema 的验证。
也就是说,当我们写如下 SQL 从数据集 main 中查询事件的 a, b, c 字段。
对于关系型数据库的 SQL 引擎,会利用 catalog 当中存储的 main 的 schema 结构,验证 a, b, c 列( column )是否存在于 main 的 table schema 定义中。如果不存在,我们会得到一个 SQL 编译错误。
但是对于鸿鹄来说,由于数据集 main 中可以包含任意格式的事件,并且数据集没有 schema 定义,所以这个SQL语句在鸿鹄中永远是合法可以执行的,不会报任何的 SQL 解析错误。如果a,b,c字段在数据集中的事件中不存在,则他们返回null即可。
下表是一个不严谨的概念类比,用于帮助对照理解关系型数据库和鸿鹄中的概念异同
从 SQL 的角度理解,所有的数据都会被看做是二维的数据表,每个表有若干个列,已经数据行构成。在使用鸿鹄 SQL 查询数据集的时候,可以将数据集看成一个无限宽度(列可以扩展)的二维表。
1、当 main 数据集当中没有任何事件,此时的数据集是如下一个空表。下划线开头的保留字段是事件必要的字段。
2、向 main 中导入第一条事件 event1,并且事件上有自定义的a、b字段。此时,数据集变成如下的结构,列 a 和 b 将自动扩展。
3、向 main 中导入第二条事件 event2,并且事件上有自定义的 c、d字段。此时,数据集将变成如下的结构,列 c 和 d 将自动扩展。对应的事件上没有的字段,字段的值将是 null。
鸿鹄数据存储最佳实践
数据集是鸿鹄中规划数据存储的重要概念。针对数据集,我们可以配置数据的保存策略,按照存储大小或者是数据保存时间进行数据淘汰和归档的策略配置。可以基于角色配置数据访问权限。
由于数据集提供了比关系型数据库中Table更宽松的存储特性,在规划数据存储的时候,也和关系型数据库有所区别,可以将不同格式的数据存储到同一个数据集。
以下是常用的一些最佳实践建议:
1、对于有不同访问权限要求的数据,应该放入不同的数据集当中,然后可以分别配置数据集的访问权限,以达到数据安全隔离的目的。
2、如果数据有相同的保存策略,建议将同样保存策略的数据放入同一数据集,方便配置管理。
3、不要创建过多的数据集,在鸿鹄中,将不同的异构数据写入同一个数据集的开销很小。但是创建新的数据集,会有更多的内存开销,因为每个数据集会有单独的数据导入通路。
4、合理设置事件的时间戳,在鸿鹄中,_time 字段是一等公民,系统会针对时间戳构建索引,并且在SQL查询的时候,可以通过设置合理的查询时间窗口范围,快速缩扫描的数据集。所以,根据业务特点合理的设置时间的时间戳,能够达到优化查询的目的。_time 字段应该被提取成为查询的时候需要频繁被使用作为过滤的条件的时间。如何配置时间戳提取的规则,可以参考相关文档。
5、利用好 _source , _host , _datatype 三个内建的索引时字段,系统会针对三个字段构建索引,利用好三个字段,可以使查询时数据过滤地更快。在导入数据的时候,这三个字段时常容易被忽略,或被赋予了null值:
-
_datatype 数据源类型字段是一个非常重要的元数据信息字段,数据源类型不仅可以用来标记事件的格式信息,还可以用来绑定字段抽取 pipeline,从而快速方便的实现对不同的事件自定义读时建模规则。
-
_source 通常都会被用于标记事件的来源,它可以是一个文件名,或者是一个系统的名称,或者是一个频繁会被用在过滤条件当中维度。
-
_host 通常被设置成为产生事件的主机的主机名,但是,我们也可以根据实际业务场景,合理的配置和修改 _host 的值。
在配置和使用数据集的时候,还需要注意以下的细节:
1、_datatype 需要谨慎配置,字段的值会被存储到磁盘,如果在数据导入之后要更新 _datatype,只能重新导入数据。
2、对于一些有通用格式的事件,也不要将 _datatype 不要随意设置成为数据的格式。例如,针对一个应用 A 的 json 格式的数据,不要简单的把 _datatype 设置成 json,因为这样会丢掉关于应用 A 相关的信息,如果有应用 B 也有 json 格式的数据,_datatype 也设置为 json,那么在查询的时候,就无法快速的通过 _datatype 过滤出应用A或者应用 B 的数据。
我们必须要在查询时通过数据当中的其他信息加以区别,这样是低效的。对这样的情况,我们的建议是,把应用A 的 json 数据,_datatype 设置成为 appA.json, 把应用 B 的 _datatype 设置为 appB.json,这样在搜索的时候,可以快速的通过 _datatype 实现数据的过滤和分类。
同时,这样的数据源类型命名,也可以轻松看出事件属于的应用系统,以及原始数据的格式。
3、我们推荐如下的 _datatype 的值的命名规范:
-
不同部分的信息使用点.来分隔
-
_datatype 的总体格式推荐遵循<product _name>.<module_name>.<version>.<raw_message_format>这样的规范。其中如果不存在模块名或者是原始数据格式等信息,可以省略相应的部分。
总结
不同于一般的关系型数据库,鸿鹄支持“无模式存储”( schemaless storage )的数据集,可以灵活的将不同格式,不同数据源的数据快速的存储到同一个数据集中。
并且通过使用鸿鹄 schemaless SQL 可以灵活、快速的实现数据的过滤,进行数据分析。
鸿鹄的数据集内建了对时间戳和元数据字段的索引,通过合理的配置时间戳和元数据字段的信息,可以实现快速高效的数据过滤和分析。