搞大数据的基本都绕不开这样一个场景业务扩张到一定程度单集群开始吃紧要么是机房容灾要求要么是主集群扛不住分析查询的折腾。这时候HBase跨集群数据复制就会从架构图上的一条辅助连线变成实打实的日常运维工作。这篇文章我会围绕这套方案把选型思路、核心机制、配置步骤和踩过的问题一次讲透适合正在搭或已经维护跨集群同步的运维、数据开发、架构师参考。别急着拿工具就往生产上怼先把方案背后的逻辑搞清楚后面出问题才知道从哪里下手。1. 跨集群复制的核心场景与方案选型1.1 三种典型需求我见过的大多数跨集群复制需求逃不出下面三类。第一类是容灾备份。这是最朴素的需求主集群跑在线业务某个机房万一出故障备集群能顶上。这类场景对RPO有要求希望数据丢失窗口尽量小同时对RTO也有预期切流要快。第二类是读写分离或者叫分析下放。主集群承担大量线上读写报表、数仓抽取、临时统计分析这类重查询经常把RegionServer的CPU和IO打满。把复制集群作为分析专用集群查询随便跑主集群的稳定性立刻上一个台阶。第三类是集群迁移和升级。比如老集群版本太低、机器老化或者要把数据从一个机房迁到另一个机房需要先把存量数据搬过去再做增量同步最后切流量。很多团队一开始只是想做个备份做着做着发现复制集群还能承接分析任务于是慢慢演变成了准生产环境。这里提醒一句复制集群一旦承接分析查询它的角色就不再是纯备份容量规划、Compaction、Region均衡都要按生产标准来管否则等真出故障要切换时备机可能已经被慢查询拖垮了。1.2 主流复制方式对比HBase生态里做跨集群数据复制常见的有三条路。方案原理典型场景优点短板HBase原生Replication基于WALWrite-Ahead Log预写日志实时推送增量同步、容灾、读写分离延迟低、自动化程度高、增量持续不断只同步开启后的增量存量数据需另想办法Snapshot ExportSnapshot表快照文件级拷贝存量迁移、全量初始化、版本升级快照期间基本不影响在线读写速度快不是持续同步只能作为一次性铺底CopyTable / Export Import客户端Scan出来再Put过去小表迁移、临时表备份操作简单不用太关注集群底层大表效率差Scan和Put会打满两端集群从项目落地的角度说真正的跨集群数据复制方案日常增量部分基本都选HBase原生Replication它是这套链路的核心。Snapshot和CopyTable更多是辅助角色用来解决存量数据铺底。选择原生Replication还有一个很重要的原因它不需要业务侧改代码。只要表结构一致、表开启复制属性源端写入的数据就会自动出现在目标端。对于几十张甚至上百张表的大集群来说这是唯一能规模化落地的方式。业务侧双写或者自己写同步任务的方案看着灵活实际上每张表都要开发维护数据不一致时还没地方说理后期成本极高。2. HBase Replication链路是怎么工作的2.1 一条写入从源端到目标端的完整路径要理解Replication先理解HBase一次写入做了什么。客户端写入一个Put时RegionServer会先把这次修改追加到WAL里然后写入MemStore最后刷成HFile。WAL的存在意义是宕机恢复记录的是这份数据从产生开始的所有操作历史。Replication做的事情相当于在WAL后面又接了一个“搬运工”。RegionServer上运行着一组ReplicationSource线程它们盯着本地新产生的WAL文件读取里面的WALEdit记录把数据通过RPC推送到目标集群的RegionServer目标端再由ReplicationSink线程把数据写进目标表。整个链路可以用日常场景来类比主库写一份操作日志日志每追加一条专职搬运工就拿着新内容跑到从库去重放一遍。主库业务完全感知不到搬运工存在这就是Replication最大的优势。这个链路里值得注意的有几个点。第一搬运是异步的所以主从之间一定存在延迟窗口。第二搬运的是操作记录不是结果数据所以目标端的表结构必须和源端一致列族对不上就会报错。第三搬运工读取的是WAL如果目标端长时间连不上、WAL又被源端清理掉了这个搬运就会断在一个坑里后续数据再也补不回来。2.2 几个绕不开的核心角色Peer源集群和目标集群之间的一条复制关系。定义时指定目标集群的ZooKeeper地址、端口、根节点路径HBase靠它在源端建立一条独立的推送通道。ReplicationSource源端RegionServer上的推送线程组负责读WAL、过滤数据、RPC推送。ReplicationSink目标端接收数据的线程负责把收到的WALEdit转换成目标表的数据写入。ReplicationQueue源端每个RegionServer在ZooKeeper上维护的复制进度队列记录当前推到哪个WAL文件的哪个偏移量。实际排障时ZooKeeper里的复制队列节点非常有用路径大概在/hbase/replication/rs下面。源端某个RegionServer宕了它没推完的WAL会被其他RegionServer接管队列节点就记录了这种归属关系。这个机制保证了单个RegionServer故障不会导致复制中断。2.3 顺序、幂等与循环复制同一个RegionServer写入的数据是单线程顺序读WAL推送的所以单RegionServer内的写入顺序在目标端会保持一致。不同RegionServer之间没有全局顺序HBase也不承诺这对绝大多数业务来说够了最终一致就能接受。Replication是at-least-once语义意思是极端情况下一条记录可能推了不止一次。好在HBase的Put、Delete天然带行键和版本同一个rowkey的数据重复写入目标表最终结果也是覆盖成最新值不会被算成脏数据这就是幂等性的底气。不过要注意Increment和Append这类操作在Replication里容易产生重复累加的问题。老版本HBase对这类操作的处理不理想我一般建议同步链路涉及的关键表尽量避免用Increment或者业务侧能容忍计数器类数据偶尔偏大。这个坑一旦踩中数据对账会非常痛苦。循环复制则是双向同步场景里的隐患。如果两个集群互相配置了Peer一条从A写入的数据推到B之后可能又被B当成新数据推回A。HBase用集群ID机制来防止这种无限循环每个集群在复制时会给数据打上来源标记目标集群遇到来源是自己的数据就不再往回推。所以不要手动改集群ID改了后果自负。3. 配置跨集群复制的完整实操流程3.1 部署方式与前置检查先看版本。源集群和目标集群的HBase版本尽量保持一致至少也要保证兼容。版本差太大时WALEdit的序列化格式可能变目标端ReplicationSink解析会抛异常表现就是队列一直堆、数据不动。再看网络。两个集群的RegionServer之间必须能互通ZooKeeper端口、HBase RPC端口要放行。最容易踩的坑是源端ping通目标端ZK就以为没问题实际上RegionServer之间的16020端口不通复制一样起不来。然后是ZooKeeper。两个集群必须用独立的ZK集群Peer里配置的是目标集群的ZK地址加chroot路径。如果两边共用一个ZKHBase复制会直接乱套生产环境绝对不允许这么干。最后是表结构。目标端必须提前把要同步的表建好表名、列族保持一致。曾经有个同事图省事目标端少建了一个列族结果这个表所有数据复制都报错排查了半天才在目标端RegionServer日志里找到线索。3.2 配置参数与HBase Shell操作HBase新版默认已开启Replication能力不需要在hbase-site.xml里额外设置总开关。但有几个参数值得根据集群规模调整比如ReplicationSource线程数和发送队列容量。配置示例property namereplication.source.maxthreads/name value20/value description源端每个RegionServer用于推送复制数据的最大线程数/description /property property namehbase.master.log.cleaner.ttl/name value3600000/value descriptionWAL文件保留时间毫秒保证复制延迟大时有缓冲余量/description /propertyreplication.source.maxthreads默认值不算大写流量高的集群建议提高到10到20。WAL保留时间默认可能只有几分钟当目标集群维护或网络故障时复制队列很容易断所以我会把保留时间调大一些比如1小时。代价是HDFS上WAL占用空间增加需要根据集群磁盘情况权衡。接着在源集群执行关键操作。第一步建立复制Peer。CLUSTER_KEY格式是目标集群的ZK quorum、端口、chroot路径hbase shelladd_peer 1, CLUSTER_KEY zk1,zk2,zk3:2181:/hbase如果目标ZK地址包含多个节点逗号在HBase Shell里需要转义处理不同版本处理方式略有差异。我一般建议写成一个字符串参数避免Shell解析逗号出问题。执行后可以用list_peers确认list_peers第二步给表开启复制属性。创建一个新表时直接指定create biz:user_tab, {NAME cf1, REPLICATION_SCOPE 1}对已经存在的表用alter修改列族的REPLICATION_SCOPE为1alter biz:user_tab, {NAME cf1, REPLICATION_SCOPE 1}REPLICATION_SCOPE默认是0不改就永远不会同步。这个属性是列族级别的所以在建表时就要规划好哪些表要复制哪些不需要不要一股脑全开。第三步如果希望某个Peer只同步指定表可以用tableCFs参数限制set_peer_tableCFs 1, biz:user_tab:cf1;biz:order:cf1这个参数作用很大。生产环境里一个集群往往同时跑在线和离线业务全量复制会浪费大量带宽和存储用白名单机制只复制关键表才划算。3.3 存量数据的铺底迁移Replication只复制开启之后的增量数据。假设你先在源端开启复制再设置表的REPLICATION_SCOPE那开启之前的存量数据不会自动出现。所以生产落地的标准动作是先铺底后增量。铺底用Snapshot最合适。先在源端给表打快照snapshot biz:user_tab, snap_user_before_rep快照机制不会阻塞在线读写对业务影响很小。然后把快照导出到目标集群的HDFShbase org.apache.hadoop.hbase.snapshot.ExportSnapshot \ -snapshot snap_user_before_rep \ -copy-to hdfs://target-cluster-namenode:8020/hbase/.snapshot \ -mappers 8-copy-to路径要放在目标集群的HBase快照目录下这样目标端的HMaster才能识别。然后到目标集群执行恢复restore_snapshot snap_user_before_rep这里有个操作顺序问题。正确的节奏是先铺底、再建Peer、最后开表REPLICATION_SCOPE。如果顺序反了从建Peer到铺底完成这期间写入的增量数据会因为目标表已经存在而重复写入或产生版本冲突虽然不影响最终一致但对账时会很烦。3.4 复制效果验证配置完成后第一件事是看复制状态。HBase Shell里执行status replication输出里最关键的两个指标是AgeOfLastShippedOp和每个Peer的队列堆积情况。AgeOfLastShippedOp表示最近一次成功推送数据距今的时间正常情况下应该在秒级到分钟级。如果这个值持续增大说明链路有问题目标端要么连不上要么写得太慢。再做一次实际写入验证。源端往表里写入一条带标记的数据put biz:user_tab, rowkey_test_001, cf1:name, smoke-test然后到目标端Scan这条数据scan biz:user_tab, {STARTROW rowkey_test_001, LIMIT 1}能看到记录就说明基础链路通了。大表不建议直接走count对比非常耗资源。我通常的做法是挑一批固定时间段写入的rowkey前缀去抽样对比比如取最近5分钟写入的100个随机rowkey两端逐个Get对比时间戳和版本号效率和可靠性都在线。4. 同步推进节奏与容灾衔接经验4.1 先小表后全量灰度思路生产集群几十张表一次性全开复制是很危险的动作。所有RegionServer同时开始读WAL推送源端网络出口瞬间打满目标端RegionServer也可能被写入风暴冲垮。我的习惯是分三批推进。第一批挑流量最小的两三张表跑通链路、确认延迟和稳定性。第二批加入中等流量的表。确认目标集群处理能力足够后第三批再把剩余大表全部打开并重新评估延迟指标。每次开启新表还要关注源端RegionServer的CPU和网络IO。Replication线程虽然不大但在高写入集群里推送本身会有资源开销。如果添加表后AgeOfLastShippedOp出现明显上升说明链路已经接近瓶颈要先把目标集群的资源问题解决掉再继续放量。4.2 延迟监控与追赶方案跨集群复制一定要有监控不能靠人肉去看Shell输出。至少要监控三个指标AgeOfLastShippedOp超出阈值报警比如连续5分钟超过5分钟。源端ZooKeeper复制队列长度队列积压代表推送阻塞。目标端RegionServer的写延迟和BlockedTime目标端跟不上队列只会越堆越长。如果延迟起来了先判断是网络问题还是目标集群能力问题。网络抖动一般延迟会自己恢复目标集群写能力不足则表现为目标端写BlockedTime高、队列持续不降。追赶的方式无非是几种增加源端ReplicationSource线程、给目标表预分区并提前做Compaction优化、临时调大目标端RegionServer的MemStore和写线程数。最忌讳的是靠重启RegionServer来“解决”重启往往会导致复制进度回退反而制造更多重复数据和对账工作量。还有一个容易忽略的点WAL保留时间。如果目标端停机超过WAL保留时长源端的WAL已经被清理复制就没有办法自动追平。这种情况下只能从别的备份补数据非常被动。所以维护容灾集群必须确保WAL保留时间大于最大容忍停机时间我一般保守设为24小时宁可多占HDFS空间也要给排障留出余地。4.3 容灾切换时的数据一致性窗口原生Replication是异步复制这决定了它不可能做到零丢失切换。源端WAL里还有未推送的数据时如果源集群整个宕掉这些数据在目标端是不存在的。很多架构师喜欢拿这个说事说Replication不是真正的容灾。从技术角度我认可但实际生产里大多数业务的RPO要求是分钟级或小时级异步复制配合及时告警完全够用。关键是切换流程要想清楚。我在某跨机房容灾项目里用的流程是检测到主集群故障立即停业务写入等待复制队列推空或者确认剩余延迟在可接受范围然后目标集群强制激活把域名切流到目标端。如果主集群还能访问可以先把主集群Region置为只读再继续追平延迟这样RPO能压到极低但操作复杂度和风险也高不适合每次演练都做。平时一定要做切换演练不要等到真出事才第一次切。演练过程能暴露出很多平时看不到的问题比如目标集群容量是否真的够、各团队是否知道切换后怎么处理、监控告警是否能及时触发。5. 常见问题与排障实录5.1 常见问题速查表现象可能原因处理方式复制完全没有生效表REPLICATION_SCOPE没设置Peer状态异常目标表不存在检查列族属性、list_peers状态、目标端表结构AgeOfLastShippedOp持续增大网络不通、目标端写入慢、源端WAL被清理逐段检查连通性、看目标端写指标、调整WAL保留时间Peer显示DISABLED目标ZK不可达或chroot路径配置错误核对CLUSTER_KEY测试ZK通信目标端丢列或写入异常源表和目标表结构不一致对比表结构重建目标表大数据量重复Increment/Append类操作导致检查业务是否用了非幂等写操作BulkLoad的数据没同步BulkLoad不写WAL不走Replication额外在目标端导入HFile或改造写入链路5.2 一次排障的思路复盘有个同事负责的表同步突然停了现象是源端status replication显示某个RegionServer的复制队列在增长但目标端没有任何新数据进来。他第一反应是去看网络能通。再看目标端RegisonServer日志没有写入报错。后来我让他去看ZooKeeper里复制队列的节点归属结果发现源端有个RegionServer进程假死但HMaster没把它摘掉它的WAL保留给了自己没有其他RegionServer接管。处理办法其实不复杂重启这个RegionServerZooKeeper队列会重新分配其他节点接手它的WAL继续推送数据自然追平。关键是排障顺序先确认队列归属再判断是否有节点hang住不要一上来就改配置那样大概率越改越乱。这类问题有一个通用排查路径先看Peer状态再看ZooKeeper队列再看源端RegionServer日志里的ReplicationSource线程状态最后看目标端ReplicationSink日志。按照这个顺序走大部分问题都能定位到具体节点。5.3 我的避坑心得维护这套复制方案这么久我总结了几条刻进骨子里的经验。第一新表上线流程里必须加复制开关校验。HBase新表默认REPLICATION_SCOPE0如果建表工具没自动设置这表就永远不会同步。等哪天容灾演练发现备集群少了一张表再排查的代价就大了。我在团队里规定所有关键业务表建表脚本必须带复制属性Code Review阶段就要确认。第二BulkLoad要单独处理。HBase的BulkLoad是直接生成HFile并挪到表目录下全程不写WAL所以Replication对它无效。如果业务大量使用BulkLoad导入数据同步链路要设计成导入动作同时在目标端执行一次或者把BulkLoad的文件在目标端reload一遍。第三定期做数据校验。Replication不是最终一致性问题的万能解药我每两周会用抽样对比的方式做一次两个集群的数据一致性检查主要针对最近新增数据的rowkey集合重点看两端最新版本的时间戳是否一致。这个动作看起来费时但能提前发现很多隐性问题。第四别乱改集群ID和下划线开头的内部配置。复制链路对底层元数据非常敏感有些配置平时看着没用改完可能触发不可预期的行为。核心集群只变化自己完全理解的东西剩下的保持默认。最后再分享一个小技巧给Peer命名的时候用有意义的编号比如按业务线区分不要用纯数字123。Peers多了以后排查问题时靠名称能省很多时间。我在集群里就是rep_online、rep_report这样命名一眼就知道这条链路是给谁用的。跨集群复制本身不难难的是把链路维护得稳定、可查、可切换。把这套东西沉淀下来以后无论做迁移、容灾还是多活都能少踩很多坑。