12_Flink Streaming cluster

在此之前的都是如何编写api,制作topology的过程。提交到集群后,就是运行topology了。运行时的模块大多都是scala写的,可能是因为使用akka通信。之前绘制DAG的过程大多是java写的。

flink的cluster有两种,LocalFlinkMiniCluster和FlinkMiniCluster,本地运行时是LocalFlinkMiniCluster,集群运行时是FlinkMiniCluster。LocalFlinkMiniCluster是在jvm中使用多线程模拟分布式计算。研究这个意义较小。所以研究一下FlinkMiniCluster。

FlinkMiniCluster。

在了解集群之前。先了解flink的架构。flink runtime 集群由1个JobManager(非HA)和多个TaskManager。组成。

JobManager,负责接收Client请求,统一管理TaskManager。类似storm的nimbus和worker的关系。

TaskManager,管理Task任务的执行。

以下简称JM和TM

JM和TM的通信由akka实现。两者都实现了FlinkActor。通过handleMessage方法,传递不同的Message进行通信。


/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.flink.runtime

import _root_.akka.actor.Actor
import grizzled.slf4j.Logger

/** Base trait for Flink's actors.
  *
  * The message handling logic is defined in the handleMessage method. This allows to mixin
  * stackable traits which change the message receiving behaviour.
  */
trait FlinkActor extends Actor {
  val log: Logger

  override def receive: Receive = handleMessage

  /** Handle incoming messages
    *
    * @return
    */
  def handleMessage: Receive

  /** Factory method for messages. This method can be used by mixins to decorate messages
    *
    * @param message The message to decorate
    * @return The decorated message
    */
  def decorateMessage(message: Any): Any = {
    message
  }
}

TaskManager

/**
   * Central handling of actor messages. This method delegates to the more specialized
   * methods for handling certain classes of messages.
   */
  override def handleMessage: Receive = {
    // task messages are most common and critical, we handle them first
    case message: TaskMessage => handleTaskMessage(message)

    // messages for coordinating checkpoints
    case message: AbstractCheckpointMessage => handleCheckpointingMessage(message)

    case JobManagerLeaderAddress(address, newLeaderSessionID) =>
      handleJobManagerLeaderAddress(address, newLeaderSessionID)

    // registration messages for connecting and disconnecting from / to the JobManager
    case message: RegistrationMessage => handleRegistrationMessage(message)

    // task sampling messages
    case message: StackTraceSampleMessages => handleStackTraceSampleMessage(message)

    // ----- miscellaneous messages ----

    // periodic heart beats that transport metrics
    case SendHeartbeat => sendHeartbeatToJobManager()

    // sends the stack trace of this TaskManager to the sender
    case SendStackTrace => sendStackTrace(sender())

    // registers the message sender to be notified once this TaskManager has completed
    // its registration at the JobManager
    case NotifyWhenRegisteredAtJobManager =>
      if (isConnected) {
        sender ! decorateMessage(RegisteredAtJobManager)
      } else {
        waitForRegistration += sender
      }

    // this message indicates that some actor watched by this TaskManager has died
    case Terminated(actor: ActorRef) =>
      if (isConnected && actor == currentJobManager.orNull) {
          handleJobManagerDisconnect(sender(), "JobManager is no longer reachable")
          triggerTaskManagerRegistration()
      } else {
        log.warn(s"Received unrecognized disconnect message " +
            s"from ${if (actor == null) null else actor.path}.")
      }

    case Disconnect(msg) =>
      handleJobManagerDisconnect(sender(), s"JobManager requested disconnect: $msg")
      triggerTaskManagerRegistration()

    case msg: StopCluster =>
      log.info(s"Stopping TaskManager with final application status ${msg.finalStatus()} " +
        s"and diagnostics: ${msg.message()}")
      shutdown()

    case FatalError(message, cause) =>
      killTaskManagerFatal(message, cause)

    case RequestTaskManagerLog(requestType : LogTypeRequest) =>
      blobService match {
        case Some(_) =>
          handleRequestTaskManagerLog(sender(), requestType, currentJobManager.get)
        case None =>
          sender() ! new IOException("BlobService not available. Cannot upload TaskManager logs.")
      }
  }

  /**
   * Handle unmatched messages with an exception.
   */
  override def unhandled(message: Any): Unit = {
    val errorMessage = "Received unknown message " + message
    val error = new RuntimeException(errorMessage)
    log.error(errorMessage)

    // terminate all we are currently running (with a dedicated message)
    // before the actor is stopped
    cancelAndClearEverything(error)

    // let the actor crash
    throw error
  }

