十二 状态

本文详细介绍了Apache Flink的状态管理,包括键控状态、ValueState、flatMapWithState、ListState和广播状态的使用。同时,讨论了检查点、保存点的触发、恢复与删除,以及Flink命令行操作,如提交作业、取消作业和调整并行度等。


两种状态,键控状态 (keyed state) 和算子状态 (operator state)。

1 键控状态 (keyed state)

键控状态用于keyed stream,对于每一个 key, Flink将会维护一个状态实例。

keyed state 仅可用于 KeyedStream。 Flink 支持以下数据类型的状态变量:
• ValueState[T] 保存单个的值,值的类型为 T。
– get 操作: ValueState.value()
– set 操作: ValueState.update(value: T)
• ListState[T] 保存一个列表,列表里的元素的数据类型为 T。基本操作如下:
– ListState.add(value: T)
– ListState.addAll(values: java.util.List[T])
– ListState.get() 返回 Iterable[T]
– ListState.update(values: java.util.List[T])
• MapState[K, V] 保存 Key-Value 对。
– MapState.get(key: K)
– MapState.put(key: K, value: V)
– MapState.contains(key: K)
– MapState.remove(key: K)
• ReducingState[T] (和ListState差不多)
• AggregatingState[I, O] (和ListState差不多)
State.clear() 是清空操作。
不同 key 对应的 keyed state 是相互隔离的

当一个函数注册了 StateDescriptor 描述符, Flink 会检查状态后端是否已经存在这个状态。这种情况通常出现在应用挂掉要从检查点或者保存点恢复的时候。在这两种情况下, Flink 会将注册的状态连接到已经存在的状态。如果不存在状态,则初始化一个空的状态。

1.1 ValueState 案例

package org.example.state

import org.apache.flink.api.common.functions.RichFlatMapFunction
import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.api.scala.typeutils.Types
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector
import org.example.source.self.{SensorReading, SensorSource}

/**
 * 实现上一次温度和这次温度差如果超过1.7度,输出当前温度
 */
object ValueStateInFlatMap {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)

    env.addSource(new SensorSource)
      .keyBy(_.id)
      .flatMap(new TemperatureAlert(1.7))
      .print()

    env.execute()
  }

  class TemperatureAlert(val diff:Double) extends RichFlatMapFunction[SensorReading,(String,Double,Double)] {
    var lastTemp: ValueState[Double] = _
    override def open(parameters: Configuration): Unit = {
      /**
       * 初始化一个状态变量保存上一次的温度
       */
      lastTemp = getRuntimeContext.getState(
        new ValueStateDescriptor[Double]("last-temp", Types.of[Double])
      )
    }
    override def flatMap(in: SensorReading, collector: Collector[(String, Double, Double)]): Unit = {
      val last = lastTemp.value() //获取上次温度

      //求这次温度和上次温度的温度差的绝对值
      val tempDiff = (in.temperature - last).abs

      //如果设置的差值,输出
      if (tempDiff > diff){
        collector.collect((in.id,in.temperature,tempDiff))
      }

      //更新状态变量
      lastTemp.update(in.temperature)
    }
  }
}

1.2 flatMapWithState 实现以上需求

package org.example.state

import org.apache.flink.streaming.api.scala._
import org.example.source.self.{SensorReading, SensorSource}
import org.example.state.ValueStateInFlatMap.TemperatureAlert

object FlatMapWithStateExample {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)

    env.addSource(new SensorSource)
      .keyBy(_.id)
      // 第一个参数是输出的元素的类型,第二个参数是状态变量的类型
      .flatMapWithState[(String,Double,Double),Double]{
        case (in:SensorReading,None) =>{ //匹配第一个元素
          (List.empty,Some(in.temperature)) //只更新状态变量,不返回任何值到下游算子
        }
        case (in:SensorReading,lastTemp:Some[Double]) =>{ //状态中已经和有值
          var tempDiff = (in.temperature - lastTemp.get).abs
          if(tempDiff > 1.7){
            (List((in.id,in.temperature,tempDiff)),Some(in.temperature)) //输出结果到下游
          }else{
            (List.empty,Some(in.temperature)) //值更新状态
          }
        }

      }
      .print()

    env.execute()
  }
}

1.3 ListState

package org.example.state

import org.apache.flink.api.common.state.{ListState, ListStateDescriptor, ValueState, ValueStateDescriptor}
import org.apache.flink.api.scala.typeutils.Types
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector
import org.example.source.self.{SensorReading, SensorSource}

import scala.collection.mutable.ListBuffer

/**
 * 每10秒统计信息的数量并输出
 */
object ListStateExample {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)

    val stream = env.addSource(new SensorSource)
      .filter(_.id.equals("sensor_1"))
      .keyBy(_.id)
      .process(new MyKeyedProcess)
      .print()
    env.execute()
  }

  class MyKeyedProcess extends KeyedProcessFunction[String, SensorReading, String] {
    var listState: ListState[SensorReading] = _
    var timeTs: ValueState[Long] = _

    override def open(parameters: Configuration): Unit = {
      
      //定义一个状态列表
      listState = getRuntimeContext.getListState(
        new ListStateDescriptor[SensorReading]("list-state",
          Types.of[SensorReading]
        )
      )

      //定义一个状态值
      timeTs = getRuntimeContext.getState(
        new ValueStateDescriptor[Long](
          "timer",
          Types.of[Long]
        )
      )
      
    }
    
    override def processElement(value: SensorReading, ctx: KeyedProcessFunction[String, SensorReading, String]#Context, out: Collector[String]): Unit = {
      listState.add(value) //把元素添加到状态列表中
      if(timeTs.value() == 0L){ //第一个元素
        val ts = ctx.timerService().currentProcessingTime() + 10 * 1000L
        ctx.timerService().registerProcessingTimeTimer(ts) //注册一个定时器
        timeTs.update(ts) 
      }
      
    }

    override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, SensorReading, String]#OnTimerContext, out: Collector[String]): Unit = {
      val list: ListBuffer[SensorReading] = ListBuffer() // 初始化一个空列表
      import scala.collection.JavaConversions._ // 必须导入
      // 将列表状态变量的数据都添加到列表中
      for (r <- listState.get()) {
        list += r
      }
      listState.clear() // gc列表状态变量

      out.collect("列表状态变量中的元素数量有 " + list.size + " 个")
      timeTs.clear()
      
    }
  }

}

1.4 使用连接的广播状态 (using connected broadcast state)

一个常见的需求就是流应用需要将同样的事件分发到操作符的所有的并行实例中,而这样的分发操作还得是可恢复的。
我们举个例子:一条流是一个规则 (比如 5 秒钟内连续两个超过阈值的温度),另一条流是待匹配的流。也就是说,规则流和事件流。所以每一个操作符的并行实例都需要把规则流保存在操作符状态中。也就是说,规则流需要被广播到所有的并行实例中去。
在 Flink 中,这样的状态叫做广播状态 (broadcast state)。广播状态和 DataStream 或者 KeyedStream 都可以做连接操作。
下面的例子实现了一个温度报警应用,应用有可以动态设定的阈值,动态设定通过广播流来实现。

 val sensorData: DataStream[SensorReading] = ...

  val thresholds: DataStream[ThresholdUpdate] = ...

  val keyedSensorData: KeyedStream[SensorReading, String] = sensorData
    .keyBy(_.id)
  // the descriptor of the broadcast state
  val broadcastStateDescriptor =
    new MapStateDescriptor[String, Double](
      "thresholds", classOf[String], classOf[Double])
  val broadcastThresholds: BroadcastStream[ThresholdUpdate] = thresholds
    .broadcast(broadcastStateDescriptor)
  // connect keyed sensor stream and broadcasted rules stream
  val alerts: DataStream[(String, Double, Double)] = keyedSensorData
    .connect(broadcastThresholds)
    .process(new UpdatableTemperatureAlertFunction())

