版本 | 日期 | 备注 |
---|---|---|
1.0 | 2021.10.19 | 文章首发 |
1.0 | 2021.11.21 | 针对在公司中分享时添加的内容进行补充 |
1.1 Prometheus踩过的坑
在这里,我们先简单复习一下Prometheus中的数据结构。其为典型的k-v对,k(一般叫Series)由MetricName
,Lables
,TimeStamp
组成,v则是值。
在早期的设计中,相同的Series会按照一定的规则组织起来,同时也会根据时间去组织文件。于是就变成了一个矩阵:
优点是写可以并行写,读也可以并行读(无论是根据条件还是时间段)。但缺点也很明显:首先是查询会变成一个矩阵,这样的设计容易触发随机读写,这无论在HDD还是SSD上都很难受(有兴趣的同学可以看后面的3.2小节)。
于是Prometheus又改进了一版存储。每一个Series一个文件,每个Series的数据在内存里存满1KB往下刷一次。
这样缓解了随机读写的问题,但也带来新的问题:
1.2 InfluxDB踩过的坑 1.2.1 基于LSM Tree的LevelDB LSM Tree的写性能比读性能好的多。不过InfluxDB提供了删除的API,一旦删除发生时,就很麻烦——它会插入一个墓碑记录,并等待一个查询,查询将结果集和墓碑合并,稍后合并程序则会运行,将底层数据删除。并且InfluxDB提供了TTL,这意味着数据删起来是Range删除的。 为了避免这种较慢的删除,InfluxDB采用了分片设计。将不同的时间段切成不同的LevelDB,删除时只需关闭数据库并删文件就好了。不过当数据量很大的时候,会造成文件句柄过多的问题。 1.2.2 基于mmap B+Tree的BoltDB BoltDB基于单个文件作为数据存储,基于mmap的B+Tree在运行时的性能也并不差。但当写入数据大起来后,事情变得麻烦了起来——如何缓解一次写入数十万个Serires带来的问题? 为了缓解这个问题,InfluxDB引入了WAL,这样可以有效缓解随机写入带来的问题。将多个相邻的写入缓冲,然后一起fresh下去,就像MySQL的BufferPool。不过这并没有解决写入吞吐量下降的问题,这个方法仅仅是拖延了这个问题的出现。 2. 解决方案 细细想来,时序数据库的数据热点只集中在近期数据。而且多写少读、几乎不删改、数据只顺序追加。因此,对于时序数据库我们则可以做出很激进的存储、访问和保留策略(Retention Policies)。 2.1 关键数据结构
2.2 关键策略
3.小结 总体看下来,相比Kafka、HBase来说,时序数据库的内部结构并不简单,非常有学习价值。 3.1 参考链接
3.2 磁盘随机读写vs顺序读写 3.2.1 HHD HHD的随机读写弱势根本原因在于它的物理结构。当我们对磁盘产生寻址请求时候(可能是读一个区域的数据,或者定位到某个区域来写入数据),首先可以看到的瓶颈就是主轴的转速,其次是磁头臂。 放到现在来看,HHD的随机读写大致为速度为2MB/S、2.2MB/S。而顺序读写大致为200MB/S,220MB/S。 3.2.2 SSD SSD看起来一切都很美好,随机读写速度一般在400MB/S、360MB/S,顺序读写速度一般在560MB/S,550MB/S。 但真正的问题在于它的内部结构。它的最基本物理单位是一个闪存颗粒,多个闪存颗粒可以组成一个page,多个page可以组成一个block。 写入时,会以page为单位,我们可以看到图里是4kb。这意味着你哪怕写1b的数据,也要占据4kb。这还不是最致命的,最致命的是删除,删除是以整个block为单位发生的。图中是512kb,这意味着你哪怕删里面1kb的数据,都要导致写放大发生。