拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HBase跨集群数据复制:WAL日志同步原理、配置与实战指南

HBase跨集群数据复制:WAL日志同步原理、配置与实战指南

做大数据运维和开发的朋友,迟早都会碰到一个绕不开的需求:把 HBase 的数据从一套集群复制到另一套集群。我最早接触跨集群数据复制,是因为公司要把两个机房的 HBase 集群打通做容灾,后来又在读写分离、版本升级、数据汇聚这些场景里反反复复用同一套机制。HBase 自带的 Replication 方案,说白了就是基于 WAL 日志的异地重放,能实现表级别、异步优先的跨集群数据同步。这篇我把从原理、配置、实操到排障的完整过程都梳理一遍,内容适用于 HBase 1.4、2.x 和 3.x 的主流版本,也适合正在看 HBase 面试题、准备大数据集群部署方案的朋友当一份参考手册。

1. 为什么业务跑得好好的,我却要折腾跨集群复制

先说说场景。很多人觉得 HBase 集群只要部署好了、数据写进去了、查询也稳定,就万事大吉。但实际上,单集群在真实生产环境里非常脆弱。机房断电、交换机割接、磁盘阵列损坏、甚至一次误操作删表删 namespace,都会让"所有鸡蛋放在一个篮子里"的代价变得极其惨痛。跨集群复制解决的不是性能问题,而是数据可达性和业务连续性的问题。

我归纳了一下,实际工作中需要启用 HBase 跨集群复制的场景大概有这五类:

  • 容灾备份:主集群在 A 机房,备集群在 B 机房。主集群挂了,业务切换到备集群,数据不丢。这是最常见的诉求。
  • 读写分离:主集群承接写入流量,备集群承接分析查询、报表任务、离线计算等读取密集型业务,避免读流量拖垮主集群的写入延迟。
  • 地理就近访问:公司业务覆盖多个区域,用户集中在不同地域,通过复制把数据分发到离用户近的机房,降低访问时延。
  • 数据汇聚:多个边缘机房或业务线各自有 HBase 集群,需要把数据统一汇总到总部数据中心做分析。
  • 集群迁移与升级:HBase 大版本升级(比如 1.x 迁 2.x)、集群整体搬迁、rebalance 数据分布,这些场景不能停机,只能靠复制做增量同步,配合 Snapshot 做全量初始化。

重要提示:HBase 自带的跨集群复制是表级别的异步复制,不是 HDFS 层面的块复制,也不是整个集群的镜像同步。你在 A 集群开启复制某张表,A 集群写入的 WAL 日志会被异步转发到 B 集群,B 集群的 RegionServer 负责把日志里的修改重放到对应的表上。这个机制只保证"最终一致",不保证严格实时。

所以,在动手配置之前,先想清楚你的业务能否接受"异步复制下,备集群数据可能有几十秒甚至分钟的延迟"。如果业务要求强一致双写,那你需要的不是复制,而是双活网关或者业务层同步写,那种方案成本和复杂度都高一个数量级。

2. 复制到底怎么“跑”起来的:HBase Replication 实现机制

跨集群复制看着黑盒,其实核心就一句话:把主集群的 WAL 日志当作数据源,异步搬运到备集群,再在备集群上按日志重放。

HBase 的每个 RegionServer 在写入数据时,会先写 WAL(Write-Ahead Log),再写 MemStore,最后刷写成 HFile。WAL 里记录的是每次修改操作的物理日志,里面包含 rowkey、列族、单元格、时间戳、操作类型(Put/Delete)等信息。Replication 机制就是盯着这份 WAL,把里面的改动一条条复制到目标集群。

具体流程是这样的:

  1. 客户端向主集群 RegionServer 发起 Put/Delete 请求。
  2. RegionServer 先把修改写入 WAL,确认日志持久化。
  3. 后台有一个线程叫 ReplicationSource,它从 WAL 中读取新的 HLog 条目。
  4. 读出来的条目经过打包、序列化,通过 RPC 推送到备集群对应 RegionServer 的 ReplicationSink。
  5. 备集群的 ReplicationSink 接到数据后,调用本地的mutateRowsWAL接口,把修改重放到目标表上。

