资讯详情

资讯详情

HBase跨集群复制从原理到落地:WAL、Peer与容灾实战

搞大数据的同学迟早会撞上这么个需求业务要做多机房容灾了线上集群的数据要汇到离线集群做分析了老集群要整体换硬件了。你打开搜索引擎跳出来的基本都是官方文档碎片看着不难真上手就会发现一堆细节。这篇文章就把 HBase 跨集群数据复制从原理到落地从头串一遍。刚接触 HBase 的可以把它当成一篇能读懂的入门实战笔记正在做架构改造、要自己搭复制链路的人可以直接拿里面的命令、参数和排查思路去对方案。我会尽量把“为什么这么做”讲透而不是扔一堆命令让你抄完就完事。1. 跨集群复制到底是干嘛的别和普通备份搞混跨集群复制在 HBase 里对应的官方术语是 Replication很多时候也被叫“集群间数据同步”。它解决的问题其实很单一让一个集群里的写入自动、持续地出现在另一个集群中。但就是这个看似简单的需求细分下来有四种完全不同的业务场景每种场景对延迟、一致性和容错的要求都不一样。如果在设计阶段没想清楚自己属于哪一类后面所有参数调优都会很拧巴。1.1 四种最常见的业务场景第一种是容灾。主集群在 A 机房备集群在 B 机房主挂了以后业务切到备。这是最经典的“冷备转热备”诉求。这里对复制的要求是数据延迟尽量小最好在秒级到分钟级备集群的 RegionServer 即使平时不接流量也时刻准备好承接请求。要注意的是这里的“备”不是备份而是另一套处在运行状态的 HBase 集群。第二种是数据汇聚或读分离。比如线上业务实时写入源集群分析部门不希望自己的大批量 Scan 任务把线上 RegionServer 压垮所以让源集群实时复制一份数据到分析集群。这种场景下复制出来的集群可以有不同的表结构、不同的预分区策略也可以适当降低副本因子反正最终只是供离线任务读取。第三种是集群迁移。老集群版本太旧、机器太破或者要换机房最稳妥的方式就是开一条复制链路让新集群边接数据边追平最后择机切换业务流量。这个方法比停机导出再用 Import 灌数据要平滑得多也是最推荐的迁移姿势之一。这里对复制的要求是追平速度快存量迁移能并行进行切换窗口越小越好。第四种是双活或者多活。A 和 B 同时给业务读写两边数据互相同步。说句实在话HBase 的异步复制并不是做双活的银弹尤其是“双向复制”对冲突处理和环路问题要求极高很多团队最后都退化成单向复制加应用层双写。如果对一致性有硬性要求建议一开始就把这个方案排除掉。1.2 为什么不用定时 ETL 和导出导入代替很多人一听到跨集群同步第一反应是写个定时任务从源集群 Scan 增量数据再写进目标集群。真这么干过的人基本都被折腾过Scan 会消耗源集群大量资源Range 边界变化后增量位点难以维护大批量 Put 会让目标集群 MemStore 频繁 flush垃圾回收和 Region Split 也跟着凑热闹。HBase 官方的 Replication 走的不是应用层读写而是直接读每个 RegionServer 写下的 WAL 日志。业务每次 Put 或 Delete 在写 MemStore 时都同步落了一条 WAL 记录复制线程从这条日志里把编辑拆出来打包发给远端集群再由远端集群的 RegionServer 应用到内存和 WAL。整个流程对业务线程的干预极小也不依赖你写任何业务代码。所以说到底复制和备份是两个层面的事。备份是“给你一个能恢复的时间点”复制是“让两个集群持续保持接近一致的状态”。定时的导出导入只解决“一次性”问题复制解决的是“一直变化”的问题。如果你需要的只是某个历史时间点的数据那就老老实实做快照和备份开复制反而是给自己找麻烦。2. 复制原理拆解从 WAL 到远端的完整链路跨集群复制的原理其实可以总结成三兄弟协作源集群 RegionServer 里的 WAL、ZooKeeper 里的复制队列、目标集群的 RegionServer。三者各干各的但配合不好就会出现“队列堆积、磁盘告警、复制延迟飙升”这些常见事故。理解这条链路后面的调优和故障排查才有依据。2.1 一条写入到底是怎么“流”到远端集群的先看源集群这边。业务每写一条数据实际上是先写 WAL再更新内存里的 MemStore。WAL 文件本质是 HDFS 上的 SequenceFile里面是一长串有先后顺序的编辑记录。复制功能开启后只要这张表的列族被标记为 global scopeRegionServer 上的 ReplicationSource 线程就会实时打开当前 WAL 文件从尾部继续读相当于边写边同步。读出来的编辑不会一条条往远端发而是攒成一个批次尽量按行键和列族分组通过网络传给目标集群的对应 RegionServer。目标集群接到这批编辑后会先解包、按目标表的 Region 重新路由再写入自己的 WAL 和 MemStore。因为写入请求本身就具备幂等性质所以重复收到同一条 Put 或 Delete 并不会导致灾难性错误只是会被再次执行一遍。这中间 ZooKeeper 到底扮演什么角色它维护的是每个源 RegionServer 到每个 Peer 的“复制进度队列”队列里记录的是当前正在复制的 WAL 文件名以及这个文件已经读到的偏移量。只要一条编辑确认被远端成功接收并处理对应偏移量才会往前走。假如源 RegionServer 挂了它的复制队列不会丢其他存活的 RegionServer 会通过 ZooKeeper 感知并接管剩余 WAL继续复制。这也是为什么 HBase 复制能撑住 RegionServer 故障的关键原因。2.2 不是每张表都会自动复制关键在 scope很多第一次上手的人都会踩同一个坑我明明 add_peer 了为什么新写入的另一套集群里啥也没有大概率是表列族的复制范围没开。HBase 表在创建时列族的 REPLICATION_SCOPE 默认为 0也就是只在本集群生效只有把 scope 改成 1编辑才会进入全局复制通道。这个设计初看有点多余细想非常有道理。假设一个集群里有几十张表其中只有两张表需要跨集群那其他表的 WAL 根本没必要被复制线程扫描能省下大量 CPU 和网络开销。在 HBase Shell 里可以通过这样开启alter ods:user, {NAME info, REPLICATION_SCOPE 1}或者在建表时直接指定create ods:user, {NAME info, REPLICATION_SCOPE 1}如果想偷懒直接用enable_table_replication ods:user也能把表里所有列族一次性打开。不过在生产环境我更推荐手动控制因为有些敏感列族其实并不需要出集群开着等于少了一道过滤。2.3 关键参数怎么调先看机制再动按钮跨集群复制不是“配置完就能自动跑满带宽”的组件它有几个参数直接决定批次大小和线程数量。我列一张表大家照着语义去 hbase-site.xml 里找对应配置项即可不同版本的参数名可能稍有差异但机制不变。参数作用常用配置方向建议参与复制的源 RegionServer 比例hbase.replication.source.ratio默认 1.0表示每个 RS 都开启复制线程RS 很多时可以考虑降低单个批次最大数据量hbase.replication.source.size控制批次字节数避免大 Value 撑爆内存单个批次最多操作数hbase.replication.source.nb.capacity控制批次条数和上面那个参数共同生效目标端接收端并发target 集群 RegionServer 数量目标 Region 分布越均匀复制吞吐越高调参时改一个参数解决所有问题是不现实的很多时候瓶颈根本不在源端参数而在目标集群有没有足够的空闲 RegionServer 去处理这些写入。如果你发现复制速率一直上不去别急着一口气把批次参数拉满先看看目标集群的写路径压力和网络带宽是不是已经打满。我是见过有人把批次调得特别大结果源端 RegionServer 频繁 Full GC只是把问题从目标端搬到了源端。2.4 顺序性、串行复制和异步复制之间的取舍HBase 复制天然强调顺序同一个 WAL 里的编辑会按顺序发送和处理这样才能保证同一行的多次更新在目标端按时间顺序落地。但“全局串行”对吞吐是非常大的限制因为一个 RegionServer 里的所有表、所有列族都串在一个复制流后面前面慢一点后面全卡住。HBase 2.x 开始支持 Serial Replication它允许复制的编辑按列族拆分到不同队列同一列族内部保持顺序不同列族之间可以并行。这个能力对“同一个 CF 内强一致、不同 CF 允许乱序”的业务来说吞吐提升非常可观。如果你的 HBase 版本不支持只能接受全串行模式那就更需要在源端把待复制表的 Region 分布控制好避免单个 RegionServer 撑着全集群的复制流量。我这里再强调一句复制的本质是异步的RPO 不可能是零。如果业务对数据一致性要求极高比如金融系统双活官方复制方案只能作为其中一个环节不能作为唯一保障。你始终要有一个“追平检查”和“切换演练”机制而不是假设复制链路永远健康。3. 一份可以照着做的落地实施流程原理聊清楚了接下来就是实操。我这套流程不是从官方手册抄的而是结合多次迁移、容灾演练总结出来的“安全路线”先补存量、再开增量、最后验证追踪。如果你跳过其中某一步比如直接 add_peer 然后指望历史数据也自动过去大概率会在第二天发现目标集群少了一大半数据。3.1 动手前先检查这五件事第一件事是版本兼容。HBase 跨版本复制并不是所有组合都能跑主版本不一致很容易出现序列化不兼容或 RPC 协议对不上的问题。稳妥的做法是源集群和目标集群大版本保持一致小版本尽量别差太远。如果确实存在版本差异先找官方兼容性说明别拿生产环境试错。第二件事是网络和端口。复制链路用的是 RegionServer 的 RPC 端口默认 16020。源和目标之间的安全组、防火墙要放通而且要确认不是“单向 ping 通就行”是源端所有 RegionServer 到目标端所有 RegionServer 都能连通。很多问题看着像复制超时其实就是一个跨机房防火墙规则把部分源节点挡住了。第三件事是目标集群的表结构。复制不会帮你自动建表也不会帮你自动补列族。源表叫 ods:user有 info、tag 两个列族目标集群就必须有一个 ods:user且 info、tag 都存在。列族参数不一致一般不影响数据落地但 Region 分布、压缩算法、TTL 不一致会导致目标集群性能差异极大。第四件事是 Kerberos 权限。如果两边都开了认证复制使用的用户必须在目标端有“写”权限否则大量编辑会在远端被拒绝错误日志还会被淹没在“AccessDeniedException”里。我建议提前建一个专用复制账号权限最小化别拿业务超级管理员账号去配复制链路。第五件事是存量数据规模。复制只是增量通道它解决不了“源集群里已经躺着 10TB 历史数据”这个事实。在启用复制前生产上一定要先做一次快照导出把存量数据搬运到目标集群。这一步是很多人最容易忽略的。3.2 存量同步实操快照导出而不是直接复制存量数据同步我推荐用 HBase 自带的快照工具。它比 DistCp 拷 HFile 更安全因为快照不会阻塞在线写入而且导出的 HFile 可以直接被目标集群加载。操作分三步。第一步在源集群创建快照hbase snapshot create -n initial_sync -t ods:user注意快照名不能和已有快照重复。表特别大的时候这个命令会执行一会儿但不会锁表业务照常读写。第二步使用 ExportSnapshot 把快照推到目标集群的 HDFShbase org.apache.hadoop.hbase.snapshot.ExportSnapshot \ -snapshot initial_sync \ -copy-to hdfs://target-namenode:8020/hbase \ -mappers 16-mappers控制并发度可以根据网络带宽适当调大。这里假设两边 HDFS 是物理隔离的需要通过 DistCp 类型的方式传文件。如果两个集群共享同一套 HDFS这一步可以变成直接在目标集群 clone 快照速度会快很多。第三步在目标集群通过恢复快照的方式拿到数据hbase shell restore_snapshot initial_sync恢复完成后先用count或者Scan抽查几个关键表的行数确保存量数据已经完整到达。数据量特别大时用完全精确的行数比对可能太慢我通常的土办法是取最近一段时间的几个关键行键对比两边数据是否一致再用离线任务统计全表数量随后再做第二轮核对。3.3 增量复制配置Peer 和表级开启存量同步和增量启用的先后顺序我建议是“先把快照恢复跑完再开复制”。因为如果先开复制源端历史上残留在旧 WAL 里的编辑也有可能被扫描出来和你正在导入的存量数据叠在一起虽然幂等但会让目标集群多承受大量不必要的写放大。添加复制 Peer 的命令很简单hbase shell add_peer peer_dr, zk1,zk2,zk3:2181:/hbase这里的zk1,zk2,zk3:2181:/hbase是目标集群的连接串由目标集群的 ZooKeeper 节点列表和 chroot 组成。千万注意这个 ZooKeeper 必须属于目标集群不能顺手写成源集群自己的 ZooKeeper否则 Peer 会连回自己导致环路。如果只想复制某些表的某些列族可以用带映射的写法add_peer peer_dr, zk1,zk2,zk3:2181:/hbase, TABLE_CFS { ods:user [info, tag], ods:order [record] }添加完 Peer 后再把对应表的复制范围打开。使用enable_table_replication最省事enable_table_replication ods:user也可以指定列族alter ods:user, {NAME info, REPLICATION_SCOPE 1}有些文章会建议在加 Peer 之前就改表配置其实关系不大关键是队列建立后 WAL 里新写入的编辑才会被标记为待复制。修改完表配置后如果有需要可以通过滚动重启 RegionServer 来确保所有 RegionServer 都加载到新的配置但在线alter一般也能生效具体看你的版本。3.4 复制状态怎么确认别靠用人眼扫数据配置完成后进入 HBase Shell 执行status replication这个命令会显示 Peer 列表和当前复制源的作用状态。更细一点可以拆开看status replication, source status replication, sink源端会显示每个 RegionServer 的复制队列长度、最后传输时间目标端会显示收到了多少批次。如果你启用了 Hadoop 的监控面板可以再盯一下 JMX 里的ReplicationSource指标重点看ageOfLastShippedOp。这个数字代表上一次成功传输距离当前有多远持续快速增长就是复制卡住的信号。最后做一次端到端的验证从源集群 put 一条新数据十秒之内去目标集群 scan 这条记录。如果目标是读写分离场景这一步基本就能证明链路通了。之后不要忘了把验证脚本留着以后每次配置新 Peer 或者变更表结构都能用同一套回归方法快速判断风险。4. 实施中的典型坑和排查实录复制链路这东西平时不显山不露水一旦出了问题就是“源端磁盘被 WAL 堆满”这种级别的事故。我在这部分把几个高频问题按“症状、可能原因、处理办法”列出来大家可以直接对照排查。4.1 存量同步完了目标集群却收不到任何增量这是最常见的起步问题。快照恢复了Peer 也 add 了表也 enable 了新写入的作业始终不出现。第一优先怀疑对象是 CF 的 REPLICATION_SCOPE 没有真正生效尤其当表是通过旧代码建出来、列族参数没改的时候。你可以用describe ods:user看一眼如果列族里没有REPLICATION_SCOPE或值为 0那数据就不可能进复制通道。处理方式是执行一次alter把列族 scope 改为 1或者直接enable_table_replication。改完之后再观察源端 WAL 是否在滚动后进入队列。有些版本对存量 WAL 的处理并不积极如果修改配置前那个 WAL 已经被复制线程跳过了新数据就只能等下一个 WAL 文件生成。等不及的话可以用flush ods:user强制滚动 WAL但线下测试时别随便在生产这么干容易引发瞬时性能抖动。4.2 目标集群宕了源端磁盘突然暴涨复制是异步队列机制目标集群连不上源端不会丢数据但会不停积压 WAL 文件。因为复制线程没法把日志偏移量推进HMaster 和 RegionServer 就会认为这些 WAL 还没被“消费完”不能清理。时间一长源端 HDFS 的磁盘空间会被复制日志占满。这个坑其实是机制问题不是 bug。处理办法分两级。短期应急是找到积压最严重的 RegionServer看它的复制队列长度然后评估目标集群恢复时间。如果目标集群几个小时内能恢复就扛一扛如果确定要挂很久先执行disable_peer peer_dr注意是disable_peer不是remove_peer。remove_peer会删掉整个复制队列和进度信息等链路恢复后很可能会漏数据。disable_peer只是暂停发送队列和偏移量都还在后续再enable_peer就能接着传这是容灾链路里比较稳妥的“暂停”按钮。长期方案则是给复制队列单独划一块磁盘或者配置 HDFS Quota防止某个 Peer 故障把整个源集群拖垮。治本的方法是在目标集群做更完整的监控和告警比如 ZooKeeper 可用性、YARN 资源、RegionServer 存活数任何一项出问题都可能在短时间内传导成源端磁盘压力。4.3 复制延迟很高但源端 CPU 和网络看起来都不忙这种情况我遇到过多次表面上什么都正常就是ageOfLastShippedOp越来越大复制队列越积越长。把排查视角放到目标端后发现目标集群的 Region 分布很不均匀大量写入全打在几个 RegionServer 上它们负载很高处理不过来。如果你确认目标端负载正常、网络也正常那问题大概率出在复制线程本身。HBase 的复制线程在读取 WAL 时是串行的一个 RegionServer 上如果有大量表开了复制且早期 WAL 文件比较大日志在“拆装打包”阶段就会占用较多时间。这时可以尝试调大批次参数让每一次 RPC 携带更多数据降低 RPC 次数从而摊薄固定开销。另一个容易被忽略的是 HBase 2.x 里 Serial Replication 的使用。如果你的业务允许多个列族并行复制可以研究一下串行复制特性。它能把原来一条大流水拆成多条小流水整个复制吞吐往往能翻倍。不过引入它之前建议先做一次完整的乱序接受能力评估别为了吞吐破坏业务一致性。4.4 双向复制配置好后发现数据出现环路风险双向复制看起来美好实际上非常容易出问题。A 集群的数据复制到 BB 又把收到的数据当成自己的写入再复制回 A如此反复。虽然 HBase 会对来自复制通道的写入做一些特殊标记但官方并不建议通过简单配置双向 Peer 来实现双向同步版本差异和表配置不一致都可能让环路风险变成实际事故。我自己的经验是能做单向就做单向。需要两个方向都同步时优先考虑应用层双写或者只用消息队列做一端的异步回放。如果你真的逃不开双向 Peer至少把两张表拆成两个不同的列族或者在应用层打上“来源标记”从写入逻辑上强制避免同一行数据在两个集群里反复修改。这个方案很土但比依赖底层防环机制要可靠得多。4.5 复制链路的常见故障速查表症状可能原因第一步排查长期方案目标集群没有新数据列族 scope 为 0 或 Peer 未生效describe看 REPLICATION_SCOPE统一脚本管理表结构复制队列一直增长目标集群故障或网络问题查看目标端 RS 状态和端口连通性监控目标端健康指标配置自动告警源端 HDFS 磁盘告警复制日志无法清理disable_peer暂停清理积压日志限制复制日志目录容量快速恢复目标集群复制延迟大但源端不忙目标 Region 分布不均或批次太小查看目标端热点 Region重新预分区、调大批次、评估串行复制大量 AccessDeniedExceptionKerberos 权限或 ACL 缺失查看源端日志里的认证异常配置专用的复制账号并授权最小权限5. 什么样的场景不建议用官方复制跨集群复制不是万金油在某些场景下它比你想的更难用。批量离线同步几千张表这种场景就不太适合。因为复制是对每一张打开 scope 的表实时生效的如果只是想把一天一次的批处理结果从一个集群“搬”到另一个集群开复制反而会让源集群持续背负网络开销和日志堆积压力不如直接用快照、DistCp 或其他离线同步工具。强一致同步写场景也不建议。官方复制默认异步意味着主集群写入成功以后备集群可能还没收到。金融交易、库存扣减这种必须保证两边同时生效的业务靠复制是不够的。你可以考虑双写与事务消息或者使用更专业的分布式数据层把一致性问题交给有事务保证的系统去解决。跨大版本甚至跨社区分支复制比如 CDH 的 HBase 复制到原生 HBase或者 HBase 1.x 复制到 HBase 3.x也尽量别碰。不同版本之间的序列化格式、RPC 协议、ZooKeeper 数据结构差异非常大表面看着能加 Peer但真跑到一半报错排查成本比直接升级迁移还要高。跨集群复制这个方向也是面试里经常被深挖的点。很多人只背了 add_peer 命令问到“WAL 积压怎么处理”“为什么先迁存量再开增量”就答不上来。你把上面这套机制理解透了面试官再往里问也无非是看你能不能把 ZooKeeper 队列、WAL 清理、目标端 RPC 处理和幂等性串成一个完整故事。最后再分享一个我自己踩过几次坑之后的习惯每次搭完复制链路我都会写一个小脚本周期性从源集群随机取若干行到目标集群比对最新值。这个脚本不重但能帮你在“复制已经坏了几天”之前及时发现问题。大数据链路最怕的不是故障而是故障发生后所有人都没察觉。复制链路看着是后台自动跑的其实也需要你用非常朴素的措施去确认它还在好好工作。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →