Flink Agents 实战:基于 Flink 构建实时智能决策链路

发布时间:2026/10/16 18:29:22
Flink Agents 实战:基于 Flink 构建实时智能决策链路 1. 从算得快到想得对Flink Agents 到底在解决什么问题实时计算这个领域过去十年我们一直在解决一个核心命题数据来了怎么在毫秒级把它算出来。从最早的 Storm 到后来的 Spark Streaming再到 Flink 坐稳流批一体的头把交椅整个技术栈的演进方向都是更快、更准、更稳。但这两年我在实际项目里越来越强烈地感受到一个变化——业务方不再满足于你告诉我现在发生了什么他们开始问接下来该怎么办。这个转变非常关键。举个例子以前做实时风控需求是检测到异常交易立刻告警现在业务方会说检测到异常交易后自动判断风险等级决定是拦截、二次验证还是放行并且把决策理由写清楚。前者是计算后者是决策。Flink Agents 这个概念本质上就是在这个缝隙里长出来的东西——它把 Flink 的实时计算能力和 Agent 的自主决策能力缝在了一起让流处理管道不再只是数据的搬运工和计算器而是具备了感知-推理-行动的闭环。我先把话说清楚Flink Agents 不是一个官方发布的独立产品名它更像是一个架构范式指的是以 Flink 为实时数据底座在其上构建具备 Agent 能力的智能处理层。你在热搜里看到的openai agents api、llm powered autonomous agents、agents项目demo这些词和Flink、DataStream、Checkpoint放在一起恰恰说明了这个范式的两个支柱一边是流计算的工程可靠性一边是 LLM 驱动的智能决策。这篇文章适合谁看如果你已经在用 Flink 做实时计算想搞清楚怎么把 Agent 能力接进来或者你在做 LLM 应用发现纯请求-响应模式撑不住高并发实时场景想找一个靠谱的流式底座——那这篇就是写给你的。我会从架构设计、核心细节、实操落地到踩坑排查把这条链路完整拆一遍。全程按我实际做过的项目来讲不玩虚的。2. 架构设计为什么是 Flink 而不是别的底座2.1 实时 Agent 对底座的三条硬要求在决定用 Flink 之前我先梳理了一个实时 Agent 系统对底层计算引擎到底有什么要求。想清楚这个选型就不会拍脑袋。第一条是状态管理。Agent 做决策不是无状态的它需要记住上下文——这个用户过去 5 分钟的行为、这个会话之前几轮的交互、这个设备的历史告警记录。传统做法是把状态丢到 Redis 或数据库里但那样每次决策都要跨网络读写延迟直接上去了。Flink 的Keyed State和Operator State把状态放在计算节点本地配合Checkpoint机制做持久化这是天然优势。第二条是事件时间语义。实时场景里数据乱序是常态Agent 做决策必须基于正确的时序。Flink 的 Watermark 机制能处理乱序事件保证在正确的时间窗口里做正确的判断。这一点很多轻量级流处理框架做不到位。第三条是 Exactly-Once 语义。Agent 的决策往往伴随副作用——发通知、调外部 API、写结果表。如果 Checkpoint 恢复后重复执行可能造成重复扣款、重复告警这类严重问题。Flink 的 Checkpoint 两阶段提交能保证端到端的精确一次。我把这三条要求和其他候选方案做了个对比你可以直接参考能力维度FlinkSpark Streaming纯消息队列函数自建轮询服务状态管理原生 Keyed State本地化需外部存储无需外部存储事件时间Watermark 完整支持微批精度受限不支持不支持Exactly-Once端到端支持部分支持取决于实现难保证背压处理原生背压机制有限无无与 LLM 集成异步 IO 天然适配需额外设计简单但脆弱简单但脆弱结论很清晰要做有状态、有时序、要保证一致性的实时 AgentFlink 是目前最稳的底座。这不是情怀是工程现实。2.2 Agent 层怎么嵌入三种集成模式确定了底座接下来是 Agent 层怎么接。我实践下来有三种模式各有适用场景。模式一同步调用模式。在 Flink 的ProcessFunction里直接调用 LLM 或 Agent 服务拿到结果再往下走。这种模式最简单但有个致命问题——LLM 调用通常几百毫秒到几秒同步阻塞会把整个算子的吞吐拖垮。只适合 QPS 极低的场景比如内部管理类的决策。模式二异步 IO 模式。这是 Flink 官方推荐的与外部系统交互的方式。用AsyncDataStream.unorderedWait或orderedWait把 LLM 调用异步化一个算子可以并发处理成百上千个请求。这是生产环境的主流选择我后面实操部分会重点讲。模式三双流分离模式。把感知流和决策流拆成两个作业感知流负责快速过滤和特征提取把需要决策的事件写到消息队列决策流消费后调用 Agent结果再回流。这种模式解耦最彻底适合决策逻辑复杂、需要独立扩缩容的场景但运维复杂度也最高。我个人的经验是先用异步 IO 模式跑通等决策链路真的复杂到影响主流程了再考虑拆双流。一上来就搞双流大概率是过度设计。2.3 一个关键取舍Agent 决策放流内还是流外这里有个很多人纠结的问题Agent 的推理逻辑到底应该写在 Flink 作业里还是放在外部服务里我的判断标准是看决策是否需要强状态和强时序。如果决策依赖窗口聚合、依赖事件时间对齐、依赖跨事件的会话状态那必须放流内因为只有 Flink 能提供这些语义。如果决策是给一段文本返回一个分类那放外部服务更合适Flink 只负责把请求异步发出去、把结果接回来。实际项目里通常是混合的流内做特征工程和状态维护流外做纯推理。Flink 把加工好的特征通过异步 IO 发给 Agent 服务Agent 服务无状态、可水平扩展返回决策结果。这样两边各司其职扩展性最好。3. 核心细节把 Agent 能力接进 DataStream 的关键技术点3.1 Async I/O 的正确打开方式异步 IO 是整条链路的心脏但很多人用不对。我先讲清楚它的原理再讲怎么用。Flink 的 Async I/O 本质上是把发出请求和处理响应解耦。算子在处理一条记录时不是等结果回来才处理下一条而是把请求发出去后立刻处理下一条结果回来时通过回调触发后续逻辑。这样单个并行度就能同时挂起大量在途请求。关键参数有两个必须理解Capacity容量单个并行度最多同时有多少个在途请求。设太小并发上不去设太大内存压力和下游压力都大。经验值是QPS × 平均响应时间比如目标 QPS 是 1000LLM 平均响应 2 秒那容量至少要 2000。Timeout超时请求多久没回来算失败。LLM 调用波动大建议设成 P99 响应时间的 1.5 到 2 倍。代码骨架大概长这样public class AgentAsyncFunction extends RichAsyncFunctionEvent, Decision { private transient ExecutorService executor; private transient AgentClient agentClient; Override public void open(Configuration parameters) { executor Executors.newFixedThreadPool(20); agentClient new AgentClient(); } Override public void asyncInvoke(Event event, ResultFutureDecision resultFuture) { CompletableFuture.supplyAsync(() - { return agentClient.decide(event); }, executor).whenComplete((decision, throwable) - { if (throwable ! null) { resultFuture.complete(Collections.singleton(Decision.fallback(event))); } else { resultFuture.complete(Collections.singleton(decision)); } }); } Override public void timeout(Event event, ResultFutureDecision resultFuture) { resultFuture.complete(Collections.singleton(Decision.fallback(event))); } }注意asyncInvoke里绝对不能做阻塞操作所有耗时逻辑必须丢到线程池里。我见过有人在asyncInvoke里直接agentClient.decide()同步调用结果异步 IO 完全退化成同步吞吐还不如不用。3.2 Checkpoint 与 Agent 状态的协同这是最容易出问题的地方。Agent 调用是有副作用的——它可能已经对外部系统产生了影响但 Checkpoint 还没完成。如果这时候作业失败重启从上一个 Checkpoint 恢复那些已经产生副作用但没被记录的事件会被重新处理。解决思路有两条第一条是让 Agent 调用幂等。给每个事件生成唯一 IDAgent 服务端根据 ID 去重。这样即使重复调用效果也只有一次。这是最稳妥的做法我强烈推荐。第二条是把 Agent 结果作为状态存下来。在ProcessFunction里用 ValueState 记录这个事件已经决策过了结果是 X恢复后先查状态有就直接用没有才重新调。这样能避免重复调用但状态会膨胀需要配 TTL。ValueStateDecision decisionState getRuntimeContext() .getState(new ValueStateDescriptor(decision, Decision.class)); public void processElement(Event event, Context ctx, CollectorDecision out) { Decision cached decisionState.value(); if (cached ! null) { out.collect(cached); return; } // 走异步决策流程 }提示状态 TTL 一定要设不然 Agent 决策状态会无限增长最后把 TaskManager 内存吃光。我一般设成业务上决策有效期的 2 倍。3.3 背压下的降级策略实时系统一定会遇到流量洪峰。这时候 Agent 调用如果还按正常流程走要么超时堆积要么把下游 LLM 服务打挂。必须有降级。我的降级策略是分层的第一层快速规则兜底。在异步调用之前先用轻量规则判断如果事件明显属于低风险或高确定性类别直接走规则决策不调 Agent。这能过滤掉 60% 到 80% 的流量。第二层超时降级。异步调用超时后返回一个保守决策比如人工复核而不是让事件卡住。第三层熔断。监控 Agent 服务的错误率和响应时间超过阈值就整体切到规则模式等恢复后再切回来。这套组合拳下来即使 LLM 服务抖动主流程也不会崩。4. 实操落地从零搭一条实时 Agent 决策链路4.1 环境准备与依赖配置我按实际项目的最小可用版本来讲。假设你已经有一个能跑的 Flink 集群版本用 1.17 或以上异步 IO 和状态 API 都比较成熟。pom.xml里核心依赖dependency groupIdorg.apache.flink/groupId artifactIdflink-streaming-java/artifactId version1.17.1/version /dependency dependency groupIdorg.apache.flink/groupId artifactIdflink-connector-kafka/artifactId version3.0.2-1.17/version /dependency dependency groupIdorg.apache.flink/groupId artifactIdflink-statebackend-rocksdb/artifactId version1.17.1/version /dependency状态后端我建议直接用RocksDB因为 Agent 决策状态可能比较大内存状态后端扛不住。配置Configuration config new Configuration(); config.setString(state.backend, rocksdb); config.setString(state.checkpoints.dir, hdfs:///flink/checkpoints); config.setString(state.backend.incremental, true);增量 Checkpoint 一定要开不然每次全量快照RocksDB 大的时候能把作业卡死。4.2 数据源接入与预处理数据源用 Kafka这是实时场景的标准配置。接入后先做一轮轻量预处理——解析、过滤、打标。DataStreamEvent events env.addSource( new FlinkKafkaConsumer(raw-events, new EventDeserializer(), kafkaProps) ).assignTimestampsAndWatermarks( WatermarkStrategy.EventforBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner((event, ts) - event.getTimestamp()) );Watermark 的乱序容忍度设 5 秒这是根据实际数据乱序情况调的。设太小会丢迟到数据设太大会增加决策延迟。你可以先统计一下数据源的乱序分布取 P99 作为参考。预处理阶段做三件事字段清洗去掉脏数据、特征提取把原始事件转成 Agent 能理解的结构、规则预判能规则搞定的直接出结果。4.3 Agent 决策算子的完整实现这是核心。我把关键部分拆开讲。先定义决策请求和响应的数据结构要尽量精简因为要序列化传输public class DecisionRequest { private String eventId; private String userId; private MapString, Object features; private String context; } public class Decision { private String eventId; private String action; // 决策动作 private double confidence; // 置信度 private String reason; // 决策理由 }然后是异步决策算子。这里我把前面讲的幂等、状态缓存、降级都整合进去public class AgentDecisionFunction extends KeyedProcessFunctionString, Event, Decision { private ValueStateDecision decisionCache; private transient AsyncAgentClient client; Override public void open(Configuration parameters) { StateTtlConfig ttlConfig StateTtlConfig .newBuilder(Time.hours(2)) .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite) .cleanupInRocksdbCompactFilter(1000) .build(); ValueStateDescriptorDecision descriptor new ValueStateDescriptor(decision-cache, Decision.class); descriptor.enableTimeToLive(ttlConfig); decisionCache getRuntimeContext().getState(descriptor); client new AsyncAgentClient(); } Override public void processElement(Event event, Context ctx, CollectorDecision out) throws Exception { Decision cached decisionCache.value(); if (cached ! null) { out.collect(cached); return; } // 规则预判 Decision ruleDecision RuleEngine.evaluate(event); if (ruleDecision ! null ruleDecision.getConfidence() 0.95) { decisionCache.update(ruleDecision); out.collect(ruleDecision); return; } // 异步调用 Agent client.decideAsync(event).whenComplete((decision, error) - { Decision finalDecision error ! null ? Decision.fallback(event) : decision; try { decisionCache.update(finalDecision); } catch (Exception e) { // 状态更新失败不影响主流程 } out.collect(finalDecision); }); } }注意decisionCache.update在异步回调里执行这时候可能已经跨线程了。Flink 的状态访问不是线程安全的严格来说应该用ctx.timerService()或者把结果通过侧输出流回主线程再更新状态。上面这段是为了讲清楚逻辑生产环境要改成通过队列回传的方式。4.4 结果输出与下游对接决策结果要写到哪里取决于业务。常见的有三种写回 Kafka给下游其他系统消费比如告警系统、工单系统。写数据库用 JDBC 连接器写 MySQL 或 PostgreSQL做决策记录和审计。写 Hive/数据湖做离线分析和模型迭代。写 JDBC 的时候有个坑要提醒批量写入的 batch size 和 flush 间隔要调好。默认值在高吞吐下会产生大量小事务把数据库压垮。我一般设 batch size 500、flush interval 1 秒配合at-least-once语义重复数据靠主键去重。JdbcExecutionOptions execOptions JdbcExecutionOptions.builder() .withBatchSize(500) .withBatchIntervalMs(1000) .withMaxRetries(3) .build();4.5 参数调优的实测数据我把一个实际项目的调优过程记录一下你可以参考。初始配置并行度 4异步容量 100超时 5 秒。结果吞吐只有 800 QPS延迟 P99 达到 8 秒。分析发现瓶颈在异步容量太小大量请求在排队。调整并行度提到 8异步容量提到 500超时改成 3 秒。吞吐上到 4500 QPSP99 降到 1.2 秒。再往上压发现 LLM 服务端成了瓶颈。这时候加了规则预判过滤掉 70% 的简单事件Agent 只处理复杂决策。最终吞吐稳定在 12000 QPSP99 控制在 800 毫秒。调优阶段并行度异步容量超时吞吐P99 延迟初始41005s8008s扩容85003s45001.2s加规则预判85003s12000800ms这组数据说明一个道理实时 Agent 系统的性能瓶颈往往不在 Flink 本身而在 Agent 服务端和调用链路的并发设计。调优要先定位瓶颈别盲目加并行度。5. 常见问题与排查技巧实录5.1 Checkpoint 失败与恢复异常这是实时 Agent 作业最头疼的问题。我整理了几种典型情况和排查路径。情况一Checkpoint 一直超时。通常是状态太大快照写不完。排查步骤先看 Checkpoint 大小和耗时如果状态超过几 GB考虑开增量 Checkpoint如果还是慢检查 RocksDB 的 compaction 是否频繁可以调大state.backend.rocksdb.compaction.level.max-size-level-base。情况二恢复后状态不一致。多半是异步回调里更新状态导致的。前面提过Flink 状态不是线程安全的异步回调跨线程更新状态Checkpoint 时可能拿到中间态。解决办法是把异步结果通过队列回传到主线程在主线程统一更新状态。情况三恢复后重复调用 Agent。这是幂等没做好。检查事件 ID 是否唯一、Agent 服务端是否有去重逻辑。我一般会在 Agent 服务端用 Redis 存最近处理过的 eventIdTTL 设成 Checkpoint 间隔的 3 倍。5.2 异步 IO 的典型故障故障一NullPointerException在回调里。原因是ResultFuture被重复 complete。异步 IO 要求每个请求只能 complete 一次如果超时回调和正常回调都触发了就会报错。解决用一个AtomicBoolean标记是否已 complete。故障二请求堆积导致 OOM。异步容量设太大在途请求把内存吃光。监控numRecordsInFlight指标如果长期接近容量上限说明下游处理不过来要么扩容要么降级。故障三超时率突然飙升。先看 Agent 服务端的响应时间再看网络。我遇到过一次是 DNS 解析慢导致的把 Agent 服务地址改成 IP 直连就好了。还有一次是连接池太小调大maxConnections解决。5.3 排查速查表我把常见问题和对应动作整理成表出问题的时候直接查现象可能原因排查动作Checkpoint 超时状态过大开增量 Checkpoint检查状态 TTL恢复后重复决策幂等缺失检查 eventId 唯一性Agent 端加去重异步回调 NPEResultFuture 重复 complete加 AtomicBoolean 标记吞吐上不去异步容量小或下游瓶颈看 numRecordsInFlight压测下游超时率飙升网络或 Agent 服务问题查 DNS、连接池、服务端响应时间状态无限增长TTL 未配置给所有 ValueState 配 TTL数据不入库JDBC batch 配置不当调 batch size 和 flush 间隔5.4 几个我踩过的坑坑一在asyncInvoke里做序列化。有次我把整个大对象丢进异步调用序列化耗时比调用本身还长。后来改成只传必要字段性能提升明显。异步调用的入参一定要精简。坑二忽略 Watermark 对异步的影响。异步 IO 会打乱事件顺序如果下游依赖顺序必须用orderedWait。但orderedWait会降低吞吐因为要等前面的结果。我的做法是能容忍乱序就用unorderedWait在结果里带上原始时间戳下游自己排序。坑三Agent 决策结果没做版本管理。模型迭代后同样的输入可能给出不同决策导致历史数据无法复现。后来我在决策结果里加了modelVersion字段每次决策记录用的哪个版本方便回溯和对比。坑四忘了监控 Agent 调用成本。LLM 调用是按 token 计费的实时场景下流量大成本很容易失控。我后来加了 token 计数和成本监控设置日预算告警超了就自动降级到规则模式。6. 这套架构还能怎么扩展跑通基础链路后我实际项目里还做了几个扩展效果不错分享给你。扩展一决策结果回流做在线学习。把 Agent 的决策和最终业务结果比如用户是否真的欺诈关联起来形成反馈数据定期微调模型。Flink 的侧输出流很适合做这个把决策事件和结果事件 join 起来写到训练数据表。扩展二多 Agent 协作。复杂决策可以拆成多个 Agent一个负责风险评估一个负责策略生成一个负责合规检查。用 Flink 的广播状态把配置分发给各个 Agent用 Keyed State 维护会话上下文。这其实就是热搜里llm powered autonomous agents说的多智能体协作只不过底座换成了流计算。扩展三决策链路可观测。每个决策从事件进入到结果输出中间经过哪些环节、每步耗时多少、用了哪个模型版本全部打点。用 Flink 的 Metrics 系统上报配合 Grafana 做可视化。出问题的时候能快速定位是哪一环拖了后腿。扩展四A/B 实验框架。新模型上线前用 Flink 的侧输出流把一部分流量分流到新模型对比决策效果。这个在实时场景特别有价值因为你可以快速验证模型在真实流量下的表现而不是等离线评估。我个人在实际操作中的体会是实时 Agent 系统的难点从来不是能不能跑通而是跑通之后怎么保证它一直稳、一直对、一直省。工程上的可靠性设计、成本控制、可观测性这些才是决定项目能不能长期活下去的关键。技术选型上 Flink 给了很好的底座但上面那层 Agent 逻辑怎么写、状态怎么管、降级怎么做全看你对业务的理解深度。多花时间在幂等和降级上比多调几个模型参数值钱得多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询