带有广播状态的函数在应用到两条流上时分三个步骤:
• 调用 DataStream.broadcast() 来创建 BroadcastStream,定义一个或者多个 MapStateDescriptor 对象。
• 将 BroadcastStream 和 DataStream/KeyedStream 做 connect 操作。
• 在 connected streams 上调用 KeyedBroadcastProcessFunction/BroadcastProcessFunction。

下面的例子实现了动态设定温度阈值的功能

    class UpdatableTemperatureAlertFunction()
      extends KeyedBroadcastProcessFunction[String,
        SensorReading, ThresholdUpdate, (String, Double, Double)] {
      // the descriptor of the broadcast state
      private lazy val thresholdStateDescriptor =
        new MapStateDescriptor[String, Double](
          "thresholds", classOf[String], classOf[Double])
      // the keyed state handle
      private var lastTempState: ValueState[Double] = _

      override def open(parameters: Configuration): Unit = {
        // create keyed state descriptor
        val lastTempDescriptor = new ValueStateDescriptor[Double](
          "lastTemp", classOf[Double])
        // obtain the keyed state handle
        lastTempState = getRuntimeContext
          .getState[Double](lastTempDescriptor)
      }

      override def processBroadcastElement(
                                            update: ThresholdUpdate,
                                            ctx: KeyedBroadcastProcessFunction[String,
                                              SensorReading, ThresholdUpdate,
                                              (String, Double, Double)]#Context,
                                            out: Collector[(String, Double, Double)]): Unit = {
        // get broadcasted state handle
        val thresholds = ctx
          .getBroadcastState(thresholdStateDescriptor)
        if (update.threshold != 0.0d) {
          // configure a new threshold for the sensor
          thresholds.put(update.id, update.threshold)
        } else {
          // remove threshold for the sensor
          thresholds.remove(update.id)
        }
      }

      override def processElement(
                                   reading: SensorReading,
                                   readOnlyCtx: KeyedBroadcastProcessFunction
                                     [String, SensorReading, ThresholdUpdate,
                                       (String, Double, Double)]#ReadOnlyContext,
                                   out: Collector[(String, Double, Double)]): Unit = {
        // get read-only broadcast state
        val thresholds = readOnlyCtx
          .getBroadcastState(thresholdStateDescriptor)
        // check if we have a threshold
        if (thresholds.contains(reading.id)) {
          // get threshold for sensor
          val sensorThreshold: Double = thresholds.get(reading.id)
          // fetch the last temperature from state
          val lastTemp = lastTempState.value()
          // check if we need to emit an alert
          val tempDiff = (reading.temperature - lastTemp).abs
          if (tempDiff > sensorThreshold) {
            // temperature increased by more than the threshold
            out.collect((reading.id, reading.temperature, tempDiff))
          }
        }
        // update lastTemp state
        this.lastTempState.update(reading.temperature)
      }
    }

2 算子状态(不做介绍)

  • 列表状态(List state)

将状态表示为一组数据的列表

  • 联合列表状态(Union list state)

也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复

  • 广播状态(Broadcast state)

如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。

