这个系列写到第6篇,终于要聊集群安装部署了。前几篇我们把Flink的架构、组件分工、状态与检查点机制、流处理API这些内容都过了一遍,但说句实在话,如果只是在单机上跑跑Demo,很多概念是体会不到实际价值的。只有当你真正把多台机器组起来跑集群,才会明白为什么Flink要有JobManager和TaskManager之分、为什么需要配置高可用、为什么一个看似合理的配置在真实负载下会突然崩掉。
这篇文章的核心是一件事:从零安装部署一套Flink集群。默认环境是Linux物理机或虚拟机,部署模式选Standalone(独立模式),并在其基础上扩展ZooKeeper高可用。适合刚学完Flink基础、准备进入分布式环境练手的同学,也适合团队内部需要快速搭建一套测试集群的工程师参考。整个过程按实际操作顺序写,包括配置项的取舍、启动验证步骤,以及我踩过的坑,尽量做到每一步都有据可查、可以复现。
1. 部署方案选型:先想清楚再动手
1.1 Flink的三种部署模式,到底怎么选
Flink的部署模式,说白了就解决一个问题:TaskManager这些计算进程,由谁来启动、由谁来分配资源、由谁来回收。按这个维度,主流方案就三种:Standalone、YARN、Kubernetes。
Standalone模式不依赖任何外部资源管理系统,Flink自己启动并管理JobManager和TaskManager进程。它的优点是架构简单、部署路径最短,任何一台装了JDK的Linux机器都能跑;缺点是资源弹性差,TaskManager的启停都要人工干预,机器空闲时也不会自动缩容。
YARN模式把Flink的资源管理交给Hadoop YARN,提交任务时,YARN动态分配一个容器给Flink,JobManager和TaskManager都跑在YARN容器里。这个模式的好处是复用已有大数据体系,资源按需申请,队列和配额都现成;代价是的前提是你得先有一套Hadoop集群,部署和排查链路变长。
Kubernetes模式则是把Flink容器化,用K8s来调度Pod,适合云原生基础设施比较成熟的团队。它的优点是资源隔离好、弹性伸缩能力强、环境一致性高;缺点是学习成本高,Pod崩溃日志、网络模型、存储卷这些概念,不熟悉容器的话会相当痛苦。
三种模式的差异整理成表格更直观:
| 维度 | Standalone | YARN | Kubernetes |
|---|---|---|---|
| 资源管理 | Flink自管 | 交给YARN | 交给K8s |
| 部署复杂度 | 低 | 中 | 高 |
| 资源弹性 | 弱 | 强 | 强 |
| 学习成本 | 低 | 中 | 高 |
| 适用场景 | 测试、小规模作业 | 已有Hadoop体系 | 容器化成熟团队 |
1.2 为什么学习阶段选Standalone
如果说后面要去生产环境用YARN或K8s,那学习阶段从Standalone切入是不是白费功夫?我个人经验是:不白费。因为Flink的核心配置项,比如JobManager地址、内存参数、Slot数量、状态后端、检查点路径、高可用配置,在不同模式下是同一套逻辑。你在一套Standalone集群上把这些参数琢磨透了,到了YARN和K8s模式下,大部分概念可以直接平移,只是“谁来启动进程”这一步变了而已。
而且Standalone能让你把注意力完全放在Flink本身。遇到问题,就是一台机器、几个进程、几份日志,排查链路非常短。直接上YARN的话,任务起不来你先得判断是Flink的问题还是YARN的问题,对新手很不友好。我当年搭建第一套集群就是从Standalone入手的,到现在我都觉得这一步走得值。
2. 集群资源规划与前置配置
2.1 节点规划:几台机器、什么角色、多大规格
部署之前先规划节点,这一步最容易被跳过,也最容易后面返工。一个Flink集群至少在逻辑上要区分两类角色:JobManager负责调度、协调、Checkpoint的全局控制,TaskManager负责任务的实际执行。在Standalone模式下,两者可以部署在同一台机器上,但生产环境强烈建议分机部署。
以一套四节点测试集群为例,规划如下:
| 节点 | 角色 | 规格参考 | 备注 |
|---|---|---|---|
| node-01 | JobManager(主) + TaskManager | 4核8G | 学习环境可混合部署 |
| node-02 | JobManager(备) + TaskManager | 4核8G | 高可用章节使用 |
| node-03 | TaskManager + ZooKeeper | 4核8G | ZK为HA服务 |
| node-04 | TaskManager + ZooKeeper | 4核8G | ZK为HA服务 |
如果你只是想先跑通基础Standalone,最低可以缩到两台机器甚至单机。但既然标题是集群安装部署,我建议至少三台,这样能体会到主备切换和跨节点传输的真实效果。生产环境的最低配置,我后面在巡检章节再展开讲。
2.2 主机环境准备:JDK、SSH免密与文件句柄
先把每台机器的系统环境抹平。Flink 1.17左右的主线版本,JDK 8和JDK 11都支持,建议统一用JDK 8或11,别混用。安装完JDK后,在每台机器的/etc/hosts里把节点IP和主机名对应关系写清楚,否则Flink节点间用主机名通信时会解析失败,任务跑着跑着就断连。
SSH免密必须配好。Flink的start-cluster.sh脚本在启动集群时,会通过SSH依次登录到masters和workers文件里列出的所有主机,执行远程启动命令。如果不配免密,脚本会一个节点一个节点地提示输入密码,实际体验非常痛苦。配置方式很常规:在node-01上生成密钥对,把公钥分发到所有节点,验证ssh node-03 hostname能直接返回而不询问密码就行。
文件句柄限制也值得提前处理。流任务持续跑一天,每张表、每份状态后端、每路网络连接都在消耗句柄,一旦超过系统默认的1024,任务会莫名其妙地报“Too many open files”。建议在/etc/security/limits.conf里将nofile调到65535以上,同时把nproc限制也放开。
2.3 网络端口规划:哪些端口必须放通
Flink集群跑起来之后,涉及的端口不少,而且不同角色之间的通信端口是分开的。部署前把端口规划好,一方面是为了防火墙放行,另一方面是避免和其他中间件冲突。
以我常用的默认端口为例:
| 端口 | 用途 |
|---|---|
| 8081 | JobManager的Web UI与REST接口 |
| 6123 | JobManager RPC端口 |
| 6124 | TaskManager RPC端口 |
| 6125 | Blob服务端口,传输用户Jar包等文件 |
| 2181 | ZooKeeper客户端端口(高可用场景) |
如果你用的是云主机或者跨机房部署,安全组和防火墙一定要把这些端口放通。另外注意,TaskManager在启动时会动态分配网络通信端口,默认端口范围较宽,如果想收紧到某个段,可以通过taskmanager.bind.host和taskmanager.network.bind-port来控制,比如固定为6124-6130,这样防火墙规则就更好写。
3. Flink集群安装部署实操全记录
3.1 安装包下载与版本校验
进入正题。先去Flink官网或者镜像站下载二进制发行包,文件名类似flink-1.17.2-bin-scala_2.12.tgz。注意版本号后面的scala_2.12,这个代表内置的Scala版本。如果你后续要跑Scala API作业,建议与集群发行包的Scala版本保持一致,否则可能出现兼容性告警。
下载完先校验一下完整性。发行包提供的有SHA512校验和,命令很简单:
sha512sum flink-1.17.2-bin-scala_2.12.tgz把输出的哈希值和官网对照,一致再进行下一步。这一步看似多余,但真实环境里因为下载源代理导致包损坏的情况我遇到过不止一次,解压到一半报错才发现就晚了。
解压并规划安装目录:
tar -zxvf flink-1.17.2-bin-scala_2.12.tgz mkdir -p /opt/flink mv flink-1.17.2 /opt/flink/flink建议每台机器都放在同一路径下,后面脚本和配置会更省心。解压后目录里的关键子目录包括:bin(启动停止脚本)、conf(所有配置文件)、lib(默认依赖与用户扩展包)、log(运行日志)、plugins(可插拔模块)。先对这几个目录混个脸熟,排查问题时都是直接和它们打交道。
3.2 flink-conf.yaml核心配置逐项解析
所有核心配置都在conf/flink-conf.yaml文件里。这个文件是Flink集群的“总开关”,改错了轻则任务跑得慢,重则集群根本起不来。
我以测试集群为例,给出一个可以直接上手的配置:
# JobManager地址,写每台机器都能解析到的主机名或IP jobmanager.rpc.address: node-01 # 各进程总内存(包含堆内外、元空间、JVM开销等) jobmanager.memory.process.size: 2048m taskmanager.memory.process.size: 4096m # 每个TaskManager可以提供几个Slot,一般取CPU核数 taskmanager.numberOfTaskSlots: 2 # 默认并行度,提交任务时未指定则按此值执行 parallelism.default: 2 # 状态后端和检查点目录 state.backend: hashmap state.checkpoints.dir: file:///data/flink/checkpoints # WEB UI地址 rest.address: node-01 rest.port: 8081 # 重启策略,快速恢复实验用 restart-strategy: fixed-delay restart-strategy.fixed-delay.attempts: 3 restart-strategy.fixed-delay.delay: 10 s这里有几个关键点需要理解。
第一,jobmanager.rpc.address不能乱填。在Standalone模式下,所有TaskManager启动时都会读取这个地址去注册自己,如果你的配置和实际JobManager所在机器不一致,TaskManager永远注册不上,集群里会看到一堆“TaskManager not reachable”的错误。
第二,内存参数别拍脑袋写。taskmanager.memory.process.size表示一个TaskManager进程的总内存预算,Flink会在这个预算内自动划分堆内存、托管内存、网络缓冲和JVM开销。4G内存给到一个8G物理机上是合理的,因为系统本身、ZooKeeper、日志等还要留余量。如果你在2G的小机器上强行分配4G给TaskManager,进程会直接因为内存分配失败而退出。
第三,taskmanager.numberOfTaskSlots和并行度的关系要搞清楚。一个TaskManager能同时运行的任务线程数等于Slot数,整个集群的总Slot数就是所有TaskManager的Slot之和。你的作业并行度如果超过总Slot数,任务会一直处于Pending状态,不会报错但永远不跑。并行度不是越大越好,它受限于Slot总容量,而Slot容量又受限于CPU和内存。
3.3 masters与workers文件配置
conf/masters和conf/workers决定集群启动时会在哪些机器上拉起什么进程。masters文件每行一个JobManager地址,格式是“主机名:端口”;workers文件每行一个TaskManager地址。
基础部署时,masters文件只写一个节点:
node-01:8081workers文件写所有充当TaskManager的节点:
node-01 node-02 node-03 node-04这里有个容易踩坑的细节:masters文件里写的端口是Web UI的端口,不是RPC端口。也就是说,Flink脚本会根据你给定的端口启动对应的REST服务。而jobmanager.rpc.port是在flink-conf.yaml里单独配置的,默认是6123,二者不要混淆。
当然后面做高可用时,masters文件会改成两个节点,这个我放到下一节专门讲。
3.4 集群启动、验证与第一个测试任务
配置完成,开始启动。在node-01上执行:
bin/start-cluster.sh脚本会依次读取masters和workers文件,通过SSH拉起JobManager和TaskManager进程。启动完成后,第一件事是验证进程是否存在,不要直接看“Connect to ... failed”这种输出就以为成功了。在所有节点上用jps查看Java进程,正常情况下应该能看到StandaloneSessionClusterEntrypoint(JobManager进程名)和TaskManagerExecutor(TaskManager进程名)。
如果进程没起来,去log目录看对应日志。JobManager的日志前缀类似flink-*-standalonesession-*-node-01.log,TaskManager的日志前缀类似flink-*-taskexecutor-*-node-03.log。绝大多数启动失败问题,日志里都有明确原因,比对着屏幕猜要高效得多。
进程都在了,再验证Web UI。浏览器访问http://node-01:8081,能看到集群总览页面,左侧菜单能列出当前TaskManager列表和可用Slot数量,基本就说明集群处于正常状态。
接下来提交第一个测试任务。Flink发行包自带一些示例程序,位于examples/batch和examples/streaming目录下,我们用一个简单的流处理任务验证全链路:
bin/flink run -d examples/streaming/WindowJoin.jar \ --windowSize 200-d参数表示后台运行。提交成功后,控制台会打印出Job ID,类似a3b5c2f0...。再回到Web UI看Jobs页面,能看到这个任务处于RUNNING状态,并且每个并行子任务都分配到了对应的TaskManager和Slot上。到这里,一套Standalone集群就算真正跑通了。
提示:如果运行的是无界流任务,任务会一直处于RUNNING状态,这是正常的,停掉它用
bin/flink cancel <jobId>即可。
4. 高可用扩展:引入ZooKeeper实现JobManager故障转移
4.1 单JobManager是整个集群的单点风险
前面部署的集群只有一个JobManager,这在测试环境没问题,但如果你要让Flink承认真实任务,单点风险是绕不开的。JobManager挂了会发生什么?集群的调度层面会瘫痪,正在运行的任务虽然不会立刻丢数据,但新的任务无法提交,已有任务的重启恢复也无从谈起,整个集群处于“只读”状态。
Flink高可用的核心思路是引入ZooKeeper做分布式协调,部署多个JobManager,其中一个为Active(主),其余为Standby(备)。Active JobManager会把自己持有的作业元数据、状态位置信息写入ZooKeeper和共享存储;一旦宕机,ZooKeeper能感知到会话超时,Standby节点随即抢占成为新的Active,并从共享存储恢复元数据,继续接管集群。
4.2 HA模式配置步骤
HA模式需要两样基础设施:一套ZooKeeper集群和一个所有JobManager都能访问的共享文件系统。ZooKeeper最少三节点,对着官方文档装即可,标准模式就好,不需要特殊配置。共享文件系统在测试环境可以用NFS挂载,比如都挂载到/data/flink/ha;生产环境一般直接使用HDFS或者S3。
以三节点ZooKeeper为例,在flink-conf.yaml里追加或修改以下配置:
# 启用ZooKeeper高可用模式 high-availability: zookeeper # ZooKeeper集群地址 high-availability.zookeeper.quorum: zk-01:2181,zk-02:2181,zk-03:2181 # 元数据存储目录,必须所有JobManager共享可读写 high-availability.storageDir: file:///data/flink/ha # ZooKeeper根路径,多套集群可隔离 high-availability.zookeeper.path.root: /flink-ha同时,masters文件改为列出所有候选JobManager,一个节点一行:
node-01:8081 node-02:8081每个候选节点上都要放相同配置。磁盘和文件夹权限务必确保两个JobManager都能写入同一个共享目录,这是最容易被忽略的环节:如果storageDir只能被node-01写,node-02成为Active后是无法恢复状态的。
启动顺序上也有讲究:先启动ZooKeeper,再启动Flink集群。bin/start-cluster.sh会在所有masters节点拉起JobManager进程,此时它们都处于待命状态,ZooKeeper会自动指定一个为Active。
验证HA能否生效,我的做法是:提交一个开启检查点的流任务,找到Active JobManager的进程PID,直接kill -9模拟宕机。正常情况下10秒内,另一个节点的JobManager会接管,业务任务依靠检查点恢复,Web UI上能看到重启次数变成1,任务恢复RUNNING。这一步亲手操作一次,你对高可用的理解会完全不一样。
注意:测试HA时,任务必须开启Checkpoint并且配置了重启策略,否则任务不会自动恢复,只会失败结束。Checkpoint的存储位置也必须和HA配置一样选共享存储,才能跨节点恢复。
5. 常见故障与排查技巧实录
5.1 JobManager因OOM反复重启
症状是JobManager进程启动后不到几分钟就消失,日志文件末尾出现的内存溢出报告,或者干脆没有任何明显错误就退出。最常见原因是jobmanager.memory.process.size给得太小,或者机器上其他进程挤占了物理内存,导致JVM堆内存不够用。
我的排查套路是:先看free -g确认物理内存实际剩余量,再看日志里JVM配置的堆大小和进程退出码。如果是典型的OutOfMemoryError: Java heap space,先把jobmanager.memory.process.size调大,比如从1024m调到2048m。如果频繁重启还伴随GC overhead limit exceeded,说明堆过小之外还可能有频繁创建大量对象的作业在跑,需要检查作业代码的内存消耗,而不只是加内存。
这里有个经验值:JobManager进程总共至少预留1.5G到2G内存,哪怕你的任务并行度很低。因为JobManager同时还有RPC服务、Web服务、Blob服务等需要承载,这些不在堆内存里也会占去不少系统资源。
5.2 Slot总容量与并行度不匹配
症状很典型:任务提交后一直Pending,不报错不退出,Web UI上Tasks一栏显示某个或某些子任务始终处于“PENDING”状态。这个时候第一反应不是检查代码,而是去TaskManagers页面看集群当前到底有多少个Slot可用。
排查思路是这样:总Slot数 = TaskManager数量 ×taskmanager.numberOfTaskSlots。如果你的并行度是4,但集群总Slot只有2,那多出来的2个并行子任务永远等不到Slot。解决方案有三个:增加TaskManager节点、提高单节点Slot数量、降低作业并行度。三者优先级取决于资源实际情况,测试环境建议先调低并行度验证逻辑,生产环境再考虑扩容。
我见过不少新手在这里卡很久,以为并行度是个软件参数,调大就能提高性能,结果把集群资源直接打满甚至打崩。并行度本质上是“占坑位”,坑不够,任务就只能排队。
5.3 提交任务时连接超时或Connection refused
执行bin/flink run时,客户端要去连JobManager的REST端口,默认是8081。多数情况下,报Connection refused是因为jobmanager.rpc.address配置指向了错误的主机,或者rest.address没配置导致客户端连到了错误的地址。还有一种情况是防火墙未放行端口,在云主机上特别常见。
定位时先确认集群进程本身是健康的,然后从提交任务的机器上用telnet node-01 8081探测端口连通性。如果端口不通,检查防火墙:
systemctl status firewalld firewall-cmd --zone=public --list-ports如果网络没问题,重点看flink-conf.yaml里的jobmanager.rpc.address和rest.address,确保它们和masters文件里写的节点一致。在HA模式下,这两个地址建议都写成当前Active节点对应的主机名,或者使用负载均衡地址,否则客户端会只连一个固定的节点,主备切换后就会出现连不上。
5.4 Checkpoint频繁失败的隐性原因
Checkpoint失败在任务日志里通常表现为“Checkpoint expired before completing”或“Checkpoint ... has been discarded”。很多人第一反应是调大超时时间,但根因往往是存储目录不可写、空间不足或者跨机房延迟过高。
如果你在集群模式下把state.checkpoints.dir配成了本地路径,比如file:///data/flink/checkpoints,而TaskManager分布在不同机器上,那么每台机器上的本地路径并不共享,检查点元数据无法被JobManager完整读取,失败是必然的。正确做法是使用HDFS、S3或NFS路径。测试环境临时用NFS最省事。
另一个隐性原因是磁盘容量。RocksDB状态后端会把状态数据落盘,本地磁盘写满后任务不会立刻报错,而是Checkpoint伙同TM之间的通信开始超时,看起来像是网络问题。遇到莫名奇妙的Checkpoint失败,记得先查每台机器的df -h,把磁盘水位列出来,超八成就要考虑清理和扩容了。
5.5 故障排查速查表
| 症状 | 优先检查项 | 常用修复 |
|---|---|---|
| 进程启动即退出 | 日志、物理内存余量 | 调大对应进程内存参数 |
| 任务一直Pending | 集群总Slot数 | 扩容节点或降低并行度 |
| 提交任务Connection refused | port连通性、配置地址 | 修正rpc/rest地址、放通防火墙 |
| Checkpoint过期 | 存储目录、磁盘空间、网络延迟 | 换共享存储、清理磁盘、调大超时 |
| TaskManager注册不上 | JobManager地址、hosts解析 | 统一hosts、检查网络 |
6. 部署后的日常巡检与基础调优经验
6.1 集群健康度巡检清单
集群部署完成只是开始,真正能跑一个月不掉链子,靠的是日常巡检。我的习惯是每天固定看三样东西。
第一看进程和日志。在所有节点执行jps确认进程都在,再在log目录下用grep -i error扫一遍最近日志,重点看有没有周期性出现的Exception。很多问题在变成严重故障之前,日志里已经警告过很多天了,只是没人看。
第二看Web UI上的TaskManagers页面。这里能看到每个TaskManager的堆内存使用量、堆外内存、网络缓冲、磁盘占用。如果某个节点内存曲线持续走高且不回落,很可能是有状态任务在膨胀,趁早定位比等到OOM再恢复从容得多。
第三看Checkpoint完成情况。在任务详情页的“Checkpoints”标签里,连续一段时间内,如果“Failed”数量明显增多,或者最近一次检查点距当前时间过长,那基本可以判定存储或网络出了问题,按上一章的排查思路处理。
6.2 几个值得动手调的基础参数
部署阶段除了前面那些必配项,还有几个参数我建议你在跑真实作业前就想好。
taskmanager.memory.managed.fraction默认是0.4,代表总内存里划给状态后端托管内存的比例。对于有状态任务,比如用RocksDB做状态后端,这个比例要适当调高,比如0.5到0.6;对于无状态任务,保留默认值即可,调高了反而浪费。
taskmanager.memory.network.min和taskmanager.memory.network.max控制网络缓冲内存。任务数据Shuffle较大时,网络缓冲不够会表现为吞吐上不去、背压频发。我一般会先改成最小值64MB、最大值256MB,再根据监控曲线微调。
还有一个容易忽略的配置是jobmanager.execution.failover-strategy,默认是region,按区域失败恢复,这个策略在生产环境表现不错,别轻易改成全图重启,否则一个子任务失败整张图全部重跑,资源浪费很大。
6.3 部署阶段不该做的几件事
说是经验,不如说是反面教训。第一,不要在lib目录里随便丢依赖包。Flink的lib目录是所有作业进程共享的,放一个版本不对的冲突包,可能让所有作业一起遭殃。自定义依赖最好通过--classpath或打包到作业胖Jar里,而不是直接丢到lib下。
第二,不要用latest这种滚动版本。Flink的主线版本升级节奏不慢,社区的特性变更也频繁。给团队搭建集群,用稳定的LTS版本,至少在一个季度内不要动版本,避免把时间花在无意义的兼容性排查上。
第三,不要在主备混合部署的机器上把所有组件一股脑塞进去。有的同学手里机器有限,把JobManager、TaskManager、ZooKeeper、NFS全放一台机器,跑Demo可能没问题,但一发生资源争抢,性能问题会掩盖真正需要定位的逻辑问题,排查体验非常折磨。实在机器少,至少保证JobManager和TaskManager分开部署。
写到这里,基本把Standalone集群从选型、规划、部署、验证、HA到排查的完整链路都过了一遍。这套流程我在测试环境重复了很多次,每次踩的坑都不太一样,但核心逻辑是稳定不变的:先搞懂每个参数背后的资源模型,再动配置;先确认进程和网络基本健康,再深入业务级别的问题。安装部署这件事,真正花时间的不是敲那几行命令,而是建立对集群运行机制的整体感知。你把这套配置在你自己的环境里完整跑通一遍,后面再切YARN或者K8s,心里就很有底了。