花了大半周时间终于把 Flume HBase Kafka 这条数据管道完整地跑通了从环境搭建到数据落地中间踩了不少坑也理清了很多之前模棱两可的概念。这套组合在大数据领域非常经典Flume 负责采集日志Kafka 负责削峰和缓冲HBase 负责海量数据的实时读写。如果你正在搭建日志采集系统、准备做用户行为分析或者想搞清楚这几个组件到底怎么配合这篇文章应该能让你少走很多弯路。先说清楚这套东西解决了什么问题。假设你有几十台服务器在不停产生日志直接让业务程序往 HBase 里写首先并发压力扛不住其次 HBase 的写放大效应会让集群负载忽高忽低。Flume 把分散的日志集中收拢Kafka 在中间做一个大号的“蓄水池”下游消费程序按照自己的节奏去消费最终落到 HBase 存储。整个过程是异步解耦的数据不会丢也不会把存储端打挂。我这次搭的是最小可用版本单机部署所有组件日志文件作为数据源验证从采集到存储分发的全链路流程但配置和生产环境是一致的换到集群环境也只需要改几个地址参数。1. 整体架构设计与组件角色定位1.1 为什么是这三个组件而不是其他方案很多人第一次接触这条链路会问直接用 Logstash Elasticsearch 不行吗或者直接用 Kafka Connect 不香吗这得分场景。Logstash 全家桶在日志检索和可视化上确实强但它本质是一个管道工具存储层做数据分析能力偏弱。而一旦数据量到了每天几亿条需要按 RowKey 做高并发随机读写或者要对海量历史数据做扫描HBase 比 ES 更合适。Kafka Connect 虽然能对接 HBase但控制灵活性差一些一旦需要做清洗、补字段、个性化表结构设计写一个原生消费者会更顺手。Flume 在这里承担的是“搬运工”角色。它最擅长的是监控文件、目录、端口然后把数据推送到下游。相比直接用 Filebeat 之类的采集器Flume 内置了强大的 Channel 缓冲机制相比写一个定时脚本去 scp 日志Flume 支持断点续传和事务保障不丢数据。所以选它的核心原因不是它功能多而是它对“文件到消息队列”这件事支持得最成熟而且足够轻量。Kafka 的角色就更好理解了。采集端可能瞬时爆发比如半夜任务集中跑批或者某个接口突然被刷。如果 Flume 直接写 HBaseHBase 的 memstore 会被瞬间打满触发刷写导致 CPU 飙升、响应变慢。Kafka 像一个蓄水池上游随便泄洪下游按需取水。同时它也为“一份数据多个消费者”提供了便利比如同一条日志既要做实时指标统计又要入离线数仓加一个新的消费组就行完全不影响已有链路。1.2 数据流全链路拆解与各环节职责我把这次测试的完整数据流画出来应该一眼就能看明白整条链路的职责划分日志文件 - Flume Source 监听文件新增行 - Channel 缓冲 - Kafka Sink 发送 - Topic 存储流转 - HBase 消费程序拉取 - 解析 - 批量 Put - HBase RegionServer这个链路里的每个环节都有明确的职责边界。Flume 只做采集不管数据长什么样Kafka 只做传输和暂存不关心数据内容消费程序负责解析和转换HBase 负责最终存储。唯一需要人肉介入的就是消费程序里对数据格式的处理逻辑。这种职责单一的好处是任何一个环节出问题不会污染其他环节的数据排查起来也容易定位。每个组件内部还有一个“小链路”。比如 Flume 有三件套Source、Channel、Sink。Source 读取文件行放进 ChannelSink 再从 Channel 拿数据发给 Kafka。Source 和 Sink 之间通过 Channel 解耦两端的速率不一致不影响彼此。Kafka 内部则是 Producer、Broker、Consumer 三段Flume 充当 ProducerBroker 负责分区存储消费程序是 Consumer。HBase 内部虽然也有写路径但对我们开发人员来说基本透明只需要关心表设计。2. 测试环境搭建与组件基础配置2.1 版本选型与基础设施要求先说版本这地方很容易给自己挖坑。我这次用的组合是JDK 1.8、Flume 1.9.0、Kafka 2.8.0自带 ZooKeeper 但我是单独装的、HBase 2.4.17。这套版本在测试环境跑了四天没有遇到明显的兼容性问题。如果你的项目已经在用其他版本务必确认一点Flume 的 Kafka Sink 底层依赖 Kafka 客户端版本和你的 Kafka Broker 版本差异不要超过一个中版本。否则可能出现协议不兼容Flume 启动报错“NoSuchMethodError”或者“UnsupportedVersionException”之类的问题。另外一个基础设施层面的坑是依赖组件的重叠。Kafka 2.8 开始可以不用 ZooKeeper但 HBase 必须要 ZooKeeper。很多人在同一台机器上装了 Kafka 自带的 ZooKeeper 又装了独立的 ZooKeeper端口冲突搞得启动不了。我的建议是测试环境只维护一套独立 ZooKeeperKafka 和 HBase 共用虽然 Kafka 官方已经在去 ZooKeeper 化但共用能省一个进程测试完全够用。还需要注意内存分配。HBase 的 RegionServer 默认堆内存可能只有 1GB跑小数据量没问题但我在压测时数据量一上来就频繁 Full GC导致写请求停顿。测试环境建议手动把 HBASE_HEAPSIZE 设置为 2GB 到 4GB。Kafka 的堆内存反而不需要太大它依赖操作系统的页缓存堆外内存留给系统当缓冲区更好。Flume 的内存默认 1GB 够用只要 Channel 数据量别太大。2.2 各组件安装配置要点安装已经没有技术含量了解压即用关键是配置。我把几个关键配置项做了记录。ZooKeeper 我用的 3.5.8配置文件只改了两处clientPort 保持默认 2181dataDir 指向磁盘上空间充裕的目录。启动之后先跑一下bin/zkServer.sh status确认起来了再用 Flume 之前先通过zkCli.sh -server localhost:2181测一下连通性。很多 Flume 报错连接 Kafka 超时根子其实是 Kafka 在 ZooKeeper 上注册的地址不对提前验证 ZooKeeper 能省不少排查时间。Kafka 的 server.properties 需要注意几个点。listenersPLAINTEXT://localhost:9092这个配置之前有同事图省事配置成0.0.0.0结果客户端一直在连报错却全是连接超时。log.dirs要指定到一个空间充足的目录Kafka 的 Segment 日志如果磁盘满了会直接拒绝写入。另外log.retention.hours我设成了 72测试周期内足够翻查消息避免数据被过早清理。HBase 的 hbase-site.xml 重点配置hbase.rootdir指向 HDFS 或者本地文件系统。测试环境没有 HDFS我用的是本地路径也就是 thelocal file systemfile:///data/hbase。这里有个细节如果后面换成 HDFS根目录必须是一个不存在的路径HBase 会自动创建否则也会报错。hbase.zookeeper.quorum指向 ZooKeeper 地址。集群环境还需要把hbase.cluster.distributed配成 true单机测试保持 false 就行。我整理了一个最小可用的基础配置对照表方便对照排查组件关键配置项测试值说明ZooKeeperclientPort2181端口别被占用KafkalistenersPLAINTEXT://localhost:9092不要图省事配 0.0.0.0Kafkalog.dirs/data/kafka-logs空间充足的分区HBasehbase.rootdirfile:///data/hbase生产环境用 HDFS 路径HBasehbase.zookeeper.quorumlocalhost:2181ZooKeeper 地址HBaseHBASE_HEAPSIZE4GB压测时加大避免 GC组件之间还有一个很隐蔽的依赖关系容易被忽略HBase 需要在系统 hosts 文件里配置主机名映射。如果/etc/hosts里只写了 127.0.0.1 却没有写自己的主机名HBase 启动后 RegionServer 可能注册失败排查半天都不知道为什么。提前把hostname输出的名字和 IP 写进 hosts这是新手最容易忽略的一步。3. Flume 端配置采集与投递 Kafka 的实现细节3.1 Flume 核心配置逐行解析Flume 的配置是整个链路里第一个真正需要动脑子的地方。我用的 flume.conf 本质上只干一件事监听一个日志文件把新增的每一行发给 Kafka。但是配置里有大量可以调整的参数每一个都对应不同的行为语义。先看我的完整配置文件agent.sources src1 agent.channels ch1 agent.sinks sink1 agent.sources.src1.type taildir agent.sources.src1.positionFile /data/flume/taildir_position.json agent.sources.src1.filegroups f1 agent.sources.src1.filegroups.f1 /data/logs/app/.*\.log agent.sources.src1.fileHeader true agent.channels.ch1.type memory agent.channels.ch1.capacity 10000 agent.channels.ch1.transactionCapacity 5000 agent.sinks.sink1.type org.apache.flume.sink.kafka.KafkaSink agent.sinks.sink1.kafka.bootstrap.servers localhost:9092 agent.sinks.sink1.kafka.topic app-log-topic agent.sinks.sink1.kafka.flumeBatchSize 500 agent.sinks.sink1.kafka.producer.acks all agent.sources.src1.channels ch1 agent.sinks.sink1.channel ch1第一眼看这个配置最大的亮点是 Source 类型用的 taildir而不是大多数人熟悉的 spooldir 或 exec。taildir 和 exectail -F行为类似但多了记忆功能。它会把文件里已经消费到的位置记录在 positionFile 里Flume 重启后能接着断点继续读不会重复也不会丢。spooldir 只适合处理固定文件名的目录每个文件只能完整消费一次不适合日志这种持续追加的场景。所以只要目标是无边界的日志文件taildir 就是第一选择。Channel 我用的 memory。优点是吞吐高、延迟低缺点是 Flume 进程挂了数据会丢。Kafka 和 HBase 的框架设计都足够可靠但这不代表每个环节都能扔数据。我做测试时用 memory 图省事但如果你在生产环境文件改成 file channel 更稳妥代价是磁盘 IO 和吞吐量会打折。后面故障排查部分我还会再提这一点。Kafka Sink 部分最值得关注的是kafka.producer.acks all。acks 代表 Kafka 确认消息写入的节点数量。设成 all 意味着只有所有副本都写入成功才算发送成功这是最严的保证代价是延迟和吞吐量有损耗。测试环境无所谓生产环境如果你能接受极端情况下丢失少量数据换吞吐可以设成 1但日志系统多数还是建议 all毕竟日志丢了就没法重建了。3.2 生产场景的调参与注意事项配置能跑起来很简单但跑起来之后你会发现几个性能瓶颈点。首先是 taildir 的filegroups正则要注意同一个文件组里如果有多个文件taildir 会顺序读取还是并发读取它内部实际上是单线程串行处理每个文件的新增内容的所以如果多个日志文件都在高频写入单个 Flume agent 会形成瓶颈。生产上的解法是把不同来源文件的日志拆成多个 filegroup甚至起多个 agent每个负责一部分文件。第二个调参重点是 Channel 的 capacity 和 transactionCapacity。capacity 是 Channel 里最多缓存多少条消息transactionCapacity 是 Source 或 Sink 单次事务最多取多少条。我配置里的容量看起来很大但生产环境要谨慎memory channel 的数据是放在堆内存里的capacity 设太大一旦 Source 读进来很多但 Sink 网络堵塞Channel 里的数据就会堆积直接把 Flume 的堆内存打满表现就是 Flume 频繁 Full GC消息积压越来越严重。经验是 capacity 设置为单日高峰流量的 10% 左右transactionCapacity 不要超过 capacity 的一半。Kafka Sink 还有个隐藏参数容易被忽略flumeBatchSize。这是 Flume 每批次向 Kafka 发送的消息条数我设了 500。之前用默认配置 100 的时候遇到突发日志量Kafka 的生产端明显跟不上Topic 里的消息积压严重。调大之后 Flume 需要攒够 500 条才发一次虽然单条延迟变高了但整体吞吐明显提升。日志类场景本身就不苛求单条毫秒级延迟吞吐优先是正确方向。还有一个很容易踩的坑如果 Flume 配置里 kafka.topic 不存在KafkaSink 默认会自动创建吗虽然 Kafka 默认允许自动创建 Topic但生产环境通常会把auto.create.topics.enable关掉。所以比较稳妥的做法是提前用kafka-topics.sh把 Topic 创建好指定好分区数和副本数。提前创建也能让该 Topic 的分区分布更均匀避免自动创建时全部落在同一个节点上。我的分区数设计参考了后续消费者并行度4 个分区对应单机测试的 4 个消费线程。调试 Flume 时还有一个实用技巧加一个 logger sink 来辅助观察数据是否真的被采集到。把 Kafka sink 临时替换成 logger sinkFlume 会在日志里把每条消息内容打印出来。确认数据格式没问题之后再切回 Kafka。这样能快速区分问题出在 Flume 采集端还是 Kafka 传输端。4. 消费端落地 HBase表设计、RowKey 与批量写入4.1 数据表设计与 RowKey 策略数据从 Kafka 读出来之后真正的重头戏开始了怎么设计 HBase 的表以及怎么把数据写进去。表设计直接决定了后续查询性能和数据分布我花在这个环节的时间比配置 Flume 还多。先说表结构。针对日志类数据我建的表结构非常简洁一张表、一个列族、一列数据。因为日志内容本身是半结构化的用 HBase 的列族动态特性没有必要拆太散。表名app_log列族cf每条日志的逻辑存储为RowKey 是规则拼接的唯一标识列名data值就是日志的完整原始内容。如果你需要对日志字段做查询可以把解析后的结构化字段单独存成多个列但要注意后续的业务需求——需求没确定前保留 raw 格式最稳妥后面加列不需要改表结构HBase 列族天然支持动态列。RowKey 的设计是最关键的。HBase 的物理存储是按 RowKey 字典序排列的如果 RowKey 用客户端 ID 加时间戳那么同一个客户端的日志会全部落在相邻 Region 上热点全部集中在一个 RegionServer其他节点闲着没事干。我这次测试用了最简单的加盐方案RowKey 2 位随机前缀 时间戳倒序 客户端 ID。前缀打散分布时间戳倒序是为了让同一行的新数据靠前方便筛选最新日志。简单来说RowKey 设计要平衡三个诉求唯一性、查询效率、写入分散。预分区是表设计里不能跳过的一步。刚开始我直接建表没有做预分区结果所有写入全部挤在同一个 Region虽然测试数据量也不大但明显能感觉到写延迟不稳定。HBase 单表默认只有一个 RegionPre-split 需要提前规划好 split 点。以 RowKey 前缀 00 到 99 为例可以在建表时拆成 10 个 Region每个扫 10 个前缀区间。这一步是 HBase 性能的关键如果上线后再改需要涉及 region split 再平衡代价比预先规划大得多。4.2 消费写入代码实现与参数权衡代码部分我用 Java 写了一个 Kafka 消费程序核心逻辑很直观监听 Kafka 的 app-log-topic拿到每条消息后解析出业务字段组织成 Put 请求批量写入 HBase。让我把核心代码展示一下// 核心消费循环 try (KafkaConsumerString, String consumer new KafkaConsumer(props)) { consumer.subscribe(Collections.singletonList(app-log-topic)); Connection hbaseConn ConnectionFactory.createConnection(hbaseConf); BufferedMutator mutator hbaseConn.getBufferedMutator(TableName.valueOf(app_log)); while (running) { ConsumerRecordsString, String records consumer.poll(Duration.ofMillis(1000)); ListPut puts new ArrayList(records.count()); for (ConsumerRecordString, String record : records) { String[] fields record.value().split(\\|); String rowKey buildRowKey(fields[0], fields[1]); Put put new Put(Bytes.toBytes(rowKey)); put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(data), Bytes.toBytes(record.value())); puts.add(put); } mutator.mutate(puts); mutator.flush(); consumer.commitSync(); } }这段代码背后有三个重要的权衡决定值得展开说。第一个是为什么用 BufferedMutator 而不是普通的单条 put。写 HBase 有一个永远存在的性能原则要批量写。单条 put 会产生大量 RPC吞吐直接被网络往返打垮。BufferedMutator 在客户端维护缓冲区攒到一定量或者超过写缓冲区阈值时批量 flush 到服务端。测试里单条写入的吞吐大约是每秒 500 行BufferedMutator 加 500 批次能到每秒 1.5 万行以上差距非常明显。第二个是 Kafka 如何提交 offset。我用了同步 commitSync消费完一批消息并成功写入 HBase 之后再去提交 offset。这是“至少一次”语义写入 HBase 成功了但 offset 提交失败会怎么样程序重启后消费程序会重复消费这一批消息。重复写入 HBase 的后果取决于 RowKey如果 RowKey 相同那后续 put 就是覆盖结果幂等没问题如果 RowKey 里有随机值结果就会出现重复数据。所以实践上最好让 RowKey 能唯一标识同一条日志从源头避免重复插入。第三个是生产消费的并发度匹配。Kafka 的分区数决定消费组内的并行度上限。我这里 Topic 有 4 个分区消费组里可以起 4 个并发线程每个线程负责一个分区。写 HBase 的缓冲区和线程数也要跟上否则消费线程很快把数据拉到内存但 HBase 的写入吞吐却跟不上会导致内存溢出。测试环境跑 4 个线程时我给每个线程单独建 BufferedMutator启动时固定写缓冲区大小为 8MB限制单个客户端可能占用的内存总量防止堆内存波动。消费端的错误处理也要提前想清楚。Kafka 的 poll 拉取可能拉到空数据这是正常的不要当成异常去打印刷屏。如果写入 HBase 抛异常比如 RegionServer 正在 split 导致短暂不可用我的策略是重试三次超过重试次数就把原始消息记录到本地待补偿表。而不是死循环重试——Kafka 已经可以保证数据不丢别让客户端变成新的故障点。5. 端到端联调全流程与数据核验方法5.1 从启动组件到数据落库的完整操作序列配置和代码都就绪后联调变成了流水线式的操作。第一次通链路的人很容易卡在启动顺序上这里给一套标准顺序照做不会出问题。第一步先启动 ZooKeeper然后启动 Kafka。不要同时在终端启动多个前台进程Kafka 切换到后台运行并写日志bin/kafka-server-start.sh config/server.properties /data/kafka/logs/server.log 21 。验证 Kafka 是否正常我用bin/kafka-topics.sh --bootstrap-server localhost:9092 --list这个命令能列出 Topic 就说明启动成功。创建 Topic 的命令kafka-topics.sh --create --topic app-log-topic --partitions 4 --replication-factor 1 --bootstrap-server localhost:9092。第二步启动 HBase。bin/start-hbase.sh之后最好等 10 秒左右让 RegionServer 注册完成。然后bin/hbase shell进入命令行执行list命令确认能看到刚创建的表。这一步千万别略过我第一次就是没确认结果下游消费程序在尝试连接 HBase 时报错找不到表。第三步在测试目录/data/logs/app/下创建一个日志文件启动 Flume agent。启动命令是bin/flume-ng agent -n agent -c conf -f conf/flume.conf -Dflume.root.loggerINFO,console。日志里能看到 source 和 sink 启动成功的信息。这时候往日志文件里追加几行测试数据应该在 Kafka 的 Topic 里能立刻看到。启动 Flume 的时机有个讲究Log 文件不存在时 taildir 也能正常启动等文件出现后会自动开始读文件头。所以你可以先启动 Flume 再创建日志文件。第四步启动消费程序。理论上到这一步数据就会自动流转到 HBase 了。但我建议在启动消费程序之前先用 Kafka 自带的命令行消费者确认数据格式再让程序去消费这样排查效率最高。命令是kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic app-log-topic --from-beginning。最后打开 HBase shell执行scan app_log, {LIMIT 10}能看到数据就说明全链路通了。不要小看最后这个核验步骤很多人通到一半发现 Kafka 能看到数据就以为万事大吉了实际上数据根本没写到 HBase等到想查数据的时候才发现白高兴一场。5.2 数据核验从源头到落地的全链路一致性检查联调通了之后还要做一个更重要的事数据有没有丢、有没有重复。我这次用的是“三段对账法”把完整流程分成三段来校验每一段都能独立验证它前面的环节是否正常。第一段是采集端到 Kafka。先在日志文件里写入 N 条固定格式的日志然后用kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group group1查看消费组的位置。如果这个 Topic 没有消费者就临时用控制台消费者消费一次统计消息条数是否等于写入的文件行数。这里最容易暴露的问题是 taildir 正则写错导致没有监控到文件或者文件没有追加新内容导致 Flume 没感知到。taildir 对日志文件的移动和重命名有严格的行为约束日志框架切割文件时如果直接重命名当前文件Flume 可能误判为“新文件出现”导致整个文件重复消费一遍。第二段是 Kafka 到 HBase 消费端。这里主要看消费程序有没有重复提交 offset。判断方法是观察 HBase 里的 RowKey 是否重复或者看消费组里LAG是否为 0。如果 LAG 持续走高说明消费速度跟不上生产速度。我之前出现过消费端程序逻辑里写了Thread.sleep(10)单个消费者拉取间隔太短导致 CPU 空转的情况现象就是数据都堆在 Kafka 拉不过来。调大 poll 的批量参数和减少不必要的同步操作后LAG 才稳定下降。第三段是最终落库数据。这个环节最容易忽略的是数据内容对不对。我在消费程序里打印了实际写入 HBase 的 RowKey 和原日志的对比日志发现有一个字段的时间戳解析逻辑不对所有行的时间都偏移了 8 个小时。数据管道通了不代表数据是对的这种业务逻辑错误往往比技术问题更难发现。建议在联调阶段找三条完整的日志从原始文件开始、到 Kafka 消息、再到 HBase 数据逐条比对内容做一次全链路的字段级核对。我最后还把测试时的数据规模做了一次小压力验证写入 100 万行日志观察整个管道的吞吐。实测结果是 Flume 采集约每秒 1.2 万条Kafka 端到端消费约每秒 8000 到 1 万条HBase 批量写入也在这个量级附近。瓶颈不在单点性能而在于我的虚拟机和磁盘 IO。生产环境换 SS 磁盘、多个 agent 并行、消费端扩容整个链路的吞吐提升空间非常大。6. 常见故障与排查经验速查6.1 高频故障现象与处理方法联调过程中绝对会遇到问题。我整理了一个速查表覆盖了最容易出现的几类故障现象每条都标注了排查思路和解决办法故障现象可能原因排查与处理Flume 启动后无任何数据日志文件正则写错检查 filegroups 路径匹配看 Flume 日志有没有 source 启动记录Flume 连接 Kafka 超时bootstrap.servers 地址不对检查网络地址用 telnet 测试 9092 端口连通性Kafka Topic 消息堆积消费组 offset 没有提交或消费端卡死查看消费组 LAG 指标检查消费者线程状态消费端写入 HBase 报 RegionServer 拒绝连接RegionServer 未注册或已崩溃检查 HBase 页面状态查看 hbase-hmaster 日志HBase 数据大量重复消费端重复消费RowKey 未做幂等调整 RowKey 设计避免随机值做唯一标识HBase 写入吞吐极低没有用批量写入单条 put改用 BufferedMutator 并调整写缓冲区大小Flume 内存持续增长Memory Channel capacity 过大降低 capacity或者切换为 file channel日志文件被切割后重复读taildir 识别不了文件重命名日志框架切割文件时用 copytruncate 模式这些问题里有几个特别值得展开讲讲处理思路。Flume 启动后没有数据这个现象我遇到过的最大原因不是配置问题而是日志文件压根没有新内容追加。taildir 只会监控文件的新增行老文件的历史内容不会回头去读所以你如果直接往一个已有 100 行的日志文件里丢新行没问题但你要是期待它把历史 100 行也读出来它是读不了的。测试时不少人会用echo test app.log的方式追加这个没问题确认一下文件是不是真的追加成功了别用覆盖的方式写。Kafka 连接超时这个问题十有八九是监听地址和客户端地址不一致。如果你启动 Kafka 时 listeners 配的是localhost:9092但 Flume 配置的是服务器的内网 IP客户端会拿着内网 IP 去对 Kafka 返回的 broker 地址握手自然连不上。绝招是把advertised.listeners也配成对应的地址避免 broker 把错误的地址返回给客户端。如果你跨机器测试这个是必踩的坑。6.2 排查链路各阶段的定位思路出了故障之后快速定位到具体环节比记住故障列表更重要。我总结了一套自己的定位方法按阶段递进排查第一看 Kafka。kafka-consumer-groups --describe是定位整个链路问题的万能钥匙。如果 LAG 一直为 0说明生产速率和消费速率匹配如果 LAG 快速上涨说明消费端是瓶颈。再配合kafka-run-class kafka.tools.GetOffsetShell查看当前偏移量能判断数据是否真的进入了 Kafka。第二看 HBase。hbase shell里执行count app_log看总行数和 Kafka 消费总数做个对比。如果行数远小于 Kafka 消息总数问题出在消费端写入逻辑或者 RowKey 大量重复导致覆盖。用scan按时间范围扫一下看数据有没有持续新增。第三看日志。各组件日志是最后的线索来源。Flume 的日志会打印Sink 写入成功多少条Kafka 的 server.log 会记录分区变化和异常HBase 的 RegionServer 日志最容易暴露写阻塞、Region split 等问题。记得把日志级别调到 INFO 以上再测试WARN 级别容易把关键报错过滤掉。还有一个终端排查技巧用jstack查看消费程序当前在干什么。有一次我看到消费端一直不消费按经验怀疑是 Kafka 消费组重平衡卡住了用jstack一看原来卡在 HBase 的ConnectionFactory.createConnection上连接一直没有建立成功。这种问题不看线程栈很难定位日志里只会有几行连接超时的告警。7. 联调之外Kafka 分区数与 HBase Region 数的搭配建议通了之后我认真思考了一下这个链路的容量规划问题这是从“能跑”到“跑得好”的分水岭。Kafka 的分区数和 HBase 的预分区数应该联动规划而不是各自独立决定。Kafka 的消费并行度上限是分区数HBase 的写入并行度上限是 Region 数。如果 Kafka 有 16 个分区但 HBase 只有 4 个预分区那么 16 个消费线程会全部竞争写入那 4 个 Region热点和写入阻塞无法避免。反过来 HBase 有 32 个 Region 但 Kafka 只有 4 个分区那大部分 Region 一直闲着资源浪费。我这次测试用的比例是 Kafka 4 个分区HBase 预分区 10 个。这其实不是一个严格的公式但思路是对的先根据单条消息大小和预估 TPS 确定 Kafka 分区数再让 HBase 的预分区数等于或者略多于 Kafka 总分区数乘以单台消费节点的线程数。这样任何一端的扩容都能被另一端接住不会出现资源错配。还有一个容易忽略的容量瓶颈是磁盘 IO。这条链路的数据最终流量是双份的Kafka 写了一份日志消费端读了一份HBase 又写了一份。也就是说每来一条日志磁盘要至少承担三次 IO。SSD 在测试环境已经都快被占满机械盘跑大数据量场景基本会变成整个管道的最大短板。如果你准备把这个方案用于生产先给机器配好 SSD 再谈性能这是硬件层面性价比最高的选择。关于监控我个人的建议是不要一开始就上一套完整的监控系统那会分散精力。先把三个指标盯住Kafka 消费组的 LAG、HBase 的写请求延迟、Flume 的 Channel 占用率。这三个指标任何一个报警都能快速定位到瓶颈点。我这次就是靠 LAG 指标发现了消费端线程数设置不合理的问题及时调整后吞吐直接翻倍。Kafka 数据保留时间也值得提前规划。虽然我们最终目的是写入 HBase但 Kafka 作为缓冲层会保留一定时间段的数据这段时间就是数据重放和修复的窗口。如果你只是为了实时落库保留 24 小时就够如果你在链路还没有完全稳定建议保留 72 小时给自己留足排查时间。我测试期间就把这个参数改成了三天某次消费端程序出问题导致丢了一批数据就是靠 Kafka 里的残留数据补回来的。最终再分享一个我们测试链路时的经验改造数据时尽量保持源日志的完整性和全链路统一性。这条链路非常适合先跑起来然后逐步迭代。Flume 负责采集、Kafka 负责缓冲、HBase 负责存储每个组件只干一件事出问题时只需要盯住那一个环节。我这几天的切身体会就是最难的永远不是技术本身而是链路跑通之后如何持续监控、快速定位问题以及在上线前把每一个可能出错的细节都提前演练一遍。踩过一轮坑之后整个数据通道现在是真的一路畅通了。
阅读完成 · 觉得有帮助?