整个链路里,有几个关键坐标需要理解:

  • ClusterKey:标识目标集群的字符串,格式是目标ZK地址:客户端端口:znode父路径,例如192.168.10.20:2181:/hbase。
  • Peer:一个复制通道,每条 peer 有一个 ID(通常是数字或字符串),配置好 ClusterKey 之后就建立了主备集群之间的连接。
  • ReplicationSource:运行在主集群 RS 上的线程,按 RegionServer 维度去扫 WAL、发数据。
  • ReplicationSink:运行在备集群 RS 上的接收端,负责把收到的 WAL 条目应用到本地表。
  • ZooKeeper:主备集群各自的 ZK 记录了 peer 状态、复制队列位置等元数据。复制进度的断点续传能力,就靠 ZK 里保存的 WAL 位置信息。

有一件事经常被误解:复制不是"双写",也不是"客户端同时往两个集群各写一份"。两个集群的数据一致性完全依赖 WAL 日志的完整转发。所以主集群一旦在 WAL 刷盘前宕机,或者 WAL 被误删,那条日志对应的数据就无法复制过去,备集群就会少数据。这也是为什么我后面会专门讲 WAL 保护和复制队列监控。

另外一个重要机制是复制队列(Replication Queue)。每个主集群 RS 在处理 WAL 时,会维护一个复制队列,记录已发送到哪个文件、哪个偏移量。如果备集群挂了或者网络不通,主集群不会丢数据,而是把日志位置记录保留在 ZK 中,等网络恢复后继续从断点发送。这个设计保证了复制不会因为瞬时的网络抖动就永久中断。

HBase 2.x 之后,Replication 机制引入了 barrier 的概念,用来处理复制过程中表的 schema 变更。比如你刚把一张表的列族加了 TTL,如果日志先到备集群、schema 变更还没到,重放就可能报错。这个细节我在后面的"坑"里细说。

3. 动手前的集群规划与配置项梳理

先别急着敲命令。复制方案最怕的就是"配置一时爽,排障两行泪"。我按经验把要做的前置规划列一下,每一项都踩过坑。

3.1 版本与兼容性匹配

HBase 的复制要求主备集群的版本尽量一致,我强烈建议主备大版本保持一致,不要跨大版本做复制,比如 1.4 复制到 2.5,这在生产上会有一堆兼容性问题,包括 RPC 协议、WALEdit 序列化格式、ZK 中复制队列的结构都变了。

实际可行的组合:

主集群版本备集群版本是否建议
1.4.x1.4.x建议
2.3.x2.3.x建议
2.5.x2.5.x最稳
2.x(较老)2.x(较新)需实测
1.4.x2.x不建议

另外,主备集群的 HBase 表结构(列族、压缩方式、TTL、分区数)要尽量一致,尤其是表的预分区策略。虽然复制会自动在备集群创建不存在的表(默认开启hbase.replication后会自动建表),但预分区不一致会导致备集群热点,以及 Region 分裂日志的冲突。

3.2 网络与端口

跨机房复制依赖 RPC,主备 RegionServer 之间需要能互通以下端口:

  • 16020:RegionServer 的 RPC 端口(HBase 2.x 默认;1.x 是 60020)
  • 16000:Master RPC 端口(部分场景也要通)
  • 2181:备集群 ZooKeeper 客户端端口(主集群 RS 解析 ClusterKey 时需要访问)

注意:复制的数据流不是走 ZK 传输的,ZK 只是用来存元数据。所以别让 ZK 端口成为复制带宽的瓶颈,但 ZK 一旦不通,复制会立刻停止并开始重试。

3.3 核心配置项

主备集群的hbase-site.xml都要开启复制开关:

<property> <name>hbase.replication</name> <value>true</value> </property>

然后根据实际资源调整这些参数:

<!-- 备集群用于处理复制的 RPC handler 数量,默认 0 表示不启用复制专用 handler --> <property> <name>hbase.regionserver.replication.handler.count</name> <value>32</value> </property> <!-- 主集群复制发送线程的批处理大小(批量大小按字节数) --> <property> <name>replication.source.size.capacity</name> <value>1048576</value> </property> <!-- 主集群复制发送线程最多拉取多少条日志记录 --> <property> <name>replication.source.nb.capacity</name> <value>10000</value> </property>

我个人的经验是,hbase.regionserver.replication.handler.count是备集群最容易成为瓶颈的参数,默认值在某些版本里是 0,等于没开复制专用 handler。如果你发现备集群 RegionServer 的 RPC 队列堆积,优先调大这个值。一般一张表写入量不大时 16 就够,写入量大的在线业务建议 32 到 64。

