最近我一直在折腾实时计算这边的底层引擎,把线上的Flink集群整体往1.20版本迁,顺便在机房里用三台物理机从头搭了一套独立的Flink集群。整个过程说难不难,但坑确实不少,尤其是内存参数、网络绑定、连接器加载这几个环节,资料里写得模棱两可的地方,实际操作起来特别容易翻车。这篇文章我把完整的部署步骤、配置项解释和排错过程都整理了出来,不管你是刚接触Flink准备搭第一套测试集群,还是已经在用旧版本想升级到1.20,应该都能直接用得上。
1. 部署方式选型与整体设计
1.1 为什么选Standalone集群模式
Flink真正跑起来有好几种部署形态:可以直接用Standalone独立集群,也可以挂在YARN或者Kubernetes上,还能让Flink以嵌入式的方式集成到其他平台里。我这次选Standalone,主要有两个实际考量。第一,这套集群的规模不算大,三台机器、几十个并发任务,独立模式完全够用,没有必要引入额外的资源调度层。第二,团队里没有专职的K8s运维,YARN集群的资源也被其他计算引擎占着,与其在资源管理器上纠缠,不如让Flink作为独立的计算集群跑在专用节点上,部署链路和排障链路都简单很多。
如果你的团队已经有成熟的YARN或者K8s底座,那优先用Flink on YARN或者Flink Kubernetes Operator肯定是更好的选择,资源利用率高、扩缩容也方便。但如果是中小团队、独立的测试环境,或者你需要对集群做很细粒度的实验和控制,Standalone反而是最省心的起点。它本质上就是一台JobManager加多台TaskManager,配置清晰,所有问题都能在Flink自身范围内解决,不需要去分析资源管理器的调度日志。
1.2 节点规划与角色划分
我用了三台物理机,规划如下:一台作为JobManager节点,另外两台作为TaskManager节点。机器配置是16核64G内存,磁盘用的SSD。JobManager本身内存消耗不大,但我不建议让JobManager和TaskManager复用同一台机器,尤其是生产环境,避免某个大作业发生反压时,主节点被波及导致整个集群失联。
这里还要提一个容易被忽略的点:节点之间的内网延迟一定要低,尽量走千兆甚至万兆内网。因为TaskManager之间的数据shuffle非常吃网络,网络带宽不够,任务跑起来后会出现持续的反压和背压告警,表面看是资源不够,实际是网络链路兜不住。我这次部署之前专门检查了交换机端口和网卡协商速率,三台机器都确认是万兆互联,后续跑shuffle密集型的作业才没有出现瓶颈。
Flink的Standalone集群其实不强制要求节点间时钟严格同步,但我仍然在所有节点上配置了chrony时间同步。原因很简单,日志时间线一致对于排查分布式问题太重要了。之前有一次任务莫名失败,两个TaskManager记录的日志时间差了十几秒,让我误以为是执行顺序问题,查了半天,最后才发现是时钟偏移。同步之后,很多问题一眼就能看出先后顺序。
1.3 为什么是Flink 1.20
版本上我选了1.20,没有追最新的1.21。相比之前的1.18和1.19,1.20在状态管理、批流一体、内存管理上都有不少改进,尤其是对Paimon的集成更稳定了,CDC生态的适配也做得比较全。1.21虽然发布更晚,但刚发布的大版本总有一个稳定性观察期,生产环境没必要抢这个新。1.20正好卡在一个“新特性够用、Bug修得差不多”的位置,属于当前阶段部署新集群比较稳妥的选择。
另外提醒一句,如果你计划在作业里大量使用Flink CDC,一定要把CDC连接器的版本和Flink版本做匹配。CDC 3.x版本对Flink 1.20的支持比较完善,可以直接用。还有JDK版本也要提前确认:Flink 1.20支持Java 8、11、17,但官方从这代开始逐渐收窄对Java 8的维护力度,我建议直接用Java 11,不要再用JDK 8。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 部署前环境准备
2.1 JDK版本选择与安装
这里我踩过一次坑。最开始我图省事,直接用的系统自带的OpenJDK 8,结果发现部分JMX监控参数在Java 8上不生效,导致Ganglia监控一直抓不到GC数据。后面统一装了OpenJDK 11,问题就消失了,也没再折腾。
建议在所有节点上安装同一个版本的JDK,并且一定要设置JAVA_HOME环境变量。不要图方便用系统自带的不同版本混跑,因为JobManager和TaskManager之间的RPC通信对序列化行为很敏感,JDK版本不一致可能出现特别诡异的问题。安装命令很简单,以Ubuntu和CentOS为例:
bash复制# Ubuntu / Debian
apt install -y openjdk-11-jdk
# CentOS 7 / Rocky Linux
yum install -y java-11-openjdk-devel
装完检查一下:java -version,确认是11.x。然后编辑/etc/profile,追加:
bash复制export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 # 路径以实际为准
export PATH=$PATH:$JAVA_HOME/bin
2.2 系统参数与网络约定
Flink集群对操作系统有一些基础要求,建议在部署前一次性做完,不要在集群跑起来后再补。
文件句柄数和进程数限制要调大,编辑/etc/security/limits.conf:
bash复制* soft nofile 655350
* hard nofile 655350
* soft nproc 655350
* hard nproc 655350
这个参数对高并发连接和大量文件读写很重要,默认的1024肯定不够用。
网络端口方面需要提前梳理清楚。Flink用到的主要端口:8081是Web UI和REST端口,6123是JobManager的RPC端口,6124是TaskManager的RPC端口,6125是TaskManager数据交换端口。如果机器上有防火墙策略,需要提前放行这些端口,或者干脆在受控内网里关掉防火墙。
特别提醒一下IPv6环境的问题。Flink默认绑定地址是0.0.0.0,但如果你所在的机器同时启用了IPv4和IPv6协议栈,Flink在解析本机地址时可能会选中IPv6地址,而Java进程默认又只使用IPv4栈,两边一交错,就会出现一个让人头痛的现象:JobManager已经启动了,Web UI也能正常打开,但TaskManager怎么都注册不上。我在机房那台机器上就遇到过这个情况,后来在flink-conf.yaml里显式指定了jobmanager.bind-host和taskmanager.bind-host,并且在JVM参数里加了-Djava.net.preferIPv4Stack=true才解决。如果你部署的机器只有单一网络栈,大概率永远不会遇到这个问题,但一旦踩中,排查成本极高,所以这里专门写出来,希望能帮大家绕开。
2.3 安装包与目录规划
从Apache官网下载flink-1.20.0-bin-scala_2.12.tgz,解压到统一的安装目录。我习惯放在/data/flink/下:
bash复制mkdir -p /data/flink
tar -zxvf flink-1.20.0-bin-scala_2.12.tgz -C /data/flink
ln -s /data/flink/flink-1.20.0 /data/flink/flink
之后配置里引用软链路径,升级版本时只需要把软链指向新目录,不用改动作业脚本里的路径。这个习惯是我多年维护服务集群时养成的,看起来简单,滚动升级时真的能省不少事。
另外强烈建议配置FLINK_HOME环境变量,并把$FLINK_HOME/bin加入PATH,这样后续执行flink命令、start-cluster.sh脚本都会方便很多。所有节点都要做同样的目录规划和环境变量配置。
2.4 SSH免密与时间同步
Standalone模式启动TaskManager时,JobManager节点要通过SSH远程执行启动脚本,所以必须配置JobManager节点到所有TaskManager节点的SSH免密登录。具体操作:
bash复制# 在JobManager节点生成密钥(如果还没有)
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
# 将公钥分发到所有TaskManager节点
ssh-copy-id -i ~/.ssh/id_rsa.pub user@flink-worker-01
ssh-copy-id -i ~/.ssh/id_rsa.pub user@flink-worker-02
分发完成后一定要测试,不要直接启动集群。在JobManager节点依次执行ssh flink-worker-01和ssh flink-worker-02,确认不需要输密码能直接登进去。免密配置看起来简单,但经常有人漏了这一步,导致start-cluster.sh时JobManager能起,TaskManager全部失败。
时间同步我也放在部署前做。用chrony统一对内网时间源同步,配置方法不复杂,关键是每个节点的/etc/chrony.conf要指向同一个时间源,然后systemctl restart chronyd并确认chronyc tracking显示同步状态正常。这一步做完了,后续查日志、对水印、对事件时间都会非常舒服。
3. Standalone集群安装配置全流程
3.1 flink-conf.yaml核心配置逐项解读
Flink的配置文件在conf/flink-conf.yaml,这个文件是集群所有节点的共享配置,JobManager和TaskManager启动时都会读它。我贴一段我在生产环境用的关键配置,并解释每个参数为什么这么设:
yaml复制jobmanager.rpc.address: flink-master
jobmanager.bind-host: 0.0.0.0
jobmanager.memory.process.size: 2048m
taskmanager.bind-host: 0.0.0.0
taskmanager.host: flink-worker-01
taskmanager.memory.process.size: 8192m
taskmanager.numberOfTaskSlots: 4
parallelism.default: 2
rest.address: flink-master
rest.bind-address: 0.0.0.0
rest.port: 8081
几个重点我展开说一下。jobmanager.rpc.address必须填JobManager节点的主机名或IP,TaskManager启动时会拿着这个地址去找JobManager注册,填错就直接注册失败。jobmanager.bind-host和taskmanager.bind-host建议都设为0.0.0.0,否则进程只监听单个网卡,跨网段的节点连不上。
taskmanager.host这个参数需要在每台TaskManager节点上填自己的地址。如果你不填,Flink会通过InetAddress.getLocalHost()自行解析,在多网卡或DNS解析不规范的机器上,解析出来的地址可能完全不对,然后就会出现“TaskManager进程活着,但Web UI上就是找不到它”的情况。
这里给一个参数速查表,方便后面配置时对照:
| 参数 | 作用 | 建议值 |
|---|---|---|
| jobmanager.rpc.address | JobManager RPC地址 | JobManager节点主机名或IP |
| jobmanager.memory.process.size | JobManager进程总内存 | 2g起步 |
| taskmanager.memory.process.size | TaskManager进程总内存 | 单机内存的60%~70% |
| taskmanager.numberOfTaskSlots | 单台TaskManager的Slot数量 | CPU核数的一半到两倍 |
| parallelism.default | 默认并行度 | 与Slot总数匹配 |
| rest.bind-address | Web UI监听地址 | 0.0.0.0 |
| io.tmp.dirs | 临时文件目录 | 空间充足的分区 |
内存参数这里我多说一句:16G物理机的Worker节点,给TaskManager进程8G-10G比较稳,不要贪心全给Flink。机器上还要跑操作系统、监控Agent、可能的日志采集组件,如果全部内存都给了Flink进程,操作系统会在内存压力大的时候直接把Flink进程OOM Kill掉。我之前就干过这种事,把16G全部划给了TaskManager,结果集群一跑大作业就有一个Worker掉线,查了好半天才发现是被操作系统杀了。
3.2 workers和masters文件配置
conf/workers文件里填写所有TaskManager节点的主机名或IP,一行一个。start-cluster.sh脚本会读取这个文件,通过SSH逐个登录到对应的节点启动TaskManager进程。这个文件里不要填JobManager节点自己,除非你确实想让JobManager也承担TaskManager的角色。
conf/masters文件是HA模式下配置JobManager节点列表用的,格式是主机名:端口,例如:
code复制flink-master-01:8081
flink-master-02:8081
如果只用单JobManager节点,可以不配置masters文件,Flink默认就在当前节点启动一个JobManager。
3.3 启动集群与第一次任务验证
配置完成后,在JobManager节点执行:
bash复制bin/start-cluster.sh
正常情况下,脚本会先在本机启动JobManager进程,然后通过SSH登录workers文件里的每一个节点启动TaskManager进程。启动后第一时间检查进程:
bash复制jps
JobManager节点上应该能看到StandaloneSessionClusterEntrypoint进程,TaskManager节点上应该能看到TaskManagerRunner进程。如果TaskManager节点上没进程,去log目录看*.log和*.out文件,绝大多数启动失败的原因都会记录在那里。
进程都起来了,接着访问http://flink-master:8081,确认Web UI的Task Managers列表里已经出现两台Worker节点,状态为Running。到这里集群本身已经通了,但还没有验证任务提交链路。我习惯先提交一个内置示例作业完整跑一遍:
bash复制# 在某个节点先启动一个数据源
nc -lk 9999
# 提交SocketWindowWordCount示例作业
bin/flink run examples/streaming/SocketWindowWordCount.jar --hostname flink-master --port 9999
向nc窗口里输入英文单词,回到Web UI就能看到作业处于Running状态,并且数据被正常处理。这一步能跑通,说明整条链路——RPC通信、数据交换、UI展示、日志汇聚——全部正常,可以放心接入真实业务作业。
3.4 高可用模式配置
单JobManager节点虽然简单,但一旦宕机,整个集群的作业提交和恢复能力就断了。生产环境我建议至少配置两节点HA。传统方案是用ZooKeeper,先在三个节点部署ZK集群,然后修改flink-conf.yaml:
yaml复制high-availability: zookeeper
high-availability.zookeeper.quorum: zk-1:2181,zk-2:2181,zk-3:2181
high-availability.storageDir: hdfs:///flink/ha/
high-availability.zookeeper.path.root: /flink
然后修改masters文件,把多个JobManager节点写进去:
code复制flink-master-01:8081
flink-master-02:8081
启动集群后,两个JobManager节点会通过ZK做leader选举,一个为主,一个为备。如果主的节点宕机,备的会在几十秒内自动接管,这个切换期间正在运行的作业可能会中断,但有了checkpoint,作业重启后可以从最近一次检查点恢复。
需要注意:HA模式下,high-availability.storageDir一定要放在所有JobManager节点都能访问的共享文件系统上,比如HDFS或者NFS。如果放在某个JobManager的本地磁盘,主备切换后新的leader根本读不到旧leader上的元数据,HA就失去意义了。
4. 集群资源调优与参数规划
4.1 内存模型与参数计算
Flink的内存模型可以拆成总进程内存、JVM堆内存、托管内存、网络缓冲等几块。对部署来说,最重要的是先理解三个总内存参数:jobmanager.memory.process.size、taskmanager.memory.process.size,以及它们与堆内、堆外内存的关系。
简单来说,jobmanager.memory.process.size是JobManager整个进程的物理内存上限,包含JVM堆、堆外直接内存、Metaspace等。给它2G起步足够,如果集群里同时提交很多作业,REST请求、Job管理元数据都会堆在这里,建议升到4G。
taskmanager.memory.process.size是每个TaskManager进程的总内存,通常按单机可用内存的60%到70%分配。在这个总内存里,taskmanager.memory.managed.fraction决定托管内存的占比,默认是0.4。如果你以流计算为主,托管内存主要给RocksDB状态后端用,可以适当调低到0.3;如果你跑批任务或者做大规模排序聚合,托管内存需求量会更大,可以保持默认甚至更高。
任务之间的数据交换还需要网络缓冲内存,对应参数是taskmanager.memory.network.fraction,默认0.1。如果作业shuffle量很大、并行度高,这个默认值可能会不够,运行时会报Insufficient number of network buffers异常。我一般会提高到0.15到0.2。
给一个16G内存机器的参考配置:
yaml复制taskmanager.memory.process.size: 10g
taskmanager.memory.managed.fraction: 0.3
taskmanager.memory.network.fraction: 0.15
剩下大约4G留给操作系统和其他进程,实测比较稳。
4.2 Slot与并行度规划
taskmanager.numberOfTaskSlots决定单台TaskManager能同时运行多少个任务子任务。Slot不是线程,是一个资源隔离单元,它隔离的是TaskManager内部的内存和运算资源。常见的经验值是CPU核心数的一半到两倍之间。我这边16核的机器设了4个Slot,让每个Slot都相对宽裕,避免提交多个作业时互相挤占导致性能下降。
并行度规划有两条主线。第一条,每条流的source和sink连接器并行度,一般和Slot总数量匹配,或者略小于Slot总量,避免部分Slot空闲、部分排队。第二条,窗口算子、聚合算子的并行度要考虑Key分布,如果数据倾斜严重,需要增加并行度或者做两阶段聚合。这些可以在作业提交阶段用--parallelism指定,但集群层面的parallelism.default可以作为一个兜底默认值,避免提交作业时忘了传并行度导致资源被过度占用。
4.3 更多生产环境参数
除了内存和Slot,还有几个参数我建议在部署时顺手配好。io.tmp.dirs指定临时文件目录,默认用/tmp,如果/tmp空间小,跑大规模排序或者shuffle落盘时很快就会爆磁盘,这个坑我很早之前遇到过,非常无语,但确实很少有人一开始就注意到。env.java.opts.jobmanager和env.java.opts.taskmanager用来追加JVM参数,比如G1GC垃圾回收器之类的。classloader.resolve-order控制类加载顺序,默认是child-first,优先加载用户jar包里的类,好处是用户依赖和Flink内置依赖版本不一致时,优先走用户自己的版本,坏处是如果你往lib目录里放了多个连接器jar,又和第三方依赖冲突,就会反复出现NoClassDefFoundError。
这里展开说一下classloader.resolve-order。我在给集群引入Flink CDC时就遇到了依赖冲突:CDC连接器依赖的protobuf版本和Flink内置的不一致,报错信息全是NoSuchMethodError,看着像代码问题,其实是类加载顺序问题。最后是整理lib目录,把冲突的shaded包清理干净,再按需调整resolve-order才解决。
4.4 状态后端与checkpoint规划
集群部署完成后,还要根据业务场景选定状态后端。目前主流的两个选择是HashMapStateBackend和RocksDBStateBackend。如果作业状态不大,全部能放内存,用HashMap性能最好,也简单;如果状态上T级别,或者要做增量checkpoint,选RocksDB会更稳。
checkpoint的存储目录一般放在HDFS上,配置项是state.checkpoints.dir。同时要设置checkpoint的间隔和超时时间。我的经验是:间隔不要低于30秒,否则频繁做快照会把磁盘和网络打满;超时时间设置成间隔的2到3倍,避免一次checkpoint长时间不结束还一直占着资源。另外state.backend.incremental参数在RocksDB下可以开启增量checkpoint,能显著降低快照耗时和存储压力。
5. 常见问题与异常排查实录
5.1 集群启动失败类问题
JobManager起不来,日志报端口被占。最常见的原因是上一轮的进程没有完全退出。start-cluster.sh之前先检查一下jps输出,确保没有残留的StandaloneSessionClusterEntrypoint或TaskManagerRunner。残留进程直接kill掉,但要先确认没有正在运行的作业,不然会连带作业一起杀。
TaskManager一直没有出现在Web UI,处于Pending状态。通常是JobManager节点到TaskManager节点的SSH免密没配好。start-cluster.sh是通过SSH远程执行命令的,如果免密失败,TaskManager进程根本不会启动。排查方式很简单:在JobManager节点手动执行ssh worker节点,如果提示要密码,那就说明免密配置有问题,回到2.4重新配置。
TaskManager进程启动了,但注册不上JobManager。大概率是IP地址互相解析不了。去TaskManager节点看日志,如果出现Connection refused并指向JobManager地址,先确认jobmanager.rpc.address配置的是Worker节点能访问到的地址。我遇到过用内网IP启动JobManager,但Worker节点解析不了这个IP的情况,最后统一改用/etc/hosts里的主机名,问题就解决了。
5.2 Web UI与任务提交类问题
Web UI打不开。先检查rest.bind-address是不是0.0.0.0,再看防火墙是否放行了8081端口。还有一点容易被忽略:Flink的REST服务在1.20版本中默认监听localhost,除非显式配置了rest.bind-address,所以不要想当然地以为部署完就能远程打开Web UI。
作业提交失败,报“Could not build the program from JAR file”。大概率是作业JAR包编译时引用的Flink版本和集群不一致。Flink对类库版本比较敏感,客户端编译用的版本和集群服务端版本不一致,会出现各种NoSuchMethodError、UnsupportedClassVersionError。建议使用与集群完全一致的Flink版本编译作业,减少不必要的麻烦。
提交作业后TaskManager进程被系统杀掉。很可能是taskmanager.memory.process.size设置得过高,超过了机器实际可用内存。Flink启动时会预留配置的内存,如果操作系统在压力大的时候触发OOM Killer,第一个遭殃的就是内存占用最大的进程,也就是TaskManager。去看dmesg输出,如果出现Killed process相关记录,基本就是这个问题。调低内存配置,或者把机器上其他进程迁走。
5.3 连接器与作业运行类问题
JDBC连接器异常。这是做实时数仓、接外部数据源时最常遇到的问题。常见的报错有几种。第一种是ClassNotFoundException: com.mysql.cj.jdbc.Driver,说明lib目录缺少JDBC驱动jar,把对应的数据库驱动放到lib目录就能解决。第二种是Connection is not available, request timed out,说明连接池配置过小或者数据库连接数被打满,优化连接池参数或者给数据库扩容。第三种是Communications link failure,要么是网络问题,要么是数据库的wait_timeout把空闲连接回收了,导致连接池里的连接已经失效。处理办法是在JDBC连接串里加上autoReconnect=true和合适的验证查询,同时确保连接池的maxLifetime小于数据库的wait_timeout。
集成Flink CDC时的依赖冲突。Flink CDC 3.x的依赖树非常庞大,会依赖Debezium以及一堆protobuf库。如果Flink内置的protobuf版本和CDC连接器里面的protobuf版本不一致,就会出现版本冲突,报错大多是一长串的NoSuchMethodError。我当时把flink-shaded-protobuf放到了lib目录,然后调整classloader.resolve-order为parent-first,才把冲突解决掉。这里提醒一下:CDC连接器不是把jar扔进lib目录就完事的,它和Flink核心依赖之间的兼容性一定要提前核对。
用DataSophon这类平台管理集群时,出现Job上传失败。这类平台管理的Flink一般有自己封装的部署目录,往往和用户手动规划的目录不一致。上传Job失败时,先看Web服务暴露的地址是否正确,再确认部署目录是否有写权限,以及磁盘空间是否充足。我自己遇到过一次,平台把临时目录挂在了小容量分区上,一个作业JAR传上去磁盘就满了。如果平台排查起来麻烦,建议直接把Flink切换成手动部署,也就是本文这套流程,反而更可控。
5.4 排查Flink集群问题的通用工具
最后分享几个我常用的排查命令和思路。集群起不来时,去看log目录下的日志文件,JobManager和TaskManager的日志是分开的,TaskManager的日志里能直接看到注册失败的原因。用jstack可以看线程是否死锁,用jmap -heap看堆内存分布,用ss -tlnp看端口监听是否正确。
还有一个很实用的排查技巧:改配置后不一定要重启整个集群。如果只是TaskManager的配置变化,逐个节点重启TaskManager就可以;如果是JobManager的修改,则必须全部重启,否则新旧配置混用会出现各种莫名其妙的问题。另外,修改配置前一定备份原来的配置文件,我之前有一次手滑改错了parallelism.default,还好有备份,三分钟就恢复了。
写到这里,整套Flink 1.20集群的部署、配置、调优和排障流程基本都覆盖到了。我个人在实际操作中最大的体会是,Flink集群本身部署不难,难的是把内存参数、网络绑定的暗坑和连接器依赖理清楚。建议你在搭建时,一定先在Web UI上跑通一个内置示例作业,再接入真实业务和外部数据源,这样每一步出问题都能快速定位。后边如果大家需要,我也可以继续写一篇基于这套集群的Flink CDC同步实操,包括MySQL整库同步的配置和常见问题。先到这里,祝部署顺利。
