
接了一个电话电话那头的声音明显压着火线上报表跑不动了MySQL连Doris直接报 10060老板在催数。我第一反应不是查Doris的BE进程而是先问了句你们的Zookeeper还活着吗对面安静了两秒然后说ZK我们装Doris的时候不是一起装了吗它还能挂——这就是典型地把Zookeeper当成装了就忘的组件。在Doris这套架构里ZK不是配角它是FE集群能不能正常选主、节点能不能互相发现的地基。这篇内容我会从Zookeeper和Doris的协作机制讲起把部署时的拓扑设计、注册发现的具体路径、故障排查的完整链路以及DataX、Kettle、MySQL实时同步、Prometheus这些周边集成场景都串一遍。无论你是刚接触Doris的新手还是已经被生产环境毒打过一轮的运维这篇内容应该都能给你一些可以直接抄作业的东西。1. Doris为什么需要Zookeeper协调职责的边界在哪里1.1 一个被误解的角色定位先说结论避免你在错误的方向上浪费时间Zookeeper在Doris里存的是集群的通话录和选举现场不是Doris的业务元数据。这个误解很容易产生因为一说到分布式协调很多人下意识就觉得元数据肯定存在ZK里。实际上Doris的FE元数据数据库、表结构、分区信息、副本状态这些是通过FE节点间的BDBJE做同步和持久化的存放在FE节点的本地目录里。而Zookeeper承担的是另外三件非常具体的事FE集群的选主协调多个Follower FE之间需要确定谁是Leader这个选举过程中ZK提供了分布式锁和临时节点机制保证选主不打架。BE节点的注册与发现每个BE启动后要向FE上报FE把BE列表信息写入ZK让集群内所有FE都能拿到一份实时更新的节点拓扑。Broker与外部组件的注册管理Doris的外表访问比如访问Hive、HDFS依赖BrokerBroker也是通过ZK完成注册和状态上报的。你可以把ZK理解成小区物业的公告栏业主FE、BE、Broker进来了先在上头贴个条注册离开了再把条撕掉注销偶尔大家需要决定谁当楼长选主也在公告栏下面碰头。但公告栏本身不保存业主家里的财产明细那些东西都在各自家里锁着。1.2 Doris在ZK上到底写了哪些东西要直观理解Doris和ZK的协作最直接的办法是登录ZK看一眼。zkCli.sh -server 10.0.0.11:2181 # 查看Doris集群注册的根路径 ls /doris # 正常情况下你会看到类似这样的结构 # [cluster_xxx, be_nodes, fe_nodes, brokers]路径下的子节点大致对应Doris集群中不同类型组件的注册信息。其中/doris/be_nodes是BE的列表/doris/fe_nodes是FE的列表/doris/brokers是Broker的列表。如果你执行get命令查看某个BE节点下的内容可以看到类似{ip:10.0.0.31,heartbeatPort:9050,bePort:9060,httpPort:8040,brpcPort:8060,lastStartTime:...}这种JSON格式的注册信息。熟悉这个路径结构对故障排查特别有帮助当你的Doris集群出现某一个FE看不到某个BE这种诡异现象时先去ZK里看这条BE的注册信息是不是还在、是不是过期删除了往往能比直接重启BE更快定位问题。提示不同版本的Doris在ZK上的节点路径略有差异老版本可能挂在/doris/下直接带cluster_id新版本做了目录分层。但核心思路不变——先看注册节点是否存活再看心跳是否正常。1.3 没有ZK时Doris能跑吗理论上单FE部署的Doris可以不依赖ZK。因为只有一个FE不存在选主问题BE的注册信息由这个FE自己维护就行不需要外部协调者。但一旦你上多FE问题就来了两个Follower FE之间需要选主选主过程中一个节点失联或者网络抖动如果没有外部的权威协调者两个FE可能都认为自己是Leader往各自的BDBJE日志里写内容状态一分叉整个元数据就毁了。这种双主或脑裂场景比宕机还难恢复。所以我的建议很简单如果你只是本地测试、单FE单BE跑个Demo可以不装ZK只要你的Doris要支撑多FE的高可用ZK就是刚需没有任何妥协空间。2. 部署集成时的组件选型与参数从Demo集群到生产环境2.1 一套合理的部署拓扑长什么样先给出一套我实际用过的、比较稳妥的最小生产拓扑供你参考节点硬件建议组件角色说明node14C16GZK Doris FEFollower复用ZKFE负责查询和元数据node24C16GZK Doris FEFollower复用ZK另一个选主节点node34C16GZK Doris FEObserver复用ZKObserver扩展读能力node48C32GDoris BE存储与计算node58C32GDoris BE存储与计算node68C32GDoris BE可选的第三副本很多团队为了省机器会把ZK和Doris FE部署在同一批节点上。这种做法可行但有一个前提ZK是轻IO、重内存和网络延迟的组件它最怕的是磁盘长时间停顿和网络分区。FE在元数据轮转或checkpoint时CPU和磁盘IO会有明显波动如果同一个节点上的ZK被波及会导致ZK会话超时然后连锁引发FE选主抖动。如果你机器充裕ZK最好独立三台哪怕配置低一点2C4G都够也不要和计算节点混布。2.2 ZK侧的关键参数会话超时与自动清理部署好ZK之后有几个参数值得单独拿出来讲因为它们直接决定了Doris故障时的表现。第一个是tickTime。ZK的基础时间单位默认3000毫秒一般不建议动。它对Doris的影响体现在会话超时计算上会话超时上限通常是tickTime * 20 60秒下限是tickTime * 2 6秒。Doris的FE在初始化ZK连接时会请求一个会话超时值如果这个值超过ZK允许的上限会被强制截断。结果就是FE持有的ZK会话会比预期更短命网络一抖动会话就断了。第二个是autopurge.snapRetainCount和autopurge.purgeInterval。ZK的data目录下会持续产生snapshot和事务日志如果不做自动清理磁盘被塞满只是时间问题。磁盘满了之后ZK无法写入新的会话信息整个集群协调能力瘫痪。推荐至少保留3份快照清理间隔设为1小时autopurge.snapRetainCount3 autopurge.purgeInterval1提示ZK的JVM堆内存不要给太大4G足够。ZooKeeper的优势在于高吞吐的读写但本地内存缓存需要控制堆过大反而会导致Full GC频繁引发会话超时。2.3 Doris侧的ZK衔接配置在Doris的FE配置中其实你找不到一个叫zookeeper.servers的完整连接串配置这跟很多中间件不太一样。Doris的FE启动时是通过本机的doris-meta目录下的信息以及FE集群间的通信确认ZK地址的。更准确地说你在搭建FE集群时通过ALTER SYSTEM ADD FOLLOWER和ALTER SYSTEM ADD OBSERVER命令把节点加入集群FE内部会自动把注册信息和ZK地址绑定关系记录在元数据里。所以部署时的顺序非常重要先把ZK集群启动好确认zkCli.sh能连上。启动第一个FE也就是Helper节点初始化集群。再启动第二个FEFollower通过bin/start_fe.sh --helper host:port命令让它从Helper节点同步元数据加入集群。通过MySQL客户端连接FE执行ALTER SYSTEM ADD FOLLOWER host:port;等SQL把第二个FE正式纳入集群。后续新增的FE节点同样用--helper参数启动再执行ADD FOLLOWER或ADD OBSERVER。这个先启动、再注册的顺序本质上是让FE先建立与ZK的会话再把节点信息写入集群元数据。顺序反了比如先把ALTER SYSTEM ADD FOLLOWER执行了但新FE还没起来ZK上会挂一个临时注册信息后面FE再启动时可能因为节点状态不一致而报错。3. 集成链路里的时序细节注册、发现、选主、脑裂防护3.1 BE启动后是如何让人看见的经常有人问我我在BE上执行了start_be.shFE也自动就发现了它这中间到底发生了什么完整链路是这样的BE进程启动后读取自己的conf/be.conf拿到BE的IP、heartbeatPort、bePort这些信息。BE向FE的心跳端口发起注册请求。FE收到后会把BE信息写入ZK的/doris/be_nodes路径下同时在自己的内存中标记该BE为存活但未被加入集群。管理员在MySQL客户端执行ALTER SYSTEM ADD BACKEND host:heartbeat_port;这是真正把BE纳入集群的操作。执行完之后BE状态变为可用数据分片就可以调度到它上面了。如果跳过第3步BE是注册了但状态一直为OFFLINE或system_decommissioned表现在查询上就是该后端不可用。之前遇到过一种迷惑情况BE进程明明活着端口也能通但查询一直报错后来发现是当时只启动了BE忘记在FE里执行ADD BACKEND。这类问题从ZK节点上看会非常清楚/doris/be_nodes上可能连记录都没有或者记录里状态字段和预期不一致。3.2 FE选主并不是ZK说了算这个点比较反直觉值得展开讲讲。Doris的FE选主并不是简单地谁在ZK上抢到了leader临时节点谁就是Leader。真正确定Leader的是BDBJE的选举机制ZK在其中扮演的角色是协调者——它提供一个公平的竞争环境让多个Follower FE通过创建临时顺序节点的方式参与选举拿到最小序号的那个节点获得优先级。BDBJE再基于这个优先级结果完成最终确认。换句话说ZK会告诉你该轮到谁上场了但能不能上场、上场之后能不能服众还是BDBJE和元数据日志说了算。理解这一点有什么用它解释了为什么有时候你在ZK上看到某个FE的leader节点还是它的但Doris集群的实际响应已经很差了——因为ZK侧的选举信息只是引用不是最终状态。3.3 网络分区与脑裂ZK是怎么兜底的一旦出现网络分区比如机房内两台FE节点之间的网络断了但ZK集群整体还健康会发生什么两个Follower FE都会尝试连接ZK。由于ZK上通常只会给同一类节点的其中一个分配准leader资格另一个节点在获取资格时会失败从而保持Follower角色不变。这就是ZK的兜底作用它用外部权威状态消除了你觉得自己是Leader我也觉得自己是Leader的分裂风险。但这里有一个前提ZK集群本身必须满足多数派可用。如果ZK三台中坏了两台剩下那一台即使还活着也无法对外提供选主服务因为无法形成多数派。这时候Doris FE的选主流程会停滞表现就是FE主节点不切换写入功能不可用。这种场景下修复思路不是去重启Doris而是先恢复ZK集群的多数派。提示很多人排查Doris高可用问题时第一反应是看FE日志和BDBJE状态这没错。但养成习惯先看一眼ZK集群的健康状态能帮你节省大量时间。一条命令的事echo mntr | nc zk_host 2181看zk_server_state是不是leader或follower。4. 排查实录一次Doris全挂了的故障复盘4.1 现象多个端口同时无法访问回到开头那个场景。当时集群规模不大3个FE2 Follower 1 Observer、3个BE。故障表现是通过MySQL协议连接Doris的9030端口失败报错Cant connect to MySQL server on 127.0.0.1:9030访问FE的Web页面9010端口浏览器一直转圈BE的8040端口虽然通了但SQL跑不出来。第一感觉像是Doris服务全挂了但ps一看FE进程和BE进程都还活着。这就引出第二个问题进程活着服务为什么不可用4.2 根因链路ZK假死后FE的leadership丢失接下来排查分三步走第一步检查ZK状态。执行echo mntr | nc 10.0.0.11 2181发现连接无响应再用zkServer.sh status查看发现三台ZK全都变成了unknown。这说明ZK集群已经无法对外提供正常服务大概率是节点间通信出了问题或者磁盘IO卡死了。查了下系统日志ZK所在机器的磁盘因为日志清理工具异常IO等待时间飙到了90%以上属于典型的假死状态。第二步查看FE日志。在FE的log/fe.log里能看到大量与ZK相关的异常Failed to connect to ZooKeeper server Session expired Unable to reconnect to ZooKeeper service, session timed out这些日志出现之后FE的Leader角色很快丢失了。因为FE和ZK之间的会话超时ZK会把这个FE的临时节点删除其他Follower开始新一轮选主。但由于ZK本身已经假死新一次的选主也无法顺利完成最终所有FE都卡在没有Leader的状态。进程活着集群却是瘫痪的。第三步确认“问题发起点”在哪。通过比对系统日志和ZK日志时间线发现根因是跑在ZK同一台机器上的一个日志清理脚本在特定时间段内执行了大规模文件删除操作导致磁盘IO被占满。ZK的所有事务日志写入都被阻塞继而大量节点之间的心跳丢失ZK节点进入孤岛状态。4.3 修复步骤与恢复预案这个案例的修复过程分两步。第一步先救ZK。把日志清理脚本停掉等磁盘IO恢复正常后重启ZK服务。ZK起来后确认执行zkServer.sh status能看到一台leader、两台follower。注意ZK恢复后不要急着动Doris给ZK一点时间处理历史会话的过期事件。第二步再看Doris FE。通常情况下ZK恢复后FE的会话能自动重连选主流程会重新完成集群会慢慢回到正常状态。但如果FE日志里出现了Followers meta out of date或类似提示说明某个FE的元数据版本落后太多需要重启这个FE让它重新从Leader同步元数据。修完之后我总结了一套针对ZK异常影响Doris的恢复预案现在放到这里供你参考检查项命令/操作预期结果ZK进程状态zkServer.sh status3台节点中必须有1 leader、2 followerZK集群是否多数派查看各节点状态汇总存活节点数必须超过总节点数一半Doris FE选主状态登录MySQL执行SHOW FRONTENDS;有一个FE的IsMaster为trueBE注册状态执行SHOW BACKENDS;所有BE的Alive为true元数据同步查看FE日志中的finished to check css无报错同步正常这个预案我后来写成了运维手册每次演练直接照着执行恢复时间从最初的两个多小时压缩到了二十分钟以内。5. 周边生态集成的高频场景数据写入、同步与监控5.1 DataX与Kettle写Doris的插件选型Doris上线之后第一个绕不开的问题就是数据怎么进去。实际场景里企业数据大概率在Oracle、MySQL、SQL Server这些传统数据库里躺着先把这批数据同步到Doris后面才能谈OLAP查询。DataX是阿里开源的异构数据同步工具社区里有基于Doris的插件方案。常见做法是直接用DataX的doriswriter插件它支持通过Doris的Stream Load接口以CSV或JSON格式批量写入吞吐量远高于单条insert。实践配置大概长这样{ job: { content: [{ reader: { name: oraclereader, parameter: { username: sync_user, password: ******, connection: [{ jdbcUrl: [jdbc:oracle:thin:10.0.0.20:1521/ORCL], table: [ODS_ORDER] }], column: [ORDER_ID, ORDER_AMT, CREATE_TIME] } }, writer: { name: doriswriter, parameter: { username: doris_user, password: ******, connection: [{ table: [dwd_order], jdbcUrl: [jdbc:mysql://10.0.0.11:9030/dw] }], preSql: [], postSql: [], loadUrl: [10.0.0.31:8030], column: [ORDER_ID, ORDER_AMT, CREATE_TIME], loadProps: { column_separator: \\x01, format: json } } } }] } }Kettle这边8.3版本之后的插件体系比较成熟社区有专门的Doris插件如doris-stream-loader本质上是把Kettle的行流通过Stream Load写入Doris。这里我最想提醒的一点是不要用Kettle的普通表输出组件连接Doris的9030端口做逐行insert。虽然Doris兼容MySQL协议能插进去但每一行insert背后都是一次独立的提交性能非常差数据量大一点就能把FE的导入队列压垮。正确姿势永远是走Stream Load或其封装的插件。5.2 MySQL实时同步到Doris的链路实时同步这块通常的做法是利用MySQL的binlog通过Canal解析后将变更写入Kafka再由Doris的 Routine Load 消费Kafka数据落进Doris表。这条链路比直接对着Doris写JDBC要可靠得多原因在于Doris的Routine Load天然支持从Kafka消费CSV或JSON格式的数据并且可以自动处理断点续传。一个最小的Routine Load创建语句长这样CREATE ROUTINE LOAD dw.test_order_load ON dw_order COLUMNS(order_id, order_amt, create_time) PROPERTIES ( desired_concurrent_number 3, max_batch_interval 20, format json ) FROM KAFKA ( kafka_broker_list 10.0.0.41:9092,10.0.0.42:9092, kafka_topic ods_order, kafka_partitions 0,1,2, kafka_offsets OFFSET_BEGINNING );这里有几个容易踩的坑formatjson时Doris默认要求JSON key与表字段名一致。如果不一致需要额外配置jsonpaths和strip_outer_array否则导入出去的数据全是NULL。Routine Load的并发数不是越大越好。它每个并发都会占用一个BE的CPU和内存和查询争抢资源。通常从3开始调观察BE的CPU使用率再逐步增加。Kafka消息积压不会自动触发扩容你需要根据消费延迟手动调整desired_concurrent_number或者增加Kafka分区数。Canal到Kafka这一段推荐在Canal的instance.properties里把canal.instance.filter.black.regex配置好只同步需要的表避免把MySQL里所有表变更都无脑丢进Kafka徒增Doris消费压力。5.3 用Doris替代部分ES场景的取舍热搜词里有doris替换es这个趋势在2024年之后很明显。核心原因是Doris 2.0开始支持倒排索引和全文检索NGram、不区分大小写等对于部分以前必须用ES做全文检索的业务Doris可以直接扛下来。但替换不是无条件的建议用一张表判断场景是否适合Doris说明日志类数据全文检索适合日志量大、冷热分层明显Doris的分区分桶天然合适订单类精确查询聚合统计非常合适Doris的MPP架构在聚合场景碾压ES复杂相关性打分排序不适合ES的BM25相关性评分是强项Doris目前还是弱项高并发单条点查5ms不适合Doris不是为高并发点查设计的这类需求更适合HBase或Redis索引更新极其频繁秒级需谨慎Doris的索引更新依赖数据导入不适合随机高频单条更新我的建议是把Doris定位成能加速分析的ES替代品而不是ES的完全替代品。保留ES承担那一部分搜索体验要求极高的业务把日志分析和汇总统计类的需求逐步迁到Doris两个系统并存并不丢人架构上反而更稳。5.4 Prometheus监控与告警接入Doris的运维离不开监控。官方提供了doris-exporter用来从Doris的SHOW BACKENDS、SHOW FRONTENDS以及系统表information_schema抓取指标再暴露给Prometheus。在Prometheus的prometheus.yml里加上JOB配置- job_name: doris-exporter static_configs: - targets: [10.0.0.50:8080]关键监控指标建议盯这几个doris_be_cpu_idleBE的CPU空闲率长期低于20%说明资源吃紧。doris_be_disk_capacity_total_bytes和doris_be_disk_capacity_free_bytes磁盘剩余量这里我吃过亏BE磁盘写满后表现为导入大面积失败但进程还是活着。doris_fe_meta_log_countFE元数据日志数量积压过多说明checkpoint异常。doris_be_mem_used_bytesBE内存使用量配合容器或物理机的总内存看接近上限时优先查大查询和导入任务。告警规则不用贪多三五个核心指标足矣。我自己的规则是BE存活状态发生变化立即告警FE没有主节点且持续超过3分钟告警磁盘剩余空间低于15%告警ZK的zk_avg_latency超过50ms告警。最后一条特别重要因为ZK的延迟通常先于Doris的显性故障出现提前告警能让你在业务受损之前介入。个人经验补充把ZK当一等公民对待多聊一句。因为踩过ZK假死的坑我现在对Doris集群所有节点的绿灯检测都加了ZK这个维度。巡检脚本里最常跑的就是一句for zk in 10.0.0.11 10.0.0.12 10.0.0.13; do printf %s: $zk echo mntr | nc $zk 2181 | grep zk_server_state || echo DEAD done在真实生产里分布式集群的故障从来不是某个组件崩了这么简单而是上下游组件之间的协作状态先出了问题然后才表现成崩了。Zookeeper和Doris的集成往深了说就是一场关于谁能当主、谁还活着、谁该被剔除的持续协商。把这条协商链路的每个环节都摸透了Doris集群的很多诡异故障你都不用重启就能在十分钟内给出判断。