以下内容根据演讲视频以及PPT整理而成。
饿了么的监控体系于2015年之前通过StatsD与Graphite技术建立,在2016年公司开发了第一个版本基于RocksDB的本地存储系统LinDB1.0,系统实现了全链路监控的自用型功能,基本满足了公司的需求。在2017年,公司通过扩展RocksDB将LinDB系统升级到了2.0版本。在2018年,公司参考RocksDB的思想基于排倒索引与自研存储开发的LinDB3.0版本,很好的提升了系统的性能,满足了业务上多活的需求。下图展示了公司数据库的发展历程。
基于LinDB系统优良的性能与公司增长的需求,饿了么公司的对LinDB数据库的不断改进的目标如下所示:
- 数据库监控体系结构足够轻量,减少维护所需人力物力;
- 体系支持Measurement、Tags与Fields技术;
- 可以解决指标写入的热点问题,采用Series技术进行Sharding过程,体系支持水平扩展;
- 支持近实时读写功能,高效快捷的查询出数据;
- 监控体系下的数据库支持多活;
- 自动的Rollup;
- 支持类SQL的查询方式;
- 安全性上高度可靠,可以支持多个副本;
二.系统整体架构
如下图所示是公司数据库的整体架构。
公司的数据库从结构上分为四层,不同于传统数据库在存储层进行多活,公司将多活设计在了计算层中,以此提升系统的效率。在数据写入层中,不同客户端的写入数据会存入对应的不同机房的集群中。在用户查询层中,用户将通过HTTP的访问对相应LinProxy模块中的数据进行查询。在系统的计算层LinProxy中,公司为不同的LinProxy定义了不同的逻辑集群,逻辑集群将通过相应的方式向对应的物理集群进行数据的查询。
在进行重量级查询时,数据会不断地从下层送到上层,导致在计算层中数据处理遇到瓶颈。系统的计算层采用了并行处理请求的方式,通过拓扑结构将数据一层层的送给用户。系统同样支持更高级的计算,例如对股票曲线的预算或时间线的预判等。
三.写入过程
如下图所示,数据在系统中的写入分为WAL的复制与本地写入两步。WAL复制的过程中,系统参考了Kafka设计方式,即用户只要写入WAL成功就被认为数据写入成功,通过这种方式提高了整个系统的吞吐量。本地写入过程中,用户将WAL的数据解析写入自己的存储结构中,并可以通过本地存储查到已写入的数据。
例如下图所示情景,在一份含有三个副本的数据中,用户将写入成功的副本一数据分别同步到副本二与副本三中,系统采用了透取技术,用“推”的方式将数据从Leader节点推送给Follower节点。系统的复制协议采用多通道复制协议,主要基于WAL在多节点之间的复制,WAL在每个节点上的写入由独立的写操作完成,所以当客户端写入对应Leader节点的WAL成功就认为本次写操作是成功的,Leader所在的节点负责把相应的WAL复制到对应的Follower中, 同理写WAL成功被认为复制成功。
下图所示多通道复制的原理图,多通道复制技术仿照Kafka机制设计而来,这种设计理念将WAL视为巨大的数组以此实现将客户端上的数据一点点的写入。对于Leader节点有三个指针,分别代表写入指针,复制指针与复制完成后Follower的位置指针。通过多通道复制设计,系统提高了吞吐量,但同时降低了数据一致性的程度。
系统本地写入存储数据的结构如下图所示,系统的一个数据库在单节点上会存在多个Shard,每个Shard负责各自的数据写入,并含有各自的WAL,所有Shard共享同一个索引数据。所储存的数据会根据数据库的Interval按时间片进行存储。系统的设计具有以下的优势:
- 方便处理TTL,遇到过期的数据使用者可以直接删除相应的目录;
- 每个Shard下面会存在Segment,Segment根据Interval来存储相应时间片的数据;
- 每个Segment下面按Interval方式存储了很多Data Family,以此减少数据文件之间的合并操作,平衡写入放大的问题;
下图所示为数据本地写入的过程。系统会为每一个Shard启一个写线程,该线程负责这个Shard的所有写操作。在写入的过程中,系统首先会把Measurement、Tags、Fields对应的数据写入数据库的索引文件,并生成相应的ID数据。系统会根据索引得到的ID,结合写入时间和数据库Interval计算得到需要写入到对应Segment下的对应Family中。整个写过程先写内存,再由Flusher线程把内存中的数据推到相应的文件中。
四.如何查询
下图所示为系统的查询过程。用户提交的请求首先到达Proxy层,在该层中进行SQL解析,并将解析的数据送到Aggregator Stream中进行聚合操作。系统通过LinProxy层中的LinConnect模块将处理好的请求发送到机房集群中,在机房集群中进行数据的反馈。ZooKeeper在系统中负责简单的Metadata存储。整个系统的操作采取了异步化的方式,这样可以利用系统线程在IO等待时做一些计算操作。
系统在节点层面上的数据查询过程如下图所示。该过程类似于Map操作,公司在节点层面上使用了Actor来替代传统的Task,大大增加了索引查询的效率。
五.存储结构
在存储过程中,对数据的索引分为ID与倒排索引两部分,系统首先将输入的字符串转换成Int类型,并根据Time Series的规则为数据生成对应的唯一ID。系统根据Tags标签进行倒排索引,将索引指向一个ID列表。TSID列表以BitMap的方式存储,以方便查询的时候通过BitMap操作来过滤出用户想要的数据。系统中的每一类数据都存储在独立的RocksDB Family中。系统的具体索引步骤如下图。
将数据中的字符串转换实例的过程如下图。系统首先将CPU数据、Host数据及地区数据全部转换为整型数字,再根据相应的数据规则对转换后的数字进行倒排解析,将解析后的数据存储入库。
系统的存储模式参考了LSM方式,下图列出了系统根据LSM方式设计的存储模式。
在系统存储的内存层面,系统设计了两个版本的Memory Table,一个负责写入数据,一个负责将数据写入文件中。在Memory Table中,每个Table都会拥有对应的Store及Block块。存储结构如下图所示。
系统的文件存储中使用同一个Measurement的数据以Block的方式存储在一起,查询时通过Measurement ID定位到该的数据所存储的Block中。在文件存储结构的尾部,系统指向了Metric索引,在索引块中包含了很多Offset模块,通过这种方式方便用户得知系统的版本号并加快索引速度。系统的文件存储结构如下图所示。
六.系统自身监控
系统拥有很好的自身监控能力。在系统自身监控的模块,使用者可以清晰看到系统各个方面的运行情况,通过每项数据的变动,使用者可以清楚掌握系统目前的状况,监控数据示意图如下图所示。