一、hbase replication的原理
hbase 的复制方式是 master-push 方式,即主集群推的方式,主要是因为每个regionserver都有自己的WAL(Write Ahead Log)预写日志。 一个master集群可以复制给多个从集群。
复制是异步的,运行集群分布在不同的地方,这也意味着从集群和主集群的数据不是完全一致的。他的目标就是最终一致性。
复制的格式与mysql的状态的复制类似,不同于mysql的状态复制,整个WAL的修改(包括insert delete 和cell的修改)都会按照顺序复制到从集群中。
每个regionserver会记录最后复制的位置,然后每次复制都从最后复制的位置开始。regionserver会维持一个replication的队列,每个slave的的position都会单独维护
WAL 操作的生命周期:
1、客户端插入或删除
2、rs将操作以可以回放的格式写入wal
3、如果影响的cell正是replication的范围之内的cell,将操作放入replication的队列
4、如果slave rs 无法工作,master会重新选择新的rs作为replication的slave rs,并重新发送buffer中的数据
5、同时,wal 会被压缩并且存储到zookeeper的队列中,server rs通过移动操作日志的路径到一个中心的日志路径将操作日志归档。然后将path更新到内存中replication 线程的队列中
6、如果slave集群最终恢复正常,master会将中断复制这些log按照正常复制过程复制
replication内部原理:
hbase复制的状态都存储在zookeeper中,默认情况下,存储到 /hbase/replication。这个目录有两个子节点: peers znode 和 RS znode
如果 人为的删除 /hbase/replication 节点,会造成复制丢失数据
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper, hbase]
[zk: localhost:2181(CONNECTED) 1] ls /hbase
[replication, meta-region-server, rs, splitWAL, backup-masters, table-lock, flush-table-proc, region-in-transition, online-snapshot, master, running, recovering-regions, draining, namespace, hbaseid, table]
[zk: localhost:2181(CONNECTED) 2] ls /hbase/replication
[peers, rs]
peers znode:
存储在 zookeeper中 /hbase/replication/peers 目录下,这里存储了所有的replication peers,还有他们的状态。peer的值是他的cluster的key,
key包括了cluster的信息有: zookeeper,zookeeper port, hbase 在 hdfs 的目录
[zk: localhost:2181(CONNECTED) 3] ls /hbase/replication/peers
[31]
[zk: localhost:2181(CONNECTED) 4] ls /hbase/replication/peers/31
[tableCFs, peer-state]
RS znode:
rs node包含了哪些WAL 是需要复制的,包括:rs hostname,client port,start code
[zk: localhost:2181(CONNECTED) 5] ls /hbase/replication/rs
[hdp4,16020,1635489741181, hdp3,16020,1635489705167, hdp2,16020,1635489626389]
replication 的实现细节
选择要复制的从集群的 regionserver:
当一个主集群的regionserver 要向从集群复制数据的时候,它首先要连接从集群的zookeeper,然后扫描 RS 目录,查找所有可用的sink,随机的选择一个子集(默认是10%的比例)。比如从集群有150个regionserver,它会选择15个rs作为接收复制数据的子集。
每一个master 集群的 rs 都会在${zookeeper.znode.parent}/rs 节点中有一个zookeeper watcher,它来监控slave 集群的变化,当从集群接收数据的rs出现问题,主集群的rs 就会重新选择从集群的接收节点
跟踪日志:
每一个master集群的rs在zookeeper中都有自己的znode,每一个peer都会对应一个znode,而且每一个znode都包含一个需要处理的WAL的队列。
每一个队列都会跟踪对应的rs 产生 的WAL。
当一个source实例化的时候,他包含正在写入的WAL。日志切换的时候,在新文件可用之前,它会被加到每一个slave 集群的znode中。这样是为了保证当一个新log文件出现的时候都有的source队列都可以同步他的数据到slave集群,但是这样的操作太耗资源。如果从一个日志文件已经读不到新的条目并且已经有文件出现在队列中,那么这个日志文件对应在队列中的元素就会被抛弃。
如果edit log 已经没有再用或者log的条目数已经超出了 hbase.regionserver.maxlogs 的配置,log就会被归档。如果一个log被归档,会通知复制的source线程。如果这个log文件还在复制的队列中,那么归档后的目录会在内存中更新。如果归档的时候,日志都已经复制完毕,那么归档就不会影响复制队列,但是读取线程就不能重新读取数据,因为目录已经发生改变。
Reading, Filtering and Sending Edits.
这部分是最重要的实现逻辑。
默认的情况下,source端会尽快的读取WAL log 然后传送到复制流。传输的速度会被过滤器限制,只有 GLOBAL 范围的并且不属于系统表的log会被保留。传输速度还受制于向每个slave rs 发送队列的大小,默认是64M。如果一个主集群有三个复制集群,那么一个rs 就会存储192M数据用来复制。这里不包含那些被过滤掉的数据。
一旦这个buffer(64M)被填满或者读到WAL log的最后,source 线程停止读取log,从之前随机选取的rs子集中随机选择一个rs 发送数据。首先会发送一个RPC,如果RPC正常,正常发数据。如果WAL log文件已经读取完毕,source会将zookeeper 中的复制队列中对应的znode 删除。否则,记录下log的偏移量。如果RPC抛出异常,source将会重试10次,如果都失败,重新选择一个rs。
清除日志:
如果没有配置replication,hbase集群的清理日志线程会根据TTL配置的时间删除旧的日志。如果配置了replication,这套机制就失效了。因为归档的日志有可能已经过了TTL但是还在replication的队列中。如果log过了TTL,这个时候清理日志线程会在每个复制队列中查找是否包含这个log,如果没有,就直接删除。如果找到了,就见这个队列记录起来,下次开始清理log的时候先到记录的队列里面查看。
regionserver 容错:
每一个主机群的regionserver 会监控其他的regionserver,一旦有regionserver失败,就会感知到。如果一个rs挂掉,其他rs会比赛在zookeeper中创建一个znode 把挂掉的rs的znode锁住。创建成的rs会将挂掉的regionserver的znode中的复制队列复制到自己的复制队列,这是因为zookeeper不支持重命名队列,所有的队列复制完毕之后,旧的就会被删除。
下一步,主机群的regionserver会对每一个复制过来的queue创建一个source线程,每一个source线程依然按照 read/filter/ship 这种模式继续复制数据。主要的区别在于,这些复制队列不会复制新的数据,因为他们不属于新的regionserver。当读到最后一个log的结尾,这个复制队列的znode就会被删除,主机群的regionserver 关掉这个复制source线程
二、操作步骤
源hbase与目的hbase版本为1.2.6,zookeeper版本均为3.4.6
在源hbase集群的每个节点下,修改hbase-site.xml 添加如下配置:
<!--开启hbase.replication -->
<property>
<name>hbase.replication</name>
<value>true</value>
</property>
重启,使配置生效。
创建测试表 create ‘student’,‘score’,‘course’
hbase(main):002:0> create 'student','score','course'
0 row(s) in 1.2570 seconds
修改表的复制参数 REPLICATION_SCOPE
hbase(main):003:0> disable 'student'
0 row(s) in 2.2920 seconds
hbase(main):004:0> alter 'student', {NAME => 'score',REPLICATION_SCOPE => '1'},{NAME => 'course', REPLICATION_SCOPE => '1'}
Updating all regions with the new schema...
1/1 regions updated.
Done.
Updating all regions with the new schema...
1/1 regions updated.
Done.
0 row(s) in 3.7700 seconds
hbase(main):005:0> enable 'student'
0 row(s) in 1.2360 seconds
打开源hbase里表student的复制特性
hbase(main):008:0> add_peer '31','192.168.0.124:2182:/hbase'
0 row(s) in 0.0220 seconds
hbase(main):009:0> set_peer_tableCFs '31','student'
0 row(s) in 0.0090 seconds
hbase(main):010:0> list_peers
PEER_ID CLUSTER_KEY STATE TABLE_CFS
31 192.168.0.124:2182:/hbase ENABLED student
1 row(s) in 0.0090 seconds
注意:192.168.0.124:2182:/hbase 为目的hbase所使用的zookeeper地址。
add_peer ‘31’,'192.168.0.124:2182:/hbase’和set_peer_tableCFs ‘31’,‘student’中的’31’ 对应唯一id,每一张表的 add_peer id可随意添加但是唯一。
在目的hbase节点上创建表与源hbase 的表结构一致
create ‘student’, ‘score’, ‘course’
步骤完成,实现主从复制
验证
源hbase插入数据
hbase(main):011:0> put 'student','xiapi001','course:chinese', '001'
0 row(s) in 0.0850 seconds
hbase(main):012:0> put 'student','xiapi002','course:chinese', '002'
0 row(s) in 0.0080 seconds
hbase(main):013:0> put 'student','xiapi003','course:chinese', '003'
0 row(s) in 0.0500 seconds
目的hbase的student表已有数据
hbase(main):001:0> scan 'student'
ROW COLUMN+CELL
xiapi001 column=course:chinese, timestamp=1635490345211, value=001
xiapi002 column=course:chinese, timestamp=1635490587386, value=002
xiapi003 column=course:chinese, timestamp=1635492319864, value=003
3 row(s) in 0.1800 seconds
hbase集群间数据复制已完成。注意开启replication之前的数据不能同步。
如果为了让两集群之间实现互相复制,重复上述操作即可。