为什么写这篇文章
可能有些童鞋已经发现:1.阿里云上的Hbase服务,基于Hbase深度定制和扩展,能比较好的支持时序场景和全文检索场景,其增强版Lindorm,已经作为单独的云服务售卖,单独演进。2.腾讯云上,基于ES构建了CTSDB时序数据库单独售卖,同时也推出了增强版的ES云服务。可以看出,在这两个服务所在团队中,一边选择基于ES来支持时序和全文检索场景,一边则选择基于Hbase来支持时序和全文检索场景。基于ES来支持时序和全文检索场景是比较好理解的,因为它本身的功能就支持这些,但是Hbase如何比较好支持全文检索场景呢?实际上,阿里云的Hbase服务借助了Solr这个搜索引擎来构建这块的能力,因为Solr和ES一样,都是基于Lucene构建的搜索服务。那为什么阿里不和腾讯一样,直接基于ES来构建这块的能力呢?Hbase和ES又究竟有哪些共同点,有哪些差异点呢?
总的来说,在数据量不是非常大的时候,使用ES更加简单,单纯使用ES就能解决各种查询场景的需求,但是当数据量非常大的时候,现有的社区版本ES则存在不少问题,这个时候往往需要进行一些内核级别的改进(否则就需要更多的业务层干预,并且成本消耗会比较高),类似阿里云和腾讯云上提供的ES服务,实际上都对ES内核做过改进。使用Hbase,则需要依赖更多的组件,这样有好处也有坏处。同时如果要支持全文检索场景,还需要引入Solr或者ES等服务,就如阿里云上的Hbase服务则通过引入Solr来解决全文检索的需求。二者的对比大致如下:
- ES和Hbase的写入都是基于LSM树结构,写入性能应该是相当的,不过ES在写入时需要做更多的事情(比如分词构建倒排索引,构建DocValues,进行字段类型的校验,且主副本都需构建索引等),所以ES消耗的CPU是比较高的,但如果只是满足Hbase相关的查询场景,有些东西也是可以通过配置省去的。
- 在查询场景中,ES能同时支持全文检索和时序检索场景,可以支持丰富的查询需求,并且都是ES本身具备的能力。相对来说,Hbase的scan查询就要弱不少,不过开源生态中有OpenTSDB,SparkSQL,Phoenix,Hive等组件,可以用来丰富Hbase的查询能力(其中OpenTSDB和Phoenix是基于Hbase来构建的,SparkSQL和Hive是基于Hdfs来构建的)。但是,在全文检索这块还是缺失的,所以阿里云上的Hbase通过借助Solr来弥补这块的能力。
- 在数据的负载均衡这块,ES相对比较简单,很多时候都需要业务方进行索引的滚动、分裂等操作来实现集群的负载均衡。而Hbase提供的StochasticLoadBalancer策略(会综合考虑Region负载、读写请求数、移动代价等因素),更加符合实际的需求。
- 对于集群的稳定性,ES是计算和存储合一的设计,Hbase则是计算和存储分离的设计,Hbase负责计算部分,存储则交给其依赖的Hdfs来保障。相对来说,计算和存储分离的设计,在海量数据的场景中,稳定性和成本控制是更有优势的。现在很多大公司在使用ES的时候,都采用类似Hbase的做法,将计算和存储分离开,底层存储则利用公司内部提供的分布式文件系统。
- 对于内存管理这块,ES以前的版本内存管理成本是很高的,1TB左右的数据存储,大约会消耗2G左右的堆内存,这样一台机器存储的数据量就很有限,直到2020年的7.3版本中,才开始将一些数据放到堆外,但是目前采用的LRU内存淘汰策略还是比较简单的,腾讯云的增强版ES在off-heap堆外内存的管理这块进行了优化,提供了更加精细化的管理。Hbase在内存管理这块,相对是比较成熟的,它提供了几种缓存管理策略供用户选择,在实际实现中,一般将BucketCache和LRUBlockCache搭配使用,称为CombinedBlock-Cache。
接下来将从某些小点展开看下二者的一些相同点及差异点:
一些重要概念
ES的一些重要概念:
- Index: 对用户暴露的搜索单元,由一系列Shard组成,客户端的读写请求都是针对索引进行。
- Shard: 对应Lucene的索引,是ES后台真实的执行单元,开始针对索引的写入和查询,最后都会拆分成Shard维度,然后分配到数据节点上执行相关任务,由一系列Segment组成。
- Segment: 是ES里面一个完整索引的集合,由一系列Document和索引文件组成。
- Document: 对应一条条数据。
- Translog: 事务日志,类似于Mysql的Binlog,主要用于数据恢复。
Hbase的一些重要概念:
- Table: 由多行数据组成。
- Region: 由很多个Store组成,有多少个列簇,就有多少个Store。
- Store: 由Memstore和StoreFile组成,一个列簇对应一个Store,包含在内存中的Memstore以及持久化到hdfs里的多个HFile,StoreFile的底层就是HFile,它是HFile的一个轻量级封装
- row, column, cell: 一个row由rowKey和多个column及其对应值组成,一张表的所有row都按rowkey的字典序由小到大排序,column由columnFamily和Qualifier组成,cell是row, column, timestamp, type, value等组成。
- HLog: 类似于ES的Translog,主要也是用于数据的恢复。
部署架构
单纯从单机部署的角度看,其实ES和Hbase是差不多的,都是一个安装包稍微配置下就可以启动使用了。因为Hbase单机版部署时,会同时启动Hmaster和HRegionServer,会内置zookeeper,同时通过使用Linux本地文件系统来解决文件的存储。ES默认就是Master节点、协调节点、以及数据节点同时处于一个节点上的。不过单机版的对比没有太多意义,毕竟不可能在生产环境使用。
接下来重点看下ES和Hbase在生产环境中使用的部署架构。
上面第一幅图是ES的部署架构,第二幅图是Hbase的部署架构。
由于ES对于集群元数据的分布式管理,是自己实现的一套逻辑,没有依赖zookeeper,所以少了zookeeper部署依赖。另外对于数据的分布式存储,也没有依赖HDFS,ES是基于Lucene自己实现的分布式存储,所以少了HDFS的部署依赖,所以ES的整个部署显得简单很多。由于ES在默认情况下,是集各种角色于一身的,如果不单独指定角色,则它同时作为maser,协调节点,数据节点,ingest节点等。在数据量不是很大的时候,采用这种部署方式是最简单的。不过随着数据量的增大,读写请求越来越多,考虑到集群的稳定性,这个时候,最好将master角色独立出来,因为master节点相当于集群的大脑,其稳定性尤为重要。并且master节点主要做集群管控相关工作,不承担数据的读写,它的负载并不高,所以一般采用的硬件配置也不需要太高。另外,在有些场景中,如果有必要,也可以将ES的协调节点独立出来。对于ES节点的角色配置,相对来说也是比较简单的,只需要在配置文件里指定下角色类型即可。
对于Hbase的部署:
- 它依赖zookeeper来实现Master的高可用,管理系统核心元数据信息,参与RegionServer的宕机恢复,实现分布式锁等功能。
- 它依赖Hdfs来存储实际的数据,包括用户数据文件、HLog日志文件数据都会存储在hdfs上。hdfs主要由NameNode和DataNode组成,NameNode是整个hdfs的管控中心,它的可靠性非常重要。为了实现高可靠的部署,上图部署了3个JournalNode,他们和NameNode时刻保持通信,能保障两个NameNode的数据一致性。另外每个NameNode上都部署了ZKFC,它们会检测NameNode是否出现异常,然后向zookeeper报告。
- HMaster主要负责Hbase的各种管理工作,如用户的各种管理请求、RegionServer中Region的负载均衡、RegionServer的宕机恢复等、清理过期的日志以及文件等。HMaster采用standby模式,可以比较轻松的实现高可靠部署。
- HRegionServer主要用来响应用户的IO请求,是Hbase中最核心的模块,除了数据的存储,数据读取和数据写入时,其他和数据相关的操作都是HRegionServer完成的。
数据写入
对比写入之前,先了解下LSM树:为了规避磁盘随机写入问题,LSM树将一棵大树拆分成N棵小树,这些小树首先会写入内存中,随着小树越来越多,这些小树就会批量更新到磁盘中去,同时小树也会定期merge成大树,提高查询效率。ES和Hbase都采用了LSM树方式进行数据写入,所以他们写入性能都是比较高的,下面具体看下两者的写入流程。
Hbase写入流程:
- Hbase Client对写入请求进行预处理,并根据hbase:meta元数据定位到需要写入的RegionServer,然后将请求发送过去。
- RegionServer对数据进行解析后,首先会写入HLog(HLog的主要作用是当有节点出现宕机等异常情况时,可以根据它进行数据恢复),然后再写入对应Region的Memstore中。
- 当Region中的Memstore超过一定的阈值后,会执行Flush操作,将内存中的数据写入文件,形成HFile。在HFile中,主要由很多的block组成,为了提高后面的查询效率,会构建出DataBlock,IndexBlock和Bloom Block,MetaBlock等,并且这些Block的元数据信息会形成单独的文件,在RegionServer打开HFile时会加载到内存作为查询入口,其中BloomBlock里采用的布隆过滤器在查询时可以大幅提高查询效率。
ES写入流程:
- 协调节点处理用户写入请求,然后根据元数据信息确定需要写入的分片,之后将请求发送至数据节点。
- 数据节点会根据mapping相关的字段属性,对数据进行相关的Lucene索引构建,如倒排索引、DocValues、FiledData等。此时数据是写在Memory buffer中,写完索引文件之后,才会写translog。这里的顺序和Hbase不一样,主要原因大概是写入Lucene时,Lucene会对数据进行一些检查,可能导致Lucene写入失败,如果先写translog,就要处理写入translog成功,但写入Lucene一直失败的问题。
- 在Memory buffer中的数据每隔一段时间,就会refresh到OS的文件缓存中,然后到达一定的阈值或者index buffer超过设定值时,会执行Flush操作,将文件缓存中的数据持久化到磁盘中,当然如果一直没有达到阈值,操作系统本身在一定的时间间隔里也会将OS文件缓存中的数据持久化到磁盘。
数据查询
和数据写入相比,ES和Hbase的数据查询都要复杂不少。另外,和Hbase相比,ES支持的查询场景更加丰富。对于ES包含get和search两种类型。与之相对应的,Hbase包含get和scan。因为get比较简单,这里主要对比search和scan。
Hbase和ES查询的复杂性,主要是因为它们一次查询都可能涉及多个Hbase的Region(或ES的shard),多块缓存,或者多个数据文件。另外ES和Hbase中的更新和删除操作都没有真正地更新或删除原始数据,更新都是通过多版本号来实现,删除都是通过加’deleted’标签的方式实现。这就使得查询时,需要感知这些,才能保证查询的准确性。另外,在ES中,由于查询场景的多样性,需要考虑的查询场景是非常多的。
Hbase查询主要专注于rowkey的范围查询,它的各种设计都围绕着这种场景展开,在这种场景下,它的查询是很快的。主要原因有:1.它会将一次大的请求切分成很多小的请求。2.它能根据keyRange过滤、timeRange过滤、布隆过滤器等快速过滤不符合条件的HFile。3.基于HFile的索引树以及BlockCache机制可以快速找到HFile中对应的key。
ES的查询,最主要的优势在于它基于倒排索引构建的全文检索能力,这块是Hbase所不具备的能力。当然,ES除了倒排索引,也有基于DocValues的正排索引,DocValues采用列式存储,可以比较快速地实现聚合和排序查询场景。通过ES提供的DSL和Aggregations,以及SQL查询引擎,用户可以方便的实现各式各样的查询需求。但是ES的索引成本是比较高的,主要是前面提到的,ES为了提高搜索性能,会将一些索引数据加载到堆内存。这块消耗最大的是ES里面的倒排索引对应的FST索引文件,它对内存的消耗比较高,也就是文章最开始提到的内存管理差异点,这里不再赘述。
负载均衡
在大数据写入场景中,由于读写请求的不均衡,各个节点之间就可能出现热点问题,就可能出现节点负载很不均衡的情况,从而进一步影响了整个集群的稳定性。所以控制好整个集群的负载均衡是很重要的一件事。在负载均衡这块,我觉得Hbase是要比ES成熟不少的。
首先讲下ES,它只保证集群中各个节点的分片数量是相对均衡的,但不保证节点真正的负载是相对均衡的。因为可能各个分片之间的数据读写请求差异很大,单纯从分片数量维度做到均衡是远远不够的。所以,在真实使用的过程中,往往需要业务方自己去实现索引的合并,迁移,分裂等功能,从而实现真正的负载均衡,ES只是提供了一些基础的API供用户调用(如:rollover, shrink等)。
Hbase则提供了两种均衡策略,1.SimpleLoadBalancer策略,类似于ES的均衡策略,只是保证各个RegionServer的Region个数基本相等,但没有考虑真正的负载。2.StochasticLoadBalancer策略,它会加权计算各种负载情况(包括:Region个数,Region负载,读写请求数,stroeFile大小,Memstore大小,数据本地化率,移动代价等),这也是Hbase默认的负载均衡策略。
也就是说,Hbase会结合真实的负载情况,自动实现Region的迁移、合并、分裂等操作,可以减少使用方的干预。而ES则不具备这种能力,这也是当前使用ES比较麻烦的一个点。
数据合并策略
对于采用LSM树进行数据写入的数据库来说,由于会存在很多小文件,而在对大量小文件进行数据读取的时候,效率比较低。所以,一般都需要对小文件进行合并操作。在ES中,这种操作叫Segments merge。在Hbase中,这种操作叫HFile compaction。另外,在Hbase中,还对compaction进一步细分为minor compaction和major compaction,其中major compaction就是将一个Region下的HFile合并为一个大HFile,这是一个非常昂贵的操作,会在短时间产生大量的IO和网络消耗,一般生产环境对这个操作会非常谨慎。
【触发时机】
在ES中,触发merge动作的时机主要有:1.数据最开始写入memory buffer中,然后间隔一段时间后(间隔时间为refresh-interval,可修改),会执行refresh操作,此时会写入OS的文件缓存中,这个过程会触发一次merge。2.在文件缓存中的segments,当达到indexing buffer的阈值或者达到flush_threshhold,这些segments会执行Flush操作,也就是Lucene的commit操作,会写入磁盘,这个过程也会触发一次merge。3.手动调ES的接口执行merge操作。
在Hbase中,大致和ES是一样的,不过在细节上还是有不少差异。主要有:1.Memstore Flush: 数据最开始写入Memstore中,当满足一定的条件后(如:达到Memstore/RegionServer/Region/HLog级别的限制阈值,或者超过一定的时间周期),会执行Flush。2.后台线程周期性检查,如store中总文件数是否大于阈值,是否满足major compaction条件等。3.手动触发。
【合并策略】
由于文件的合并在短时间会消耗大量的IO和网络带宽,所以是一个比较昂贵的操作,选择什么样的合并策略,是一件很重要的事情。ES的合并策略比较单一且用户没有其他选择,只能对这个策略里的一些参数进行调整(如:每层允许的segments数量)。相对于ES,Hbase提供了更多的合并策略,用户可以根据自己的业务特点选择合适的合并策略(如:Exploring Compaction Policy, Stripe Compaction等)。
宕机恢复
当集群越来越大之后,比如几百个节点的集群,出现一两个节点宕机,应该是时长有之的情况,那么在节点出现宕机时,能否快速恢复,并且不会造成数据丢失,不会造成读写异常,就显得很重要了。
在ES中,主要存在Master,协调节点,数据节点三种角色。对于master,如果是独立部署,一般会有3个节点,可允许1个节点挂掉。如果是非独立部署,则允许一半以上的master节点挂掉,不会影响master工作。由于master本身负载不高,采用独立部署模式,一般不会出问题。对于协调节点,因为它本身不存储数据,主要做读写请求转发以及搜索时的数据聚合,协调节点宕机后,master节点会感知到这个变化,后面的读写请求就会转发到其他节点上。宕机的节点恢复后,又会自动加入到集群中,所以协调节点的宕机影响也是很小的。重点是数据节点的宕机,此时影响相对来说就要大不少。当有数据节点宕机后,master会感知到这个变化并通知给其他节点,对于挂掉节点上的主分片,其副本分片马上会升级为主分片(所以主副分片的数据一致性保障是很重要的),之后会另外找其他节点生成一份副本。对于挂掉节点上的副本分片,就只需要另外找一个节点再生成一份副本就行(这里什么时候生成丢失的副本,和选择的策略有关,默认在很短的时间内就会触发,在这种情况下,如果节点过一会就恢复了,就会涉及大量数据移动,所以生成环境中,一般会等待比较长的时间,然后在等待的时间内发出告警,让维护者及时启动宕机节点,如果宕机节点还是无法恢复,才会生成对应副本,这样可以减少很多无用的资源消耗)。
在Hbase中,涉及的组件很多,但由于zookeeper和hdfs都是其依赖的组件,并且他们的可靠性保障以及宕机恢复机制也是比较成熟的,这里暂不讨论。不过Hdfs中的NameNode节点要特别注意下,因为它相当于整个hdfs集群的大脑,一旦它出问题,整个Hbase的读写操作就无法进行下去了。所以生产环境中,NameNode一定不要有单点问题,并且最好不要和DataNode节点混合部署在一台机器上,在hadoop2.0之后,为了解决NameNode的单点问题,已经支持NameNode HA高可靠部署方式了,前面在部署架构中也有说明。
对于Hbase中的Master,它主要负责集群的负载均衡和读写调度,并没有参与用户的读写请求,所以整体负载并不高,并且可以比较简单的实现Master HA高可靠部署,所以Master的宕机恢复是很容易的。最后,就只剩下RegionServer了,实际上RegionServer也不承担数据存储,不过数据写入前,会先写MemStore,并且这部分的数据是写在内存里的。所以,RegionServer宕机后,就涉及到这部分数据的恢复。这里就要用到HLog来进行恢复了,所以在生产环境中,不要轻易禁掉HLog,虽然不写HLog可以提高写入性能,但是一旦出现RegionServer宕机,就会造成数据丢失。
当RegionServer出现宕机时:首先,zookeeper会感知到这个变化,同时把这个变化告诉master。然后,会切分未持久化数据的HLog日志。之后,Master会重新分配宕机RegionServer上的Region。最后,会回放HLog日志补救数据,完成数据恢复。因为整个恢复过程涉及的HLog数据量并不会很大,所以整个恢复过程相对来说还是可控的。
本人专注于大数据和日志领域,欢迎有兴趣的人一起交流。另外,由于本人水平有限,文章中难免会出现一些错误,欢迎大家指正!还有,转载请注明出处,谢谢!