其实,开源社区一直在反思为什么HBase 2.0经历了那么长的时间还是没有发布出来。社区也不希望在发布HBase 3.0版本的时候还是和发布HBase 2.0遇到同样的情况。所以虽然目前主要的精力还是放在2.X版本,想要将其变得更加稳定,但是HBase 3.0也已经开始计划了。本文的主要内容就是把HBase 3.0版本中可能的上线的feature先列出来,之后再出现对于其他新feature的需求如果来不及就不再往HBase 3.0里面放了,这也是为了让HBase 3.0能够尽快发布出来。
同步复制
第一个特性就是同步复制,这里指的是支持双机房的强同步。之前HBase是无法跨机房进行部署的,基本上就是在同一个机房里面进行部署,多机房部署中间所造成的延迟可能无法承受。所以同步复制可以支持两个机房之间的强同步,而原来的HBase 2.0是异步的,这个机房是主集群,另外一个是备用集群,两者之间存在几秒的延迟,一旦主机群发生宕机的话,那么备用集群的数据不一定是完整的,这个特性主要解决的就是让备用集群起来变成主机群的时候与之前的主集群所确认过的数据是一致的,至于这些具体是如何实现的,可以参考阿里在HBaseCon Asia2017上的演讲,阿里在内部做过一个版本,而HBase 3.0就是将这个特性做到社区里面去,在2018年的HBaseCon上也会进行详细地讲解。目前在社区中,在HBASE-19064上已经基本上可以使用同步复制了。综上所述,同步复制这个特性在HBase 3.0中一定能够出现。
CCSMap
CCSMap主要解决的就是GC问题,原来的HBase会把实际的数据放到2M的Chunk里面去,但是ConcurrentSkipList来做索引,那么读取一个对象在哪就需要先去ConcurrentSkipList找一下并去Chunk里面读取,这样一来因为现在的机器内存都已经很大了可能达到128G内存,而MapStore也能达到几十G,这样一来每个Value都可能成千上万甚至几千万个对象,这些对象都堆在ConcurrentSkipList里面,GC进行扫描则是非常耗费时间的。目前阿里的团队已经在内部实现将索引放到Chunk里面,所有的数据都是encode到Chunk里面去的,这样一来对象的数量能够减少很多,GC的负担也能大大减轻,这样一来在Java堆上的东西就会很小了,可能就不需要那么多的调整GC参数的策略了。目前HBASE-20312也已经在做这件事情了。
备份&恢复
备份和恢复本来是在HBase 2.0版本要加进去的,但是后来去除掉了。这个就是比较强的一致性,其支持增量备份。目前冷备份就是打一个Snapshot然后考上去,考到另外一个异构的文件系统上去,然后过一段时间再打一个Snapshot再继续拷贝。这样实现的好处是比较简单,不容易出问题。但是可能对于空间的浪费比较严重。而备份和增量特性则是在打完Snapshot之后通过Log一点点进行备份,这样就不会有太多空间的浪费,这一部分目前的代码质量不是特别好,因此在2.0版本没有让它上,而是否能够在3.0版本中增加也不好确定,因为还需要进一步优化和测试。
读路径重构
读路径优化这个工作目前还没有开始,但是已经有很多人提出过类似的问题了,就是HBase的读路径太过于复杂,发现有些优化的位置不太对,导致这部分的代码比较乱,如果现在不再进行整理可能之后就没有人敢去触碰这部分代码了。常见的一个优化就是在一个流上Seek,如果发现Seek的点离自己很近,那么就不需要重新Seek一遍把指针放到那里去,只需要直接往后读取一段就可以了。现在的问题是虽然HBase也有这样的优化,但是在开启Scanner的时候,比如Filter需要跳过这一行会返回到Seek到下一行,但是到底是Seek到下一行还是Skip到下一行将会视情况进行,如果距离很近就会Seek过去,如果距离很远就会Skip过去。
这一层在实现的时候是在StoreFileScanner这一层实现的,这是比较头疼的,因为StoreFileScanner是很多个StoreFile在Merge之后形成的,在这一层进行判断的时候比较讨厌的一点就是下面有好多个文件,所以判断并不一定非常准确。更正常的是应该由StoreFileScanner决定要Seek到哪里,进而判断要Seek到的位置有多远,然后让自己决定到底是Seek还是Skip。所以对于同一个StoreFileScanner而言,下面很多个StoreFile,有的需要Seek,有些需要Skip。在StoreFileScanner这一层决定使用pread还是stream read,熟悉HDFS的同学可能知道如果开启HDFS的stream read,那么就会不停地发送数据,pread的好处就是往HDFS发送请求的时候会说从哪个位置开始读取哪一段,当读取完毕之后就不会再发了。pread的连接用完之后可以放回到连接库中去,下次有人需要可以重用这个连接。如果使用stream read可能读取出来少了4K数据,那么就不读取了,那么这个连接也就废掉了,只能关闭了。其实在HBase 1.X版本中Scan有一个参数是setSmall,如果不是用small就默认使用stream read,如果setSmall就是使用pread。目前HBase 2.0做了一个适配默认开始时都是想要使用pread,当发现读取过长之后切换成stream read,但是其实这与上面存在同样的问题,因为其也是在StoreScanner层做的,所以问题也很多,有一些文件pread流,有的则是用stream read流。
还有一个很严重的问题就是减少不必要的bytes比较,看过HBase源码的人就会比较熟悉,特别是Filter里面需要切换了rowkey,这种情况下需要先比较一下,但是可能之前在其他地方已经比较过了,但是再需要比较的时候拿不到信息。其实这部分测试过,这样的比较浪费了大量的CPU资源,对于性能的影响是比较大的。所以这部分需要想一些办法尽量减少不必要的bytes比较,一个可能的办法是向Scanner的Context里面放一些东西,之后从Context里面取数据,这样在切换之后如果没有比较过就比较一下,之后就存储下来,就不需要比较了。还有一个问题就是保证每次读完一个cell,RegionScanner都有机会退出,也就是保证heartbeat一定能生效,这个在HBase 1.X版本中已经上线了。这里heartbeat的意思就是可能设置了Filter很稀疏,可能很久都不能扫描到有用的数据返回给你,但是Client却有Timeout,为了防止过了Timeout所以会返回没有扫描到数据的提示信息,需要做这样一个事情,所以一开始发现的最大问题就是Filter退不出来,一直在扫描,还来不及发送heartbeat就已经超时了。之前做过一件事情就是Filter可以跳出来,但是目前代码里面还有一些问题就是读取Excel还不能跳出来,这方面也需要进行改进,否则在真正实现的时候就会失效了。这部分是主要提出的问题,目前还没有具体的工作还没有开始实现。
写路径重构
目前一些团队对于写路径优化上做了一些工作,看起来效果基本上还可以。基本上就是用Staged Event Driven的架构实现,这里的写路径相比于读路径要简单很多,这是因为读路径最主要的具有Filter,这就导致了读路径的逻辑会复杂很多。而写路径则比较清晰了,先写出readlog,成功之后更新memstore并在最后complete mvcc就完成了。这样的改造使得需要的线程数会更少一些,而之前就是一个线程从头跑到尾,写readlog时如果没有写过来,就会一直在这里堵着等待,之后的更新memstore并在最后complete mvcc也会堵在这里等,所以线程很多时间都是什么事情都没干的,改成Staged之后线程需要的就会更少一些,性能就会更好一些。写路径比读路径更加清晰和简单,但是写路径需要注意的一点就是绝对不能出现Bug,读路径出现Bug可能只是数据读不到,但是如果写路径出现Bug写入的数据就丢到了。
其他Feature
其实还有一些其他的Feature本在也在HBase 2.0版本中计划上线,但是因为HBase 2.0版本计划了太多的新特性,因此不得不将这些特性拿出去,不然就更加发布不出来了。这些特性其一是C++ Client,这个基本上有一个可用的版本了,但是还需要后续的测试包装等步骤。其二是HLC,Google是使用原子钟在所有机器上面做了一套硬件,所以拿到的是时钟误区基本差不多,时间误差允许范围内。而HLC是在没有这样的硬件条件下,依靠NCP实现各个机器之间的时间同步,HBase是想把Timestrap类似的东西用HLC实现。还有一个是Spark的Connecter,这个可以帮助Spark实现一些计算的下推,这部分在Master分支中也是有的,只不过因为没有完成所以没有出现在HBase 2.0版本中。但是这些特性并不一定都能够在HBase 3.0中上线。