3 检查点

    env.enableCheckpointing(10000) //开启checkpoint 每10秒写一次
    env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE) //设置checkpoint 的模式为至少一次
    /**
    至少一次(exactly-once vs. at-least-once)
    精准一次 CheckpointingMode.EXACTLY_ONCE (默认)
    */
    
    //如果超过这个时间checkpoint还没结束,就会被认为是失败的
    env.getCheckpointConfig.setCheckpointTimeout(60000)
    
    //两次checkpoint的时间间隔
    env.getCheckpointConfig.setMinPauseBetweenCheckpoints(500)
    
    //至少同时开启多少个checkpoint
    env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
    

	/**
	enableExternalizedCheckpoints用于开启checkpoints的外部持久化,但是在job失	
	败的时候不会自动清理,需要自己手工清理state;ExternalizedCheckpointCleanup
	用于指定当job canceled的时候externalized checkpoint该如何清理,
	DELETE_ON_CANCELLATION的话,在job canceled的时候会自动删除externalized 
	state,但是如果是FAILED的状态则会保留;RETAIN_ON_CANCELLATION则在job
	 canceled的时候会保留externalized checkpoint state
	*/
	env.getCheckpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
    
    //checkpoint的保存路径
    val backend = new FsStateBackend("hdfs://xiaoai07:9000/flink/flink1/checkouts")
    env.setStateBackend(backend)

4 保存点

4.1 触发保存点

bin/flink savepoint jobId path

yarn模式:

bin/flink savepoint jobId path -yid yarnAppId

4.2 取消job的同时触发保存点

bin/flink cancel -s path jobId

4.3 从保存点启动

bin/flink run -s Path [:runArgs]

4.3 删除savepoint

bin/flink savepoint -d savepointPath

5 flink常用命令

在本机上使用flink run 指定集群的模式提交
./flink run -m remote_ip:8090 -p 1 -c com.test.TestLocal /home/hdp/flink-local.jar
# -m flink 集群地址
# -p 配置job的并行度
# -c Job执行的main class
不用指定master节点,提交是在flink集群的机器节点上
# 前台提交
./flink run -p 1 -c com.test.TestLocal /home/hdp/flink-local.jar
# 通过-d后台提交
./flink run -p 1 -c com.test.TestLocal -d /home/hdp/flink-local.jar
# -d,–detached : 在后台运行
# -s,–fromSavepoint : 基于savepoint保存下来的路径,进行恢复。
flink list
flink list:列出flink的job列表。
flink list -r/–runing :列出正在运行的job
flink list -s/–scheduled :列出已调度完成的job
flink cancel
flink cancel [options] <job_id> : 取消正在运行的job id
flink cancel -s/–withSavepoint <job_id> : 取消正在运行的job,并保存到相应的保存点
flink stop:仅仅针对Streaming job
flink stop [options] <job_id>
flink stop <job_id>:停止对应的job
flink modify(1.10.0 版已经被移除了)
flink modify <job_id> [options]
flink modify <job_id> -p/–parallelism p : 修改job的并行度

master 节点启动一个 Socket 服务
命令:nc -lk 8888(提示:nc: command not found,通过yum install nc来安装)

