在《HBase:The definitvie Guide》里,Lars George首先从多个dimensions讨论了NoSQL与传统RDBMS的异同。那么HBase在这些纬度上,是如何选型和实现的呢?

Data Model

一般认为Hbase是column-oriented。但是同样在该书里,作者也强调了HBase的column-oriented与RDBMS里的column oriented又有所不同。HBase在存储时,确实是以column family为单位的,但在查询时是以key为基础的。

Note, though,that HBase is not a column-oriented database in the typical RDBMS sense, but utilizes an on-disk column storage format. This is also where the majority of similarities end, because although HBase store data on disk in a column-oriented format, it is distinctly different from traditional columnar databases: whereas columnar databases excel at providing real-time analytical access to data, HBase excels at providing key-based access to a specific cell of data, or a sequential range of cells.

Storage Model

HBase是persistent, 但它其实是一个memory与disk的结合体。增删改时数据先写WAL(可配),再写入memstore,在memtable满了之后,才会flush到持久化存储介质(一般是HDFS,3备份,都可配)的HFile里。而查询时,会merge memstore和多个HFiles的数据。而且,为了避免disk IO的延迟,对于hot data,Region server会把它以block为单位cache起来,也就是说,最好的情况下,有可能你的查询都在内存里完成。

Consistency model

这里的consistency,我理解是指CAP(consistency, availability,partition tolerance)里的一致性,包含了原子性和隔离性两个概念。HBase保证对于一个row-key里多个column families or columns的写入是原子的,但跨row-keys的写入就非原子了。这也意味着,如果有两个clients A和B在访问HBase,A批量写入一些跨row-keys的数据,B在此过程中读取会看到行与行的中间态,但不会有一行数据的中间态。

Physical model

HBase天生就是分布式的(当然为了学习目的,也可以把所有进程都配置在一台服务器上)。它依赖zookeeper协调各种进程角色,例如master与backup master之间的选主,region servers的存活性检查等,以及关键数据的保存,例如root,meta tables等。

在增加新region servers,对用户基本无感,master会在合适的时机(例如load balance 或者splitting),把流量分配到新服务器上。

Read/write performance

HBase支持random access, range scan, sequence access等,由于其数据是按key有序存储的,且存在block cache,所以比较高效的是range scan和sequence access。大量的Random access会使block cache无效,且造成频繁的disk IO,甚至Java heap问题,导致GC,性能会受到影响。

写入是先追加写WAL,再写memstore。后者是内存操作,耗时较小。WAL可以配置是实时flush,还是定期、阈值flush,由于其底层存储也是HDFS,故flush的代价较大,需要平衡持久性和写性能。

Secondary indexes

HBase不支持secondary indexes。但可以通过row-key,column family,version等的设计,模拟一些secondary indexes的场景。而且在HBase的设计理念里,也可以通过一些反范式的方式,例如冗余存储,提高查询性能,所以,是否一定要依赖secondary indexes也是一件值得商榷的事情。

Failure handling

首先,HBase架构里,master是一个单点,但由于client对数据的ACID不需要经过master,是直接与zookeeper和region servers通信的,所以master故障时,服务仍然可用。

但master的故障,在恢复或容错之前,会导致表结构、集群配置等无法修改。而且load balancing、splitting等操作也无法进行,如果再同时遇到region server的故障,那么也无法恢复等等。所以,master的故障还是非常需要重视的。

它的容错通过backup masters与zookeeper集群配合的选主完成,由于master上没有什么不可恢复的数据,例如region servers配置等是在zookeeper上,每个region server有哪些分片数据,是由region server自己维护并上报的,所以master的切换一般不会造成数据或元数据的丢失。

其次,如果region server故障,由于在逻辑层它是单点,所以短期内其上拥有的分片都是不可访问的(??),但数据不会丢失。

master监测zookeeper 临时节点的变化,感知到region server的故障(不论是硬件还是网络导致的),会选择新的region server来承担它之前的任务。在切换前,需要将故障region server的WAL日志splitting(由master和多个region servers配合完成)和replay(新region server完成,它之前需要从HDFS加载各个分片对应的已持久化的HFiles)。之后新的region server可用。

底层存储层,如果使用的是HDFS,则failure handling是由hdfs保证的。

Compression

如果不是大数据,用HBase就太复杂了。而既然是大数据,那么压缩就是一个重要的问题,动辄上T的数据啊!HBase支持多种压缩算法,例如gzip等,且支持扩展。这里指的HBase的压缩是kv层面的,底层的HDFS可能还会有压缩吧(??)。

Load balancing

HMaster定期会启动load balancing(默认5min),检查有没有负载不均衡的情况并予以处理。其理念比较简单,就是每个region servers平均分配regions。

元数据的修改,应该比较好办,耗时也较小。但假设A region server负载重,需要把一些regions移走,那么在此之前,它需要flush WAL,flush memstore,如果本身负载就重,再做这些事情,也比较麻烦吧?HBase有专门的config限制balancing的耗时,默认是2.5min。

由于没真实经历过,蛮好奇在实际运维中,是如何解决的呢?选择流量低峰期收工触发?

Atomic read-modify-write

在上面consistent里,已经介绍了HBase的row层级的事务。同时,HBase还提供Compare and set(CAS)操作。例如checkAndPut、checkAndDelete,针对相同的rowkey,先check是否等于指定值,然后再put or delete。

HBase的增删改操作,如前所述,都需要先写optional的WAL,再写memstore。猜测memstore是可以用row级别的锁来保证没有竞争,但WAL呢?如果涉及一个row下的多个column,且分成多个kv pair写入log,中途失败了怎么办呢?那么在replay的时候就破坏了原子性了!为了解决这个问题,HBase将一次写入的、同一rowkey的、多个column的数据,封装为一个WALEdit对象,一次性写入log,这样就保证了故障时的原子性。(异步flush这个waledit时如果失败会怎样?这时client端应该已经接收到true的返回值,认为成功。所以,就丢失数据了!这个topic应该在durability里讨论)

Locking, waits and deadlocks

HBase与数据相关的锁,应该大部分是行级锁。写入有row level lock;get/scan操作依赖multiversion concurrency control-style,所以不需要锁。

以put操作为例,默认情况下,锁是由server端自动管理的。也提供方法,在client端自己构造RowLock实例,并作为参数调用put方法。但这样会增加deadlock的几率,并降低HBase的吞吐!所以,除非万不得已,别这么玩。

get操作也同样提供了RowLock参赛,也同样由于deadlock和性能的原因,不推荐用。

关于锁,也有一些发散的疑问:

每个regionserver共享一个HLog实例,那锁加在哪里呢?HRegion是同步调用HLog写入的,所以会不会是互斥锁保证HLog实例在同一时间,只能被一个HRegion调用?

memstore是一个怎样的结构呢?有序数组?树形结构?数组插入数据的效率太差,应该不大可能。如果是树形结构,那么一旦发生节点关系的调整,会不会对整棵树、或path有影响呢?这依赖于其数据结构的选择。

当跨column families时,涉及多个store,即涉及其中的多个memstores,所以,这个锁是较为独立的?

Leave a Reply