1. Replication 大致流程
从RS的选取:
主机群根据提供给它的从集群zk群组,扫描/hbase/rs目录来发现所有可用的从RS,然后随即挑选一部分服务器来复制数据(默认10%)。例如,当从集群有150台RS,15台会被选取;从集群有5台RS,一台会被选取。
日志复制的Position追踪:
每个主RS在zk中都有一个znode,该znode中都包含一个需要处理的HLog队列(/hbase/replication/rs/hbase12.hadoop,60020,1385024790434/2/...)。每个队列都会跟踪RS创建的HLog,不同队列,大小可能不同。
每次日志滚动后,新的HLog也会添加到znode的同步队列中,即保证所有的资源都知道有新的HLog需要复制到自己的集群中。当复制线程从日志文件读不到新的日志条目,并且队列中还有其他文件的路径,说明该文件也被读完并且没有后续数据写入了,即它可以被丢弃; 如果读到当前文件的末尾,且该文件仍正在被写入,该文件不能被删除。
另外一种情况:日志归档。归档的日志分三种情况处理:
1> 如果一个源已经同步了这个日志,便会忽略这条信息。
2> 如果这个文件在队列中,那么更新其路径。
3> 如果日志正在被同步,且修改是原子性的,读进程会在文件移动完毕之后进行。
读取与过滤数据:
理想情况下,日志文件应该被迅速读取并发送到从RS,以达到最小的同步延迟。实际是受限两种情况:
1) 读取的日志需要过滤,只有被分为GLOBAL类并且不属于ROOT,META表日志的KeyValue会被保留。
2) 默认主机群读取日志信息到Buffer达到 64MB 后才会发往从集群。(如果有3个从集群,则需要缓存192MB的数据,才能开始发送)
清理日志:
如果没有启用同步复制,Master的日志清理线程会按照配置的生存期(TTL)删除旧的日志。那么开启了同步复制之后,就不能这样做,因为超过了生存期的日志有可能还在同步队列中。所以清理线程会先找到超过生存期的日志文件,然后判断这些日志是否在同步队列中,如果在先缓存起来,否则日志会被删除。
2. Replication 异常处理
从RS异常:
1> 如果正在同步的从RS出现了异常(假死,挂了……),它不再响应主RS的同步请求,主RS会先睡眠然后重试(次数可配置),重试完毕还是不行,则重选从RS.
2> 如果从集群的所有RS出现了异常,WAL会回滚日志并存储在zk同步队列里。日志被主RS归档起来,并更新同步队列里日志的路径。当从集群可用后,缓冲区的修改会被同步,主机群积压的日志接着同步。
主RS异常:
主集群的每台RS都会为其他的RS保留一个监听器(watcher),以便当其他的服务器崩溃时收到通知。当主机群的某台RS崩溃,其他的RS都会竞争着为宕机RS创建一个叫做lock的znode,该znode包含宕机RS的复制队列。创建成功的RS会把队列添加到自己的复制队列中,这个新队列的名字为集群ID附加宕机RS的名称。一旦完成,为这个队列新建一个ReplicationSource。
集群正常工作时,ZK的状态如下:
这时 1.1.1.2这台RS突然下线,ZK会第一时间watch到这个动作,最先发现的集群中的某台(1.1.1.3)rs将其在Replication/rs下对应的lock住,并将其考到自己的节点之下。其他的RS(1.1.1.1)发现其被lock后就不做动作。
1.1.1.3启动一个新的线程处理掉所有未被同步的hlog.保证数据不丢失。
3. Replication 实际测试:
主从复制模式: dev81, dev82, dev83 => test88.hadoop, test89.hadoop, test90.hadoop, test91.hadoop
1> 单线程写HBase, TPS 800左右
模拟主RS宕机
过程:
1. dev82宕机 10分钟,然后重启;
2. dev81, dev83宕机 5 分钟,然后重启;
3. 等待5分钟,然后停止数据写入,开始校验数据
主机群:写入数据 836506 row(s)
从集群:写入数据 836506 row(s)
模拟从RS宕机
过程(包括极端情况):
1. test88 宕机10分钟,然后重启;
2, test89,test91宕机3分钟,然后重启;
3, test89,90,91 宕机5分钟,然后重启;
4. test88, 89, 90, 91 宕机5分钟, 然后重启; 中间意外出现Master宕机,立即重启
PS:重启后,出现一波明显的高TPS访问量
5. 等待5分钟,然后停止数据写入,开始校验数据
主机群:写入数据 2031251 row(s)
从集群:写入数据 2031251 row(s)
2> 多线程写HBase, TPS > 4000
模拟主RS宕机
过程-1:
1. dev81宕机 5分钟,dev82宕机 5分钟,然后都重启
2. 等待3分钟,然后停止数据写入,开始校验数据
主机群:写入数据 2541713 row(s)
从集群:写入数据 2541713 row(s)
过程-2 (极端情况-1):
1. 运行25分钟,然后 dev81,82,83 全部宕机
2. 等待1分钟,然后逐步启动 dev81, 82, 83
3. 等待3分钟,然后停止数据写入,开始校验数据
主机群:写入数据 5721059 row(s)
从集群:写入数据 6238011 row(s)
PS: (极端情况-2) 再次重现这个问题时,只是在开始写数据5分钟后,让dev81,82,83全部宕机,然后在全部重启,停止写数据,check,数据一致!
PS: (极端情况-3) 再次重现这个问题时,在开始写数据20分钟后,让dev81,82,83全部宕机,然后在全部重启,停止写数据,check:
主机群:写入数据 3743198 row(s)
从集群:写入数据 3787271 row(s)
PS: 主机群全部RS宕机造成的结果无法预料,所以数据很可能不一致。
模拟从RS宕机
过程:
1. test88 宕机5分钟,然后重启;
2. test89,test91宕机3分钟,然后重启;
3. test88, 89, 90, 91 宕机10分钟, 然后重启;
4. 等待5分钟,然后停止数据写入,开始校验数据
主机群:写入数据 6464858 row(s)
从集群:写入数据 6464858 row(s)
正常情况下
主机群:写入数据 8518936 row(s)
从集群:写入数据 8518936 row(s)
通过详细测试,得出结论如下:
1. 在整个复制过程中,如果没有任何异常的情况下,主从集群数据最终一致。
2. 在整个复制过程中,不管从集群发生了任何异常,宕机一台or全部宕机,只要从集群恢复之后,经过一段时间,主从集群数据最终一致。
3. 在整个复制过程中,如果主机群部分RS宕机,不影响主从集群数据最终一致。
4. 在整个复制过程中,如果主机群RS全部宕机,主从集群数据最终可能不一致。
主主模式 : dev81, dev82, dev83 <=> test88.hadoop, test89.hadoop, test90.hadoop, test91.hadoop
正常情况下:
Test 1:
dev HBase 写入数据:8116619 row(s)
test HBase 写入数据:8907947 row(s)
Test 2:
dev HBase 写入数据:16373203 row(s)
test HBase 写入数据:15883361 row(s)
通过测试得知:在双写的情况下,数据记录数基本处于不一致的状态。而且相同rowkey的记录,由于写两个集群的timestamp不一样,最终的value值也是不一致的。即开启master-master模式的replication时,不能两边同时写表。
结论:
HBase的Replication方案的设计目的是容灾,而不是严格意义上的HA。要做到严格意义的HA,必须保证双集群所有数据(包括timestamp)严格一致,即双向同步复制,就像MySQL的Schooner同步复制方案。这点,可能不是HBase的设计初衷。所以我们线上HBase双集群复制方案,目的是集群数据双向容灾。对于应用来说,仍然是connect到一个集群,只有在发生灾难的时候,才会临时切到另一个集群。这应该足够满足HBase的可用性了,也可以针对两个集群做读写分离,分别优化配置。