<think>嗯,用户想深入了解TCP/IP协议的12状态及其含义,并且补充说明要结合站内引用资料的信息。用户之前提到过需要严格使用数学公式格式,这次主题是网络协议,可能需要引用模型图示或流程图。从用户提问方式看,ta应该是计算机网络的学习者或从业者,对TCP状态转换有明确需求,但可能混淆了"TCP状态"和"TCP/IP协议分层"概念。用户引用的[1]提到TCP/IP四层模型,[2]提到三次握手,实际需要的是TCP状态机相关内容。用户可能正在排查网络问题或准备认证考试。需要重点区分:TCP状态12种(含LISTEN/SYN-SENT等),而TCP/IP模型有四层/五层/七层等不同划分标准。用户真正需要的是前者状态机解释。三次握手(SYN-SENT→SYN-RCVD→ESTABLISHED)和四次挥手(FIN-WAIT-1→CLOSE-WAIT→FIN-WAIT-2→TIME-WAIT)是核心流程,要补充TIME-WAIT等待2MSL的意义,这个常被忽视。可以这样组织回复:先用文字描述12状态的转换场景,再用状态转换图展示各状态关系(需转文本示意图)。特别注意要注明服务器端和客户端特有状态,比如LISTEN只存在于服务端。最后强调主动关闭方会经历TIME-WAIT,这是设计来防止旧连接数据包干扰的。生成的问题要引导用户理解状态机实操价值:如何用netstat查看状态状态异常如何排查?TIME-WAIT过多怎么优化?这对用户实际工作应该有帮助。用户似乎偏好技术细节,可以增加TCP状态与socketAPI的对应关系这类进阶内容。</think> 根据TCP连接的生命周期,协议定义了12状态及其转换关系(基于RFC 793)。以下是详细说明: --- ### **TCP协议12状态详解** #### **1. LISTEN (监听)** 服务端主动打开端口后等待客户端连接的状态。 *示例*:Web服务器在80端口等待请求。 #### **2. SYN-SENT (同步已发送)** 客户端发送`SYN`包后的状态,等待服务端响应`SYN-ACK`[^2]。 *触发动作*:`connect()`系统调用。 #### **3. SYN-RECEIVED (同步已接收)** 服务端收到`SYN`后回复`SYN-ACK`的状态,等待客户端`ACK`[^2]。 *特点*:中间状态,持续时间极短。 #### **4. ESTABLISHED (已建立连接)** 三次握手完成后的状态,双方可自由传输数据[^1]。 *标志*:连接进入稳定数据传输阶段。 --- #### **5. FIN-WAIT-1 (终止等待1)** 主动关闭方发送`FIN`后进入此状态,等待对方`ACK`或`FIN`。 *可能转换*: - 收到`ACK` → **FIN-WAIT-2** - 同时收到`ACK`+`FIN` → **TIME-WAIT** #### **6. FIN-WAIT-2 (终止等待2)** 收到对方对`FIN`的`ACK`后,等待对方发送`FIN`。 *超时*:若长时间未收到`FIN`则自动关闭连接。 #### **7. CLOSE-WAIT (关闭等待)** 被动关闭方收到`FIN`后回复`ACK`,进入等待上层应用关闭的状态。 *责任*:应用程序需主动调用`close()`触发下一步。 --- #### **8. CLOSING (正在关闭)** **特殊场景**:双方同时发送`FIN`,均进入此状态等待对方`ACK`。 *触发条件*:极端并发关闭情况。 #### **9. LAST-ACK (最后确认)** 被动关闭方发送`FIN`后等待对方`ACK`的状态。 *后续*:收到`ACK`后立即进入**CLOSED**。 --- #### **10. TIME-WAIT (时间等待)** 主动关闭方收到`FIN`后发送最终`ACK`,等待**2MSL**(最大报文生存时间)的状态[^2]。 *作用*: - 确保对方收到`ACK` - 防止旧连接数据包干扰新连接 *时长*:通常2分钟(Linux默认为60秒)。 #### **11. CLOSED (连接关闭)** 连接完全终止的最终状态。 *注意*:此状态仅为逻辑概念,实际系统中无对应套接字。 --- ### **状态转换示意图** ```mermaid graph TD A(LISTEN) -->|收SYN| B(SYN-RCVD) C(SYN-SENT) -->|收SYN+ACK| D(ESTABLISHED) B -->|收ACK| D D -->|主动关闭| E(FIN-WAIT-1) E -->|收ACK| F(FIN-WAIT-2) F -->|收FIN| G(TIME-WAIT) D -->|收FIN| H(CLOSE-WAIT) H -->|发FIN| I(LAST-ACK) I -->|收ACK| J(CLOSED) G -->|2MSL超时| J ``` > 注:MSL(Maximum Segment Lifetime)通常设为30秒至2分钟 --- ### **常见问题场景** 1. **TIME-WAIT状态过多** 高频短连接导致端口耗尽,可通过内核参数调整: ```sysctl net.ipv4.tcp_tw_reuse=1``` 2. **CLOSE-WAIT堆积** 应用程序未正确关闭连接,需检查代码资源释放逻辑。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值