还有两个容易被忽略的:

  • hbase.replication.source.maxretrieslimit:主集群发送失败最大重试次数,默认 1。调大可以增加韧性,但重试太多会让复制延迟飙升。
  • hbase.replication.sink.ignore.edits:备集群是否忽略某些不可执行的日志,默认 false。别乱开,开了容易丢数据。

3.4 表设计层面的约束

复制是"日志重放",所以在备集群上重放时,完全遵循主集群的时间戳和操作类型。这就带来一个双端写入的隐患:如果两个集群都允许对同一张表写入相同 rowkey 的数据,那么它们的 WAL 会互相复制、互相覆盖。最终结果取决于谁的时间戳更大(last-write-wins),而不是谁先写。

生产环境里,除非你用了高可用双活方案,否则我建议复制场景下备用集群只读,不要让业务直接往备集群的表里写数据。如果必须双写,考虑标识字段隔离或者时间戳策略,但代价都很高。后面第 6 节我会讲双活的方案。

4. 从零建立 Peer:完整实操流程

规划做好,下面开始正式配置。我以一个实际例子演示:主集群是hb-master-cluster,备集群是hb-dr-cluster,复制表名为user_behavior。

4.1 第一步:主备集群同时开启复制功能

修改两套集群的hbase-site.xml,加入:

<property> <name>hbase.replication</name> <value>true</value> </property>

改完滚动重启 HBase。注意,主备都要开,因为复制是双向通道机制,你要考虑未来可能的反向复制。

4.2 第二步:获取备集群的 ClusterKey

在备集群的主节点上,通过 hbase shell 执行:

hbase:001:0> list_peer_cluster_keys

或者直接看备集群的hbase-site.xml里三要素:ZK 地址、客户端端口、znode 父路径。假设备集群的 znode 是默认/hbase,那么 ClusterKey 就是:

192.168.30.101:2181,192.168.30.102:2181,192.168.30.103:2181:/hbase

注意端口不要写成 2888/3888(ZK 集群内部通信端口),一定要写客户端端口 2181。

4.3 第三步:主集群上添加 Peer

在主集群的 hbase shell 里执行:

add_peer '1', '192.168.30.101:2181,192.168.30.102:2181,192.168.30.103:2181:/hbase'

这里的'1'是 peer 的 ID,任意字符串都可以,但建议用有含义的命名,比如'hbase-dr'。添加之后,可以用:

list_peer '$STATE'

查看 peer 的连接状态。正常情况下能看到ENABLED和CONNECTED状态。如果一直是DISABLED,说明备集群 ZK 解析有问题,网络不通或者地址写错。

4.4 第四步:为表启用复制

默认情况下,新添加的 peer 不会自动复制所有表,必须手动启用。在主集群执行:

enable_table_replication 'user_behavior'

执行后,HBase 会扫描该表的 WAL 历史,从启用的时间点开始复制。注意这个命令只对已存在的表生效。如果后续新表也需要复制,得再执行一次。

如果要批量启用多个表,可以写个循环脚本调用 HBase Shell,或者直接改表描述:

alter 'user_behavior', {NAME => 'info', REPLICATION_SCOPE => '1'}

这里的REPLICATION_SCOPE => '1'是列族级别的复制开关。enable_table_replication的本质就是把所有列族的REPLICATION_SCOPE设为 1。所以你也可以细粒度到列族级别来控制复制范围。

4.5 第五步:在备集群确认数据

先在备集群确认表是否自动创建:

list 'user_behavior'

如果没有自动创建,手工建表即可,但注意预分区必须和主集群一致,否则大量 Region 集中在几个 RS 上,备集群读写会热点。

然后往主集群写一条测试数据:

put 'user_behavior', 'rowkey_001', 'info:uid', '10086' put 'user_behavior', 'rowkey_001', 'info:action', 'click'

等待几秒到几十秒,在备集群查:

get 'user_behavior', 'rowkey_001'

能查到就说明复制通了。如果查不到,先看复制状态和日志,排查方法我放下一节详细讲。

4.6 第六步:验证复制延迟

复制延迟是最常见的监控指标。通过 hbase shell:

status 'replication'

