近年来,开源大数据处理系统逐渐发展到相对成熟的阶段,各种大数据处理场景纷纷涌现对应的解决方案就像MySQL在当今互联网公司广泛使用关系型数据存储的地位一样。腾讯目前拥有数十万台服务器。TMP系统以1分钟为粒度采集监控数据,平均每天采集超过1200亿个数据点。本文将从目前存储架构存在的问题出发,介绍从尝试使用Opentsdb到设计Hbase存储方案存储TMP服务器海量监控数据的实践历程。TMP目前的存储架构分析我们先来看看目前TMP的1分钟粒度数据存储架构。Agent上报的数据通过Collector分发到不同的数据存储节点,从MySQL数据表中查询索引和路由规则a。数据节点Datacache接收到数据后将数据缓存在内存中,内存中的数据会周期性的dump到文件系统中。这种架构的优点是显而易见的,例如设计简单、专用数据缓存、分布式数据存储和水平可扩展性。同时完全自主研发,每个实现细节可控。但也存在一些问题:数据节点Cache程序异常,会导致内存缓存数据丢失,进而导致监控数据丢失,需要从Agent端或者点对点恢复簇。如果数据节点磁盘故障或者机器故障,持久化的FILE也会丢失。还需要从点对点集群恢复数据,数据访问入口需要人工干预切换集群。数据格式和占用空间是固定的,监控粒度没有可扩展性。空数据点也占用存储空间,数据不支持压缩。索引和路由规则依赖外部数据库系统,这些元数据的可用性影响整个系统。Hbase存储引擎的优势Hbase是Hadoop生态栈中的一个分布式列存储数据库。基于Bigtable模型和LSM-Tree存储引擎,用于海量数据的随机读写。在行业中已经应用于大型监控系统的时序数据存储成熟的应用案例,比如某度、某宝。图1:Hbase的存储原理我们来看看使用Hbase存储的优势:数据的高可靠性和高可用性。当数据写入内存时,会向HDFS写入一个HLog。如果一个RegionServer出现异常,内存中的数据会自动从Hlog中恢复。持久化数据存储在HDFS中,默认持有3份,不存在单点数据丢失风险。高性能。LSM-Tree存储引擎保证了Hbase的超高写入性能。其实前面介绍的TMP自研存储系统也是LSM-Tree存储引擎的简化版,所以也有这么高的性能。自然的水平扩展和高扩展性。存储层的DataNode和数据服务层的RegionServer支持自由伸缩和扩展。数据表支持压缩,空列不占用存储空间。Opentsdb尝试及瓶颈分析在准备使用Hbase存储TMP监控数据之初,我们尝试使用基于Hbase的开源时序数据库Opentsdb直接存储服务器监控数据。但是当Opentsdb达到70w/s时,整个Hbase集群就已经超载,响应缓慢,完全无法满足如此大规模的存储需求。我们仔细分析了Opentsdb在超大规模时序数据存储中的主要瓶颈:所有的metrics和tags都必须通过tsdb-uid表进行转换。这种设计旨在压缩rowkey大小,但引入了较大的计算资源开销;data写的Append机制和原来的compaction设计存在较大的性能问题,后面会详细分析;所有数据都放在同一张表中,不利于对数据进行基于时间的维护操作,比如一个月前的非热点数据被采样存储,无法控制region的个数,所以无法进行split受控,对性能影响很大;基于这些原因,我们最终决定直接使用Hbase进行TMP服务器监控数据存储。TMP监控存储设计实践Hbase的使用在整个hadoop生态栈中属于比较复杂的一类。TMP监控存储的设计结合了业界使用Hbase的一些成熟实践经验。同时参考并改进了OpenTSDB在使用HBase支持大规模读写TMP监控数据时更好的设计思路。Region预拆分Hbase中的数据会根据rowkey的范围分布在各个Region中。新建一张表时,只有一个Region,随着Region变大,会开始分裂。这个过程中会消耗大量的网络、磁盘IO等资源,对集群的性能影响很大。同时,由于一开始只有少量的Region,数据的读写很容易落在单个RegionServer上,造成HotSpot现象,严重影响读写性能。因此,对存储表进行Region预拆分是Hbase使用中非常重要的一步。这里我们将每天的数据表预先切分为100个Region,取{0x01,0x01...0x63},即二进制1~99作为splitKeys,第一个RegionRowkey范围为[0x00...~0x01],第二个RegionRowkey的范围是[0x01…~0x02],可以据此推算。结合下一节的Rowkeysalt设计,可以让数据均匀分布在各个Region中。Rowkey和column设计Rowkey设计由salt(1字节)serverID(4byte)时间戳(4byte)监控特征ID(4byte)组成:Salt用serverid哈希后计算出单表的初始Region号剩下的一个字节用于将不同服务器的监控数据均匀分布在表的各个Region中。Rowkey的第二部分是服务器ID。服务器监控数据查询通常是查询指定服务器的某些特征,所以将服务器ID放在第二部分可以大大提高查询效率。timestamp实际上是一个时基,用于在同一行中存储一段时间内的数据。attr_id为featureid,区分具体的监控指标。这里使用一个字节t作为列族。列族的名称本身没有任何意义。主要强调只用一个列族来存储数据,名字尽量小。使用多列族,每个Region会有多个Memstores,会增加内存消耗,不适用该场景。列名(在Hbase中称为Qualifier)是一个时间偏移量,它与Rowkey中的time-base一起构成时间戳来标识数据点的精确时间。Column-basedCompaction在介绍columnCompaction之前,我们先看一下Hbase数据的具体存储结构:图2:表结构和存储结构如图2所示,是表结构和对应存储结构的一个例子,在Hbase中或表存储在这种底层使用LSM-tree的数据库中,表数据会存储在列中。每一行中的每一列在存储文件中都会以Key-value的形式存在于文件中。Key的结构是:行主键列族列名,Value是列的值。这种存储结构的特点是:每一行的主键会根据列数重复存储。列名会被重复存储,每一列的存储都会带着列名。存储的数据按行键排序,相邻的行键将存储在相邻的块中。可以注意到,在Hbase的物理存储中,每一列都会存储该列的rowkey和列族信息。在列很多的情况下,这些重复的信息会占用大量的存储空间。所以这里参考Opentsdb的做法,将同一个时基内的所有列合并压缩为一列(注意这里说的列Compaction和HBase本身的Compation是完全不同的,Hbase的Compation指的是合并多个小HFiles合并为一个大HFile)。Opentsdb的columncompaction是由数据量大小和时间间隔触发的。当并发写操作很大时,会对Hbase产生很大的读写压力,会阻塞写操作,导致性能变差。2.2版本加入的append机制是每次写操作都会产生一次读操作,这对于Hbase的使用来说是非常不经济的,而且当写量很大的时候,会对整个集群的读压力造成巨大的影响。由于这些原因,TMP监控数据每天早上对前一天的数据表进行全表扫描,将每一行数据的列名(Qualifier)和Value合并为一列。在实际网络环境中可以看到数据表经过列压缩后占用的存储空间比压缩前减少了近90%,如下图所示:Hbase性能调优Hbase性能调优是一个比较复杂的事情,还有很多专门的关于Hbase调优的文章。这里只是分享一些更直接的要点:堆和Memstore大小。尽可能增加RegionServer的堆大小。如果写入量远大于查询量,可以增加Memstore与BlockCache的比例,比如0.5:0.3。原因是HBase是基于LSMTree存储引擎,数据会先缓存到Memstore,再刷到磁盘。活泼的压缩。对数据表启用Snappy压缩可以减少磁盘IO并提高写入性能。Hbase本身的Compation线程数。Hbase会在flush和compaction时对rowkey进行排序,适当增加compaction线程,充分利用CPU,提高性能。具体可以将hbase.regionserver.thread.compaction.small/large调整为5.GC调优。GC参数设置不当会引发严重影响性能的问题,例如Stoptheworld。总结基于以上设计和优化,TMP监控数据存储方案相比直接使用Opentsdb,存储性能提升3~5倍。8台RegionServer的峰值写入速率可以达到400w/s,而当Opentsdb达到70w/s时,整个Hbase集群就不行了。目前这套基于Hbase的监控数据存储系统已经上线提供服务,后续计划在存储到Hbase之前增加一个缓冲层,用于列的预压缩,可以进一步提升整体性能.HBase从发展至今,已经是一个比较成熟的开源分布式数据库。其高性能、高可用性和高扩展性可以为海量数据访问提供强大的驱动力。
