生产级Doris集群用Docker-Compose部署算是我近几年在大数据基建里折腾得比较频繁的一件事儿。三节点起步、配置落盘、故障转移验证整套流程看起来简单但真正要跑到生产标准坑其实不少。这篇文章主要围绕用Docker-Compose三节点部署生产级Doris集群这个主题从配置文件编写、服务编排、元数据初始化到故障转移的链路验证尽量把每一步的原理和实操细节讲清楚。无论你是正在调研选型的技术负责人还是自己动手搭环境的运维工程师都能从这里找到可以直接抄作业的东西。1. 整体设计与思路拆解1.1 为什么选择Docker-Compose而不是裸机部署先聊一个基础问题生产级Doris集群为什么要用Docker-Compose很多人一听生产环境上容器就觉得不靠谱实际上Doris官方对Docker部署的定位早就过了“测试玩具”的阶段。Doris的FE和BE都是无状态化设计外加元数据持久化这意味着容器可以随便重启、迁移只要把元数据目录和存储目录挂载到宿主机容器本质上就是个执行环境。Docker-Compose的价值在于编排。三节点集群要保证FE和BE的网络互通、端口映射、目录挂载、启动顺序用一条docker-compose up -d全部搞定比手写几十行shell脚本维护起来清爽得多。而且Compose文件本身就是“基础设施即代码”无论是新环境复现、灾备演练还是集群扩容改几行配置直接拉起来就行。但我必须泼一盆冷水Docker-Compose适合的是中小规模集群、跨环境一致性要求高的场景。如果你要跑几百台BE的超级集群Kubernetes或者裸机部署是更合理的路径。三节点起步、几十TB以内的数据量Compose完全够用。1.2 三节点集群的架构决策FE与BE的混合部署方案三节点集群怎么分配FE和BE角色是第一个决策点。我见过两种方案第一种是2个FE Follower 1个FE Observer3个BE全部独立部署在同样的三台机器上。这种方案要求每台机器上既跑一个FE进程又跑一个BE进程。第二种是单纯的3个BE 单FE或者2个FE 3个BE但混合部署。从资源角度讲混合部署更省机器但运维时要特别注意CPU和内存的竞争尤其是BE的BRPC线程池和FE的JVM堆之间互相抢内存。我推荐的组合是三台机器上各部署一个FE进程和一个BE进程FE角色规划为2个Follower加1个Observer。为什么需要ObserverFollower参与选举Observer只同步元数据但不参与选举。当两个Follower中的leader宕机时另一个Follower可以快速接管。而Observer在扩容时承担读负载不会增加选举压力。这样既保证了高可用又从设计上限制了脑裂风险。1.3 资源规划与生产级门槛生产级这个概念很多时候不是由软件功能决定的而是由资源的合理规划决定的。Doris集群在三节点规模下我一般按以下标准去定容量CPUFE要求4核起步BE建议8核起步。三节点混部时单机至少12核。内存FE的JVM堆建议16GB以内默认4GB也行但生产环境我一般给8GB到16GB。BE的内存大头是MemTable和PageCache建议32GB起步。混部时单机内存建议64GB。磁盘FE元数据目录建议用SSD因为每一次DDL和事务提交都会刷EditLogBE存储目录用机械盘可以但建议多做几个路径挂载分散IO压力。从实际项目经验看三节点集群扛住日均千万级实时写入、数百GB查询扫描资源规划到上述水位是安全的。如果把FE和BE拆到独立资源上那单机部署本身就是一种生产级容器编排的收益更多体现在环境一致性上。2. 镜像选型与自定义配置2.1 Doris版本选择与Docker镜像细节Doris的版本迭代速度比较快2.1.x是目前建议优先考虑的生产稳定版本特性完整且社区反馈的坑相对少。镜像方面Doris官方在Docker Hub上维护了apache/doris仓库同时提供了两个关键标签apache/doris:2.1.x-be-x.yBE镜像apache/doris:2.1.x-fe-x.yFE镜像这里有一个非常容易踩坑的点官方FE和BE镜像是分开的不像某些数据库提供一个all-in-one镜像。所以在docker-compose.yml里要分别写两个服务FE用FE镜像BE用BE镜像不能搞混。另一个坑是镜像版本里FE默认启动内存参数是-Xmx4g如果你宿主机内存足够但未修改Doris也跑得起来但一旦查询并发上来GC就会很频繁。所以我习惯在环境变量中覆盖Java堆大小。通过Compose的environment段控制JAVA_OPTS而不是进去改配置。2.2 自定义fe.conf与be.conf的正确写法Doris在容器中启动时会读取/opt/apache-doris/fe/conf/fe.conf和/opt/apache-doris/be/conf/be.conf。官方镜像里这两个配置文件是预置的直接改镜像内的文件不现实我推荐用挂载文件的方式将宿主机上的配置文件覆盖到容器内路径。以fe.conf为例生产环境必须关注这几个关键项# 指定FE集群角色 role FOLLOWER # 元数据目录 meta_dir /opt/apache-doris/fe/doris-meta # JVM参数 JAVA_OPTS -Xmx16384m -Xms16384m -XX:UseG1GC # 监听端口 http_port 8030 rpc_port 9020 query_port 9030 edit_log_port 9010其中role的取值有两种FOLLOWER和OBSERVER。第一次启动集群时第一个FE必须指定为FOLLOWER这是集群的“种子节点”。后续的FE如果是FOLLOWER或OBSERVER需要用MySQL协议连接到集群执行ALTER SYSTEM ADD FOLLOWER或ALTER SYSTEM ADD OBSERVER来注册。be.conf的配置也有关键点# BE存储路径 storage_root_path /opt/apache-doris/be/storage # 系统表空间 sys_log_dir /opt/apache-doris/be/log # BE端口 be_port 9060 webserver_port 8040 heartbeat_service_port 9050 brpc_port 8060BE的storage_root_path支持配置多个路径用分号隔开。如果机器上有多个数据盘比如/data1;/data2;/data3Doris会自动做数据均衡这比单盘抗风险能力强很多。2.3 环境变量与Compose文件编写Docker-Compose文件是整个部署的核心。在一遍遍优化之后我形成了下面这个模板三台机器之间唯一变化的是FE_SERVER的IP地址和当前节点的角色version: 3.8 services: doris-fe: image: apache/doris:2.1.5-fe container_name: doris-fe-1 hostname: doris-fe-1 environment: - FE_ROLEFOLLOWER - FE_SERVERSfe1:10.0.0.11:9010,fe2:10.0.0.12:9010,fe3:10.0.0.13:9010 - FE_ID1 - JAVA_OPTS-Xmx16384m -Xms16384m ports: - 8030:8030 - 9020:9020 - 9030:9030 - 9010:9010 volumes: - /opt/doris/fe/conf/fe.conf:/opt/apache-doris/fe/conf/fe.conf - /opt/doris/fe/doris-meta:/opt/apache-doris/fe/doris-meta - /opt/doris/fe/log:/opt/apache-doris/fe/log networks: - doris-net restart: always doris-be: image: apache/doris:2.1.5-be container_name: doris-be-1 hostname: doris-be-1 environment: - FE_SERVERSfe1:10.0.0.11:9010,fe2:10.0.0.12:9010,fe3:10.0.0.13:9010 - BE_ADDR10.0.0.11:9050 ports: - 8040:8040 - 9060:9060 - 9050:9050 - 8060:8060 volumes: - /opt/doris/be/conf/be.conf:/opt/apache-doris/be/conf/be.conf - /opt/doris/be/storage:/opt/apache-doris/be/storage - /opt/doris/be/log:/opt/apache-doris/be/log depends_on: - doris-fe networks: - doris-net restart: always networks: doris-net: driver: bridge这里解释几个容易踩坑的点FE_SERVERS不能乱写格式必须是名字:IP:edit_log_port多个FE用逗号分隔。这个名字和IP必须和集群里其他节点能互通不能用localhost。FE_ID是一个整数用于标识FE在集群里的序号不同FE必须不同。hostname建议设置因为Doris节点之间的通信会用到hostname做解析如果让Docker随机生成容器ID当hostname集群节点互相注册时会出现身份不一致问题。BE镜像需要通过depends_on确保FE先启动但这里有个逻辑陷阱depends_on管的是容器启动顺序不是服务可用状态。FE进程起来需要时间BE启动时如果连不上FE会自动重试所以一次性执行docker-compose up -d是可以的只是在确认BE注册成功前多等一下。3. 初始化流程与集群注册3.1 启动第一个FE节点种子节点的正确姿势第一步是在第一台机器上启动FE容器。首次启动时FE会以初始化模式运行在doris-meta目录生成集群元数据。我建议单独先启动FE不要一上来就三台全部拉起来。等到第一个FE状态正常后再逐步添加其余节点。执行命令如下docker-compose up -d doris-fe查看日志确认启动是否成功docker logs -f doris-fe-1看到类似Fe server started的日志说明FE已经成功启动。此时可以通过浏览器访问http://10.0.0.11:8030看到Doris的Web UI登录界面。也可以用MySQL客户端连接验证mysql -h 10.0.0.11 -P 9030 -uroot初次连接默认无密码直接进入即可。3.2 添加第二个和第三个FE节点第一个FE启动成功后在第二台机器上修改FE_SERVERS为三节点列表但FE_ID2FE_ROLEFOLLOWER。第三台机器同理FE_ID3FE_ROLEOBSERVER也可以都配FOLLOWER但前面说了为了选举效率我建议三节点中两个FOLLOWER一个OBSERVER。不过有一个前置条件需要先在第一个FE的MySQL客户端里执行注册语句ALTER SYSTEM ADD FOLLOWER fe2:9010; ALTER SYSTEM ADD OBSERVER fe3:9010;如果不执行这条语句直接启动第二个FE它会因为无法在集群元数据中找到自己而初始化失败。这是一条最容易忽视的步骤也是网上很多部署教程里让人反复折腾的根源。执行注册后再启动第二、三个FE容器它们会通过edit log从第一个FE同步元数据然后自动加入集群。此时再次用SHOW FRONTENDS查看状态SHOW FRONTENDS;正常情况下能看到三行记录其中第一个FE的Alive列显示true其余FE的Alive也依次变为true。3.3 注册BE节点到集群BE节点的注册方式类似但不是自动发现的。BE启动后不会自动加入FE集群需要在FE的MySQL客户端执行ALTER SYSTEM ADD BACKEND 10.0.0.11:9050; ALTER SYSTEM ADD BACKEND 10.0.0.12:9050; ALTER SYSTEM ADD BACKEND 10.0.0.13:9050;注意这里的地址是BE的IP:heartbeat_service_port不是be.conf里的be_port。heartbeat_service_port默认是9050用于FE向BE发送心跳和收集状态。执行后用SHOW BACKENDS验证SHOW BACKENDS;重点看Alive字段是否为true以及TabletNum是否为0。刚加入时TabletNum为0Doris会自动触发数据均衡把已有表的数据副本分布到新建的BE节点上。这个过程是异步的可能需要几分钟到几小时取决于数据量。这里补充一个实践细节加入BE节点前我习惯先用SHOW BACKENDS看一眼现有BE列表确认没有同名或同IP的废弃节点残留。旧节点如果还挂在列表里但Alivefalse新节点注册时可能出现资源冲突影响数据副本的分配。4. 故障转移验证与高可用检查4.1 模拟FE宕机的故障演练集群部署完成后不做故障演练等于白搭。我通常会做三个维度的故障模拟FE进程宕机、容器整个被kill、BE节点失联。先做最温和的停掉第二个FE的容器。docker stop doris-fe-2观察整个集群的服务连续性。此时第一个FEFollower应该自动接管leader角色集群的元数据服务对外保持可用。用MySQL客户端分别连接到第一个FE的9030端口和第三个FE的9030端口执行简单查询SHOW FRONTENDS; SELECT * FROM information_schema.tables LIMIT 1;如果都能正常返回说明FE高可用生效。核心原理是FE之间通过Quorum机制选举leaderFollower和Observer同步元数据时用的是多数派提交所以即使一个Follower宕机只要还有两个节点存活Quorum就成立集群不会停止服务。再模拟极端情况停掉两个FE只留一个Observer存活。docker stop doris-fe-1 doris-fe-2这时候用MySQL客户端连接第三个FE的9030端口查询会立刻报错或者连接被拒绝。原因很简单Observer不参与选举集群中没有可用的Followerkdc无法形成Quorum。这个结果符合预期说明三节点FE规划中Observer不是用来扛故障的而是用来分担元数据读取压力的。4.2 恢复后元数据自动同步验证故障演练的下一步是恢复节点并验证元数据同步。把之前停掉的FE容器重新启动docker compose up -d doris-fe-1 doris-fe-2启动后等待一两分钟再次执行SHOW FRONTENDS。正常情况下恢复的FE的Alive会自动变为true角色保持不变。此时可以顺手验证一次元数据操作CREATE DATABASE IF NOT EXISTS test; CREATE TABLE IF NOT EXISTS test.sample ( id INT, name VARCHAR(50), ts DATETIME ) DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 3 PROPERTIES (replication_num 2);建表成功后从另一个FE查询这张表确认表结构在所有FE上可见。这一步验证的是FE之间的元数据同步链路也就是EditLog的复制和回放没问题就说明FE集群的高可用机制运转正常。4.3 BE节点失效与数据副本重构接着做BE节点的故障模拟。在实际生产环境里BE故障比FE故障更常见磁盘坏道、网络闪断、内存ECC错误都可能让BE进程挂掉。我习惯用强制kill的方式模拟docker kill doris-be-2 docker rm doris-be-2此时FE会在几秒钟内感知到该BE心跳超时将其标记为不可用。查询数据时Doris会自动切换到其他BE上的副本用户无感知。用SHOW BACKENDS确认第三台BE的Alive变成了false。为了让集群恢复完整重新创建一个新的BE容器docker-compose up -d doris-be-2BE启动后会向FE重新注册但是这里有一个容易被忽略的细节BE之前已经注册过了现在容器重建后IP如果没变FE会自动把它的状态改回Alivetrue如果IP变了老记录会一直挂着需要在FE上执行ALTER SYSTEM DROP BACKEND清理旧记录再ALTER SYSTEM ADD BACKEND添加新记录。数据副本的恢复则是异步的。Doris后台会有replica repair线程不断扫描发现有副本缺失或损坏的tablet就会自动从健康的副本复制数据。恢复过程中可以执行SHOW PROC /cluster_balance看均衡状态。4.4 从Connector层验证故障转移的最终效果前面验证的都是Doris内部的故障转移能力最后一步才是用户视角的验证外部应用连接集群时是否真的做到了高可用。如果是Java应用通过MySQL Connector/J连接Doris需要在JDBC URL里配置多个FE地址和故障转移参数。我常用的写法是jdbc:mysql:loadbalance://10.0.0.11:9030,10.0.0.12:9030,10.0.0.13:9030/doris_db?loadBalanceConnectionGroupfirstha.enableJMXtrueloadbalance模式是MySQL Connector/J自带的客户端负载均衡可以在多个FE节点之间分发连接。当某个FE宕机时连接池会自动将新连接路由到其他存活节点已有连接中断后重连也会走存活节点。这个方案不需要额外引入中间件是最轻量级的客户端高可用方案。如果应用层是Spring Boot建议在连接池配置里多做一层保障比如HikariCP的connectionTestQuery设置为SELECT 1配合minimumIdle和maximumPoolSize的合理配比可以在FE故障切换时缩短连接重建的时间。实测下来故障转移从FE宕机到应用恢复连接大约在10秒以内完成这基本能覆盖绝大多数业务场景的可用性要求。5. 常见问题与排查技巧实录5.1 FE初始化失败meta_dir非空这是一个极其经典的坑。当你第二次尝试启动一个FE节点时如果doris-meta目录里已经有了之前初始化失败留下的残留文件FE会报类似meta_dir is not empty的错误。解决思路非常简单但操作要谨慎确认该FE节点是新建的、没有保留价值的元数据直接清空对应目录再启动rm -rf /opt/doris/fe/doris-meta/* docker-compose up -d doris-fe如果这个FE节点已经运行过里面存有元数据那就不能简单删除需要从其他FE节点做全量备份恢复操作复杂度会高很多。5.2 BE注册成功但表查询失败副本数不足三个BE的集群建表时如果replication_num设置为3那么每个tablet会有3个副本。当其中一台BE挂掉或者被移除后某些tablet只剩2个副本如果FE判定当前存活副本数不足以满足查询要求查询会直接报错。排查步骤一般是SHOW TABLET FROM test.sample;查看每个tablet的ReplicaCount和Alive状态定位缺失副本的tablet。然后检查对应数据目录所在磁盘是否满了或者BE进程是否异常退出。清理空间、重启BE后等后端修复任务跑完查询一般就能恢复。5.3 内存溢出与JVM参数调整FE节点最容易出现的内存问题是JAVA_OPTS设置过小导致频繁FullGC甚至OOM。一个典型的场景是FE默认4GB堆内存在查询并发较高时JVM直接OOM然后容器由于健康检查失败被反复重启。遇到这种情况不要只盯着Doris的日志先看宿主机内存水位和容器内存限制docker stats doris-fe-1 free -g确认宿主内存充足后修改fe.conf里的JAVA_OPTS把堆内存调大。这里有个容易忽略的方面Doris FE的JVM堆默认上限是8GB如果你设了-Xmx16g需要确认系统是否支持超过8GB的DirectMemory否则可能触发别的报错。5.4 docker-compose up 启动后端口冲突三台机器上如果之前跑过其他服务4030、9030、8040这些Doris常用端口很容易被占用。启动时看到类似port is already allocated的报错先用以下命令排查ss -lntup | grep 9030如果确认端口被占用优先停掉冲突服务。如果暂时停不掉就只能改Doris的端口映射在docker-compose.yml里把宿主机端口映射换掉。但这里要提醒一句Doris集群内部节点通信使用的是容器的IP和端口不是宿主机的映射端口。如果你为了避开冲突把宿主机的9030映射改成了19030但FE之间通信仍然走容器的9030所以从外部连接时用19030集群内部不受影响。这个灵活性是容器化部署相比裸机部署的又一个优势。6. 生产化收尾建议与个人体会集群能跑了故障转移也验证过并不代表可以立即上生产。我习惯在收尾前再多做几件事。备份FE元数据是优先级最高的一项。FE的doris-meta目录是整个集群的“大脑”一旦丢失且没有备份整个集群相当于废了。即使有多个FE副本理论上可以互相恢复但多副本同时损坏的情况谁也不想遇到。我每天凌晨对其中一个FE的doris-meta做全量快照保留7天同时定期将快照拷贝到独立存储。容器日志的集中处理容易被忽略。默认情况下Docker日志散落在各台机器上排查问题时割裂感很强。我建议至少做一层日志采集将Doris的FE和BE日志统一收敛到一个日志平台。如果团队暂时没有日志平台至少把/opt/doris/fe/log和/opt/doris/be/log挂载到稳定的宿主机目录便于手动检索。关于Doris本身的版本升级容器化部署的收益很大。Doris 2.1.x内的小版本升级直接替换镜像版本再滚动重启节点即可。升级前务必备份元数据升级过程中保持FE至少一个Follower存活避免整个集群短暂不可用。实测下来两三个FE滚动升级一次总共也就几分钟停写时间。最后说一点个人体会。用Docker-Compose部署Doris最大的价值不是省了安装那几步而是让整个集群的配置、依赖、版本变成了一份可追溯、可复现的代码。遇到问题你不需要回忆当初某台机器上手动改过什么只需看Compose文件和挂载的配置文件就能恢复出一模一样的环境。生产环境的稳定性很大程度上不取决于某个高深技巧而取决于变更是否可控、运维是否有记录、故障是否能复现。容器化部署在这三件事上确实帮了大忙。