这个命令会输出每个 peer 的复制队列长度、发送吞吐、延迟时间等信息。队列长度持续上涨,说明消费速度跟不上生产速度;延迟时间持续高位,说明网络带宽或者备集群处理能力有问题。

如果你有监控系统,建议把以下指标接进去:

  • hbase.regionserver.replication.source.avgReplicationLag
  • hbase.regionserver.replication.source.sizeOfLogQueue
  • hbase.regionserver.replication.sink.replicationSinkLingerMs

这套配置跑通之后,日常运维主要是盯着队列长度和延迟。复制本身不会因为网络抖动立刻丢数据,但会堆积日志位置。

5. 复制过程中的坑:一次真实故障的排查全记录

理论讲完,配置也给了,下面分享一次我实际遇到并排查了几个小时的生产故障,确保你读到这篇文章时,能少走我走过的弯路。这个问题非常典型,几乎每个做 HBase 复制的人都会碰到。

5.1 故障现象

某天早上收到监控告警:主集群到备集群的复制延迟突破 30 分钟,复制队列一直积压。刚开始我以为只是网络波动,但过了两个小时,延迟不仅没降,队列长度反而涨了 300%。更诡异的是,其他几张表的复制都正常,只有user_behavior表出问题。

5.2 排查链路

我先在主集群上看了复制状态:

status 'replication'

输出显示 peer 是CONNECTED,没有断连,但 state 里有大量报错的 WAL 文件,错误信息类似:

org.apache.hadoop.hbase.replication.ReplicationException: Can't replicate because of a schema change or missing table

看到这句,我的第一反应是 schema 出了问题。于是赶紧对比主备两端的表结构:

describe 'user_behavior'

结果发现主集群的表有一个列族info设置了TTL => 86400(24小时过期),而备集群的表因为当初手工建表时漏掉了这个 TTL 配置,是永不过期的。正常情况下这也不至于让复制卡死,但问题是复制重放到备集群时,HBase 会校验日志里的 TTL 信息与备集群表定义是否匹配,一旦发现 schema 不一致,就认为这批日志无法安全重放,于是停在那个 WAL 文件上反复重试。

这就是我在第 2 节提到的 barrier 和 schema 变更检测机制。HBase 2.x 复制时会对比日志里的列族信息和目标集群表的列族信息,如果发现列族数量、名称、TTL 等关键属性不一致,会主动阻塞该表的复制,避免物理数据写入后造成逻辑不一致。

5.3 修复过程

找到根因后,修复步骤就简单了。在备集群执行:

disable 'user_behavior' alter 'user_behavior', {NAME => 'info', TTL => '86400'} enable 'user_behavior'

把备集群表的 TTL 改得和主集群完全一致。然后强制刷新一下主集群的复制状态。注意,如果积压的 WAL 文件已经因为重试次数过多被标记为skipped,需要重启主集群对应 RegionServer 来清理复制队列的异常状态。

重启 RS 之后,复制队列从 300% 慢慢降了下来,延迟也恢复到了秒级。这种问题如果不查 schema,光靠重启、调网络、扩容备集群,永远解决不了。

5.4 这个坑教给我的三条经验

第一,备集群表必须和主集群表结构完全一致。不只是列族和 TTL,压缩算法、版本数、列族 BloomFilter 类型等关键属性都要一致。最稳妥的办法是直接用主集群的describe输出在备集群照抄建表,不要手工精简。

第二,复制积压先查 schema,再查网络,最后查资源。我在后续维护中养成了习惯:凡是复制延迟异常,第一件事就是主备两端的describe对比。脚本对比最好做成自动化巡检,每天跑一次。

第三,改了表结构后,复制不会自动恢复。你alter完备集群表之后,主集群那条 peer 仍然认为是"不可重放"状态,最干净利落的做法是remove_peer再add_peer重建复制关系。如果担心重建 peer 期间丢日志,可以先把该表的复制队列清零后重建。

5.5 另一个高频坑:误删备集群表后自动建表失败

还有一个坑也值得一提。如果你手滑在备集群disable了一张正在复制的表,复制源收到错误后会把对应的 WAL 文件标记为失败;如果不处理,积压会越来越大。此时要做的不是简单enable表,而是检查主集群 RS 日志里有没有ReplicationSink的异常,再决定是否重建 peer。