JobManager

/**
   * Central work method of the JobManager actor. Receives messages and reacts to them.
   *
   * @return
   */
  override def handleMessage: Receive = {

    case GrantLeadership(newLeaderSessionID) =>
      log.info(s"JobManager $getAddress was granted leadership with leader session ID " +
        s"$newLeaderSessionID.")

      leaderSessionID = newLeaderSessionID

      // confirming the leader session ID might be blocking, thus do it in a future
      future {
        leaderElectionService.confirmLeaderSessionID(newLeaderSessionID.orNull)

        // TODO (critical next step) This needs to be more flexible and robust (e.g. wait for task
        // managers etc.)
        if (recoveryMode != RecoveryMode.STANDALONE) {
          log.info(s"Delaying recovery of all jobs by $jobRecoveryTimeout.")

          context.system.scheduler.scheduleOnce(
            jobRecoveryTimeout,
      
#!/bin/bash ############################################################################## # 脚本功能:Flink 1.15.2 Standalone 模式单作业监控与自动恢复 # 适配输出:针对flink list的格式"时间 : 作业ID : 作业名 (状态)"专门优化 # 适用场景:单作业环境,WebUI提交,代码内置Checkpoint和重启机制 ############################################################################## # ========================== 【用户需修改的核心配置】 ========================== export FLINK_HOME="/opt/flink-1.15.2" # Flink安装目录(Standalone模式) export MAIN_CLASS="com.example.YourMainClass" # 作业主类(全限定名) export JOB_JAR="/path/to/your/job.jar" # 作业JAR包绝对路径 export PARALLELISM=4 # 并行度(与WebUI提交时一致) export CHECK_INTERVAL=30 # 状态检查间隔(秒,建议30-60) export LOG_FILE="/var/log/flink_job_monitor.log" # 脚本日志路径 export ACTUAL_JOB_NAME="Flink Streaming Job" # 替换为你的实际作业名(flink list中显示的名称) # ============================================================================== # ========================== 【依赖检查】 ========================== check_dependency() { # 检查基础工具 if ! command -v grep &> /dev/null || ! command -v awk &> /dev/null; then echo "错误:缺少必要工具(grep/awk),请先安装" exit 1 fi # 检查Flink核心脚本 local required_scripts=("${FLINK_HOME}/bin/flink" "${FLINK_HOME}/bin/start-cluster.sh" "${FLINK_HOME}/bin/stop-cluster.sh") for script in "${required_scripts[@]}"; do if [ ! -f "${script}" ]; then echo "错误:Flink脚本不存在(${script}),请检查FLINK_HOME" exit 1 fi done # 检查作业JAR包 if [ ! -f "${JOB_JAR}" ]; then echo "错误:作业JAR包不存在(${JOB_JAR})" exit 1 fi } # ========================== 【日志工具函数】 ========================== log() { local level=$1 local message=$2 local timestamp=$(date "+%Y-%m-%d %H:%M:%S") echo -e "[${timestamp}] [${level}] ${message}" | tee -a "${LOG_FILE}" } # ========================== 【获取作业ID(适配实际输出格式)】 ========================== get_job_id() { local job_id local flink_list_output # 执行flink list并捕获输出(过滤错误信息) flink_list_output=$("${FLINK_HOME}/bin/flink" list 2>/dev/null) # 提取逻辑: # 1. 过滤无关行(等待提示、无作业、调度中状态) # 2. 按": "分割列(格式为"时间 : ID : 作业名 (状态)") # 3. 匹配实际作业名,提取第二列作为ID job_id=$(echo "${flink_list_output}" | \ grep -v -E "Waiting for response|No running jobs|scheduled" | \ grep "${ACTUAL_JOB_NAME}" | \ awk -F': ' '{print $2}' | \ head -n 1) if [ -z "${job_id}" ]; then log "WARN" "未检测到作业[${ACTUAL_JOB_NAME}](可能未提交或已停止)" echo "" else log "INFO" "检测到作业[${ACTUAL_JOB_NAME}],ID:${job_id}" echo "${job_id}" fi } # ========================== 【获取作业状态(适配实际输出格式)】 ========================== get_job_status() { local job_id=$1 local job_status local flink_list_output flink_list_output=$("${FLINK_HOME}/bin/flink" list 2>/dev/null) # 提取状态:找到含目标ID的行,按括号分割取状态部分 job_status=$(echo "${flink_list_output}" | \ grep "${job_id}" | \ awk -F'[()]' '{print $2}' | \ head -n 1) if [ -z "${job_status}" ]; then log "ERROR" "获取作业[${job_id}]状态失败(可能集群异常)" echo "UNKNOWN" else log "INFO" "作业[${job_id}]当前状态:${job_status}" echo "${job_status}" fi } # ========================== 【重启Standalone集群】 ========================== restart_flink_cluster() { log "INFO" "开始重启Flink Standalone集群..." # 1. 停止集群 log "INFO" "执行停止命令:${FLINK_HOME}/bin/stop-cluster.sh" ${FLINK_HOME}/bin/stop-cluster.sh >> "${LOG_FILE}" 2>&1 sleep 5 # 等待进程退出 # 2. 清理残留进程(防止端口占用) local residual_pids=$(jps | grep -E "StandaloneSessionClusterEntrypoint|TaskManagerRunner" | awk '{print $1}') if [ -n "${residual_pids}" ]; then log "WARN" "发现残留进程:${residual_pids},强制终止" kill -9 ${residual_pids} >> "${LOG_FILE}" 2>&1 sleep 2 fi # 3. 启动集群 log "INFO" "执行启动命令:${FLINK_HOME}/bin/start-cluster.sh" ${FLINK_HOME}/bin/start-cluster.sh >> "${LOG_FILE}" 2>&1 sleep 12 # 等待集群初始化(根据机器性能调整) # 4. 验证集群状态(检查JM和TM进程) local jm_pid=$(jps | grep StandaloneSessionClusterEntrypoint | awk '{print $1}') local tm_pid=$(jps | grep TaskManagerRunner | awk '{print $1}') if [ -n "${jm_pid}" ] && [ -n "${tm_pid}" ]; then log "INFO" "集群重启成功(JM PID:${jm_pid},TM PID:${tm_pid})" else log "ERROR" "集群重启失败(JM/TM进程未启动)" exit 1 fi } # ========================== 【重新提交作业】 ========================== resubmit_job() { log "INFO" "开始重新提交作业[${ACTUAL_JOB_NAME}]..." # 用指定参数提交作业(与WebUI一致) ${FLINK_HOME}/bin/flink run \ -c "${MAIN_CLASS}" \ -p "${PARALLELISM}" \ "${JOB_JAR}" >> "${LOG_FILE}" 2>&1 # 验证提交结果 local new_job_id=$(get_job_id) if [ -n "${new_job_id}" ]; then log "INFO" "作业提交成功,新ID:${new_job_id}" else log "ERROR" "作业提交失败(检查JAR包/主类或集群资源)" exit 1 fi } # ========================== 【主监控逻辑】 ========================== main() { check_dependency log "INFO" "Flink作业监控脚本启动(配置:FLINK_HOME=${FLINK_HOME},作业名=${ACTUAL_JOB_NAME})" while true; do log "INFO" "===================== 开始状态检查 =====================" local job_id=$(get_job_id) # 无作业时等待重试 if [ -z "${job_id}" ]; then log "WARN" "无作业可监控,${CHECK_INTERVAL}秒后重试..." sleep "${CHECK_INTERVAL}" continue fi # 检查作业状态并处理 local job_status=$(get_job_status "${job_id}") case "${job_status}" in "RUNNING"|"RESTARTING") log "INFO" "作业正常运行,无需处理" ;; "FAILED"|"CANCELED") log "ERROR" "作业状态为[${job_status}],启动恢复流程..." restart_flink_cluster resubmit_job ;; "FINISHED") log "WARN" "作业已正常完成,不启动恢复" ;; "UNKNOWN") log "ERROR" "集群状态异常,${CHECK_INTERVAL}秒后重试..." ;; esac log "INFO" "===================== 检查结束,等待${CHECK_INTERVAL}s =====================" sleep "${CHECK_INTERVAL}" done } # 启动脚本 main 分析代码
09-17
<think> 我们讨论的是Flink 1.15.2 Standalone模式下的单作业监控与自动恢复脚本代码分析。 目标:监控一个Flink作业(通过作业ID),如果作业失败,则自动重启它。 步骤: 1. 我们需要能够获取作业的状态(运行中、失败等)。 2. 如果检测到作业失败,则重新提交作业(需要知道作业的jar包路径、入口类、参数等)。 在Standalone模式下,我们可以使用Flink REST API或命令行工具来查询作业状态。 参考引用: 引用[2]提到:在Standalone模式下,可以使用`flink list`命令查看作业状态。 引用[5]提到:Standalone模式不需要额外的Hadoop依赖。 我们选择使用Flink REST API来监控作业状态,因为API返回的信息更结构化,易于解析。 Flink Web UI默认使用8081端口,REST API的基地址通常是:http://<jobmanager_host>:8081 具体步骤: 第一步:定期检查作业状态(通过作业ID) - 使用curl调用REST API:GET /jobs/<jobID> - 返回的JSON中,状态字段为"state",可能的取值包括:"RUNNING", "FAILED", "CANCELED", "FINISHED"等。 第二步:如果状态为"FAILED",则重新提交作业。 - 重新提交作业需要使用`flink run`命令,指定jar包、入口类、参数等。 脚本设计要点: 1. 需要知道Flink作业的Job ID(如果作业第一次提交,需要记录下Job ID,因为后续监控和重启需要同一个ID?注意:作业重启后,Flink会生成新的Job ID) 因此,我们可能需要改变策略:我们监控的是作业是否存在(通过作业名称)?或者我们每次重新提交后记录新的Job ID? 2. 有两种思路: a) 监控特定的作业名称(因为作业名称可以保持不变)。通过REST API获取所有作业,然后根据名称找到作业ID,再检查状态。 b) 在第一次提交作业时记录Job ID,然后监控该ID。但是当作业失败重启后,会生成新的Job ID,所以需要更新存储的Job ID。 这里采用方案a),因为作业名称通常由用户指定且不变。 具体步骤: 1. 提交作业时,使用`-Djob.name`指定作业名称(或者通过程序内部设置,但这里我们假设提交时通过`-D`设置,这样在REST API中可以获取到)。 2. 通过REST API获取所有作业,然后根据名称查找作业ID和状态。 REST API获取所有作业:GET /jobs/overview 返回示例: { "jobs": [ { "jid": "d3f4e5g6h7i8j9k0l1m2n3o4p5q6r", "name": "My Flink Job", "state": "RUNNING", ... } ] } 3. 如果找不到该名称的作业,或者作业状态为"FAILED",则重新提交。 自动恢复脚本的伪代码: while true: 通过REST API获取作业列表 查找作业名称对应的作业状态 if 未找到作业 OR 作业状态为"FAILED": 重新提交作业(使用相同的作业名称) 记录新的作业ID(可选,因为后续通过名称查找) 睡眠一段时间(如30秒) 提交作业的命令(假设我们使用命令行提交): ./bin/flink run -d -Djob.name=MyJobName -c com.example.MainClass /path/to/jar arg1 arg2 注意:提交作业时,通过`-Djob.name`设置作业名称(Flink 1.15支持)。 脚本实现(使用bash和jq解析JSON): 需要安装curl和jq。 脚本代码示例: ```bash #!/bin/bash # 配置 FLINK_REST_URL="http://localhost:8081" # Flink JobManager的REST API地址 JOB_NAME="My Flink Job" # 要监控的作业名称 JAR_PATH="/path/to/your/job.jar" MAIN_CLASS="com.example.MainClass" JOB_ARGS="arg1 arg2" # 作业参数 CHECK_INTERVAL=30 # 检查间隔(秒) # 提交作业的函数 function submit_job() { # 提交作业,并设置作业名称 output=$(./bin/flink run -d -Djob.name="${JOB_NAME}" -c "${MAIN_CLASS}" "${JAR_PATH}" ${JOB_ARGS}) # 从输出中提取作业ID(可选,这里我们后续通过名称查找,所以不需要) # 但可以打印日志 echo "Submitted job: ${output}" } # 检查作业状态的函数 function check_job_status() { # 获取作业概览 response=$(curl -s -X GET "${FLINK_REST_URL}/jobs/overview") # 使用jq解析,查找作业名称为$JOB_NAME的作业的状态 # 注意:jq中需要转义双引号 job_status=$(echo "$response" | jq -r --arg jobName "$JOB_NAME" '.jobs[] | select(.name == $jobName) | .state') # 如果找到,返回状态;否则返回空 echo "$job_status" } # 主循环 while true; do status=$(check_job_status) if [ -z "$status" ]; then echo "Job is not running. Resubmitting..." submit_job elif [ "$status" == "FAILED" ]; then echo "Job has failed. Resubmitting..." submit_job elif [ "$status" == "RUNNING" ]; then echo "Job is running normally." else echo "Job status: $status" fi sleep "$CHECK_INTERVAL" done ``` 注意事项: 1. 作业名称必须唯一,否则可能监控多个作业(但我们的脚本只会取第一个匹配的,所以最好确保唯一)。 2. 如果作业正常完成(FINISHED),脚本也会重新提交。所以这个脚本只适用于需要持续运行的作业(如流作业)。 如果作业是批处理,则不应该重启。可以根据需要修改条件(例如,只对FAILED状态重启,而如果是FINISHED则不做处理)。 3. 提交作业的命令需要根据实际情况调整(如Flink的路径,可能需要使用绝对路径)。 4. 如果Flink集群重启,作业会丢失,脚本会重新提交。 改进点: - 可以添加重试次数限制。 - 监控更详细的状态(如RESTARTING状态,但FAILED是最终失败状态)。 引用说明: 使用REST API监控作业状态是Flink的标准做法[^flink-rest-docs]。 使用命令行提交作业在Standalone模式下是直接的方式(引用[2]和[4]提到了命令行操作)。 [^flink-rest-docs]: Flink REST API文档: https://nightlies.apache.org/flink/flink-docs-release-1.15/docs/ops/rest_api/ 以上脚本代码实现了基本的监控和自动恢复功能,适用于Standalone模式下的流处理作业。 §§相关问题§§ 1. Flink REST API 还能获取哪些作业信息? 2. 如何确保脚本本身的高可用性? 3. Flink 作业失败后如何保留状态以实现有状态恢复? </think> ### Flink 1.15.2 Standalone模式单作业监控与自动恢复脚本分析 基于引用内容和Flink 1.15.2特性,以下是脚本设计的核心逻辑和技术要点: --- #### 1. **脚本核心架构** ```mermaid graph LR A[监控进程] --> B{状态检查} B -->|RUNNING| C[等待下次检查] B -->|FAILED| D[重启作业] D --> E[提交作业] E --> F[记录新作业ID] ``` --- #### 2. **关键功能模块** ##### (1) **作业状态监控** ```bash # 通过REST API获取作业状态(需开启Web UI端口) curl -s "http://jobmanager:8081/jobs/${JOB_ID}" | jq -r '.state' # 状态返回值: # RUNNING, FAILED, CANCELED, FINISHED, RESTARTING ``` **解析逻辑**: ```bash if [ "$STATE" = "FAILED" ]; then echo "$(date) - Job ${JOB_ID} failed" >> recovery.log restart_job fi ``` > 引用[3]中需配置`jobmanager.rpc.address`确保API可达[^3] ##### (2) **自动恢复提交** ```bash # 作业提交模板(包含高可用配置) ./bin/flink run -d \ -D high-availability=zookeeper \ -D high-availability.zookeeper.quorum=zk1:2181,zk2:2181 \ -c com.MainClass /path/to/job.jar ``` **关键参数**: - `-d`:分离模式(后台运行) - `high-availability.storageDir`:状态存储路径(引用[3])[^3] - `-D`:覆盖`flink-conf.yaml`配置(引用[1])[^1] ##### (3) **作业ID记录** ```bash # 首次提交时捕获作业ID JOB_ID=$(./bin/flink run ... | grep -oP 'JobID \K[a-f0-9]+') # 持久化存储到文件 echo $JOB_ID > /var/flink/job.id ``` --- #### 3. **完整脚本示例** ```bash #!/bin/bash # 配置参数 JOB_NAME="StreamingJob" JAR_PATH="/opt/flink/jobs/etl.jar" MAIN_CLASS="com.ETLMain" CHECK_INTERVAL=60 # 检查间隔(秒) # 加载上次作业ID [ -f "/tmp/${JOB_NAME}.id" ] && JOB_ID=$(cat /tmp/${JOB_NAME}.id) while true; do STATUS=$(curl -s "http://jobmanager:8081/jobs/$JOB_ID" | jq -r '.state') case $STATUS in "FAILED") echo "[$(date)] Job $JOB_ID failed! Restarting..." NEW_ID=$(./bin/flink run -d -c $MAIN_CLASS $JAR_PATH | \ grep -oP 'JobID \K[0-9a-z]+') echo $NEW_ID > /tmp/${JOB_NAME}.id JOB_ID=$NEW_ID ;; "RUNNING") echo "[$(date)] Job $JOB_ID healthy" ;; *) echo "[$(date)] Unknown status: $STATUS" esac sleep $CHECK_INTERVAL done ``` --- #### 4. **关键技术点** 1. **状态持久化** - 需配置ZooKeeper和HDFS(引用[3])[^3]: ```yaml high-availability: zookeeper high-availability.storageDir: hdfs:///flink/recovery ``` 2. **日志监控替代方案** ```bash # 监控TaskManager日志(引用[2]) tail -F log/flink-*-taskexecutor-*.log | grep "Exception" ``` 3. **作业唯一性保证** - 通过`-Djob.name`指定作业名称(引用[4])[^4],避免重复提交 --- #### 5. **生产环境增强** - **重试策略**:添加指数退避重试机制 - **通知集成**:失败时触发邮件/钉钉报警 - **资源隔离**:使用`yarn.provided.lib.dirs`避免重复上传jar(引用[5])[^5] - **版本控制**:在HDFS存储jar版本,确保恢复时使用正确版本 > 此方案通过解耦监控与执行模块,实现Standalone模式下的作业自治恢复[^1][^3]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值