还有人在备集群删掉了表,期望复制自动重新建表。虽然 HBase 2.3 之后有自动建表逻辑,但前提是 peer 创建时开启了TABLE_CFS配置。默认情况下,备集群表被删之后,复制源会疯狂重试但始终失败。合理做法是:主集群暂停复制该表,备集群重新建表成功后,再启用复制。

6. 进阶玩法:双活、同步复制与大数据量初始化

基础的跨集群复制配置完成后,很多团队会继续探索更高级的用法。我这里挑三个最实用的讲。

6.1 双向复制与双活

HBase 的 replication 本身支持两个集群互相成为对方的备集群,也就是集群 A 复制表 T 到集群 B,集群 B 同一张表 T 也复制到集群 A。这样就能实现双活。

但正如我前面警告过的,双写同一 rowkey 会因为 last-write-wins 导致数据相互覆盖,严重的话会形成无限循环复制。HBase 2.1 之后引入了Serial Replication和 replication 队列中的replication标记,用来处理循环复制问题。

官方推荐的方案是:用HBase Replication + 应用层时间戳/地域标识实现接近双活的效果。具体做法:

  • 两个集群各负责一部分业务流量,rowkey 带机房前缀,从根本上避免同一 rowkey 双写。
  • 复制时不做任何改写,依靠 rowkey 天然隔离。

这其实不算真正的双活,但生产环境里最可控的就是这类"逻辑双活"。真正的强一致双活,HBase 自身做不到,需要引入第三方网关。

6.2 Synchronous Replication(同步复制)

如果你业务对数据一致性的要求很高(不是最终一致,而是备集群一定要在确认后才返回成功),HBase 2.1 开始支持 synchronous replication。开启方式:

add_peer '1', CLUSTER_KEY, SYNCHRONOUS

同步复制的代价是:主集群写入时,需要等待备集群确认后才会向客户端返回成功。这会让主集群写入 RT 大幅上涨,跨机房时尤其明显。一般来说,只有金融、订单这类强一致业务才值得用同步复制;日志、行为分析类数据,异步复制完全够用。

这里我多说一句,很多人以为同步复制就是"两阶段提交",其实不是。HBase 的同步复制仍然是 WAL 级别的复制,只是把复制从异步变成了等待 ACK。备集群没有返回确认,主集群不会提交该事务。但两个机房之间一旦发生网络分区,同步复制会把可用性打到最低,所以生产环境要慎用。

6.3 大数据量初始迁移:SnapShot + Replication 追赶

现有集群已经有 10TB 甚至更多历史数据,想搭建备集群,不可能靠复制把历史数据一条条传过去。标准做法是分两步:

  1. 做主集群表的Snapshot,并导出到备集群的 HDFS,利用ExportSnapshot工具完成全量数据拷贝。
  2. 恢复表到备集群后,开启复制,让复制只追赶全量拷贝期间的增量日志。
# 在主集群生成快照 hbase snapshot create -n user_behavior_snap -s user_behavior # 把快照导出到备集群(走 HDFS 拷贝,带宽可控) hbase org.apache.hadoop.hbase.snapshot.ExportSnapshot -snapshot user_behavior_snap -copy-to hdfs://dr-cluster-nameservice/data/hbase -mappers 20

然后在备集群用clone_snapshot恢复表:

clone_snapshot 'user_behavior_snap', 'user_behavior'

恢复完成后,立刻在主集群enable_table_replication 'user_behavior'。这样复制只会补上快照之后的新数据,不会全量扫历史。这个方案比CopyTable快得多,也是生产环境大数据量搬迁的最优解。

要注意,ExportSnapshot 的带宽和 MR 的 map 数量直接相关,建议根据机房带宽调整-mappers参数,避免把业务带宽打满。

写在最后的运维习惯

跨集群复制最大的风险,不在于配置有多复杂,而在于它太容易被忽略。复制是后台异步跑的,主业务不感知,线上用户也不感知,它坏掉的时候业务看起来还是正常的。我见过不止一次,复制断了几个月才发现,业务侧毫无异常,备集群早就成了数据黑洞。

所以,我的个人体会是:复制配置完成不算完,真正要长期维护的是复制状态的可观测性。每天看status 'replication'的延迟指标和队列深度,出了异常第一时间对比主备两端的表结构,这两件事做好,复制方案才算是真正稳了。

返回列表