1. AI 应用进入生产阶段,实时数据智能为什么成了分水岭
过去两年,大家聊 AI 应用,聊得最多的是模型能力:参数多大、推理多强、上下文多长。但真正把 AI 应用推到生产环境里的人会发现,模型能力只是入场券,决定一个 AI 应用能不能扛住真实业务压力的,往往是另一件事——实时数据智能。
我所在的团队从 2023 年底开始把 AI Agent 往生产系统里搬,踩了无数坑之后得出一个结论:一个 AI 应用在 Demo 阶段表现惊艳,到了生产环境却频繁翻车,十有八九不是模型不行,而是它拿到的数据是“过期”的、“断裂”的、“没有上下文”的。用户问一句“我上个月的订单为什么还没发货”,Agent 如果只能读到三天前的缓存数据,那它给出的回答再流畅也是错的。
这就是“实时数据智能”要解决的问题。它不是一个新概念,而是把实时数据采集、流式处理、上下文注入、Agent 编排、异步通信这几件事串起来,让 AI 应用在生产的每一秒都能拿到最新、最准、最相关的数据,并据此做出决策。适合谁来参考?如果你正在做 AI Agent 开发、云原生架构设计、或者负责把 AI 能力落地到真实业务里,这篇内容应该能帮你少走一些弯路。
我下面会从整体设计思路、核心细节、实操过程、常见问题四个维度,把我们在生产环境里摸出来的经验完整拆开讲。不是理论科普,是能直接抄作业的那种。
2. 整体设计思路:为什么实时数据智能是 AI 生产化的核心命题
2.1 从“模型中心”到“数据中心”的范式转移
早期做 AI 应用,大家的思路是“模型中心”:先把模型调好,再想办法把数据喂进去。这个思路在 Demo 阶段没问题,因为 Demo 的数据量小、变化慢、场景单一。但到了生产环境,数据是持续变化的,用户行为是实时的,业务状态是动态的。这时候如果还以模型为中心,就会出现一个尴尬的局面:模型很聪明,但它看到的世界是静止的。
我们内部做过一个对比实验。同一个 Agent,一套接的是每天凌晨批量同步的数据,另一套接的是实时数据流。在“查询当前库存”这个任务上,批量方案的准确率只有 67%,而实时方案达到了 94%。差距不在模型,在数据的新鲜度。
所以整体设计思路的第一个转变,就是从“模型中心”转向“数据中心”。模型是引擎,数据是燃料,而实时数据智能就是那套让燃料持续、稳定、精准供给的系统。这个系统要解决三个问题:数据从哪来、怎么处理、怎么在正确的时刻送到 Agent 面前。
2.2 实时数据智能的三层架构拆解
我们把整个体系拆成三层,这样每一层的职责清晰,出了问题也好定位。
第一层是数据接入层。这一层负责从各种源头把数据捞上来,包括业务数据库的变更日志、消息队列的事件流、第三方接口的回调、用户端的埋点上报。关键点是“变更即捕获”,而不是“定时去拉”。我们用的是变更数据捕获(CDC)加消息队列的组合,数据库一有变更,几毫秒内就能推到下游。
第二层是流式处理层。数据进来之后不能直接扔给 Agent,得先做清洗、聚合、关联、打标。比如一个订单状态变更事件,需要关联到用户信息、商品信息、物流信息,才能形成 Agent 能理解的上下文。这一层我们用的是流处理引擎,支持窗口计算和状态管理,保证在乱序、延迟、重复的情况下还能算出正确结果。
第三层是上下文服务层。这一层是离 Agent 最近的,负责把处理好的数据包装成 Agent 能直接消费的上下文。它要解决的是“在正确的时刻,把正确的数据,以正确的格式,送给正确的 Agent”。这里面涉及上下文缓存、优先级调度、过期策略等细节,后面会展开讲。
这三层不是简单的串联,而是有反馈回路的。Agent 的消费行为会反过来影响数据的优先级和缓存策略,形成一个动态调整的闭环。
2.3 为什么异步通信是绕不开的选择
很多人一开始会想:Agent 需要数据,那就同步调用接口拿呗,简单直接。我们在早期也这么干过,结果就是 Agent 的响应时间被数据接口的延迟绑架了。一个查询要等三个接口,每个接口 200 毫秒,加起来就是 600 毫秒,再加上模型推理的时间,用户体验直接崩掉。
异步通信的核心价值,是把“等数据”这件事从关键路径上挪走。Agent 发起一个任务,不需要同步等待所有数据就绪,而是先拿到一个任务句柄,数据在后台异步准备好之后,通过事件通知 Agent 继续处理。这样 Agent 的响应时间就只取决于模型推理和上下文组装,而不是数据接口的抖动。
我们实测下来,改成异步之后,Agent 的 P99 响应时间从 2.3 秒降到了 800 毫秒左右。更重要的是,稳定性大幅提升,因为某个数据源慢了不会拖垮整个链路,只会影响那一个任务。
2.4 Agent 编排与实时数据的协同设计
Agent 不是孤立存在的,生产环境里往往是多个 Agent 协作完成一个任务。比如一个客服场景,可能有意图识别 Agent、知识检索 Agent、订单查询 Agent、回复生成 Agent。这些 Agent 之间需要共享实时数据,但又不能互相阻塞。
我们的做法是引入一个“上下文总线”的概念。每个 Agent 把自己的数据需求注册到总线上,总线负责协调数据的获取和分发。当一个 Agent 需要另一个 Agent 的中间结果时,不需要直接调用,而是通过总线订阅。这样 Agent 之间是松耦合的,可以独立扩缩容,也可以独立升级。
这个设计还有一个好处:可观测性。所有数据的流动都经过总线,我们可以清楚地看到每个 Agent 在什么时刻消费了什么数据,出了问题能快速定位是数据的问题还是 Agent 逻辑的问题。
3. 核心细节解析与实操要点
3.1 数据接入:变更捕获与事件流的正确打开方式
数据接入这一层,最容易踩的坑就是“全量拉取”。很多团队为了省事,直接定时查数据库,把整张表拉出来。数据量小的时候没问题,一旦上了生产,数据库压力大不说,延迟还高。我们曾经有个业务,定时任务每 5 分钟跑一次,结果用户改了地址之后,Agent 还在用旧地址回复,客诉直接爆了。
正确的做法是变更数据捕获。以 MySQL 为例,可以解析二进制日志,把每一次插入、更新、删除都捕获成事件。这些事件推到消息队列里,下游按需消费。这样做的好处是延迟低(通常毫秒级)、对源库压力小、而且天然支持增量。
实操要点有几个。第一,二进制日志的格式要设成行模式,否则拿不到变更前后的完整数据。第二,消息队列的分区键要选好,通常用业务主键,保证同一个实体的变更有序。第三,要有死信队列和重试机制,网络抖动或者下游故障时不能丢数据。
注意:变更捕获工具的选择要考虑源库类型和运维成本。如果团队没有专门的中间件运维能力,优先选托管服务,别自己搭,后期维护成本远超预期。
3.2 流式处理:窗口、状态与乱序数据的处理策略
数据进了流处理层,核心要解决的是“怎么算”。实时数据往往是乱序的,事件产生的时间和到达处理引擎的时间不一致。比如用户在地铁里操作,网络延迟可能导致事件晚到几十秒。如果处理逻辑不考虑乱序,算出来的结果就是错的。
我们的做法是用事件时间加水位线。每个事件带上产生时的时间戳,处理引擎根据水位线判断“这个时间点之前的数据应该都到齐了”,然后再触发计算。水位线设得太紧,会丢晚到的数据;设得太松,结果输出会延迟。我们一般设 30 秒到 1 分钟,具体看业务对延迟的容忍度。
窗口计算也是关键。常见的窗口有滚动窗口、滑动窗口、会话窗口。订单统计一般用滚动窗口,比如每 5 分钟统计一次;用户行为分析用会话窗口,把一次连续操作归到一起。窗口的状态要持久化,否则引擎重启后状态丢失,结果就不准了。
还有一个容易被忽略的点是幂等性。流处理引擎可能会重复消费消息,如果计算逻辑不是幂等的,就会重复计数。我们的做法是给每个事件带唯一 ID,处理时做去重,保证即使重复消费也不影响结果。
3.3 上下文服务:让 Agent 拿到“刚刚好”的数据
上下文服务是离 Agent 最近的一层,也是最容易做砸的一层。常见的问题是“给太多”或者“给太少”。给太多,上下文超长,模型推理变慢,成本还高;给太少,Agent 信息不足,回答质量下降。
我们的策略是“按需组装、分级缓存”。每个 Agent 在注册时声明自己需要哪些数据字段,上下文服务根据声明去取数。取数的时候分三级:热数据放内存缓存,毫秒级读取;温数据放分布式缓存,几毫秒到几十毫秒;冷数据回源查询,几百毫秒。大部分请求都能命中热数据,因为 Agent 的上下文需求往往集中在最近几分钟的数据上。
过期策略也很重要。不同数据的有效期不一样,库存数据可能几秒就过期,用户画像数据可能几小时才更新一次。我们给每类数据配了独立的 TTL,过期后自动失效,下次请求时重新拉取。这样既保证了新鲜度,又不会频繁回源。
提示:上下文组装时一定要做裁剪。模型能处理的上下文长度是有限的,把最相关的数据放在前面,不相关的直接丢掉。我们实测发现,上下文从 8000 token 压到 3000 token,回答质量几乎没降,但推理速度提升了一倍多。
3.4 Agent 并发扛压:从单机到分布式的演进路径
Agent 怎么扛并发,是生产环境绕不开的问题。单机部署的时候,并发一上来就排队,响应时间飙升。我们的演进路径分三步。
第一步是水平扩展。把 Agent 做成无状态的,前面挂负载均衡,后面按需加实例。这一步能解决大部分并发问题,但前提是 Agent 本身不存状态,所有状态都外置到上下文服务或者缓存里。
第二步是异步化。前面提到过,把数据获取从关键路径挪走。Agent 接到请求后先返回一个任务 ID,后台异步处理,处理完了通过长连接或者轮询通知客户端。这样 Agent 的并发能力不再受数据接口延迟的影响。
第三步是分级降级。并发极高的时候,不是所有请求都值得用完整链路处理。我们做了优先级队列,高优先级请求走完整链路,低优先级请求走简化链路,比如跳过某些非核心的数据查询。这样在峰值时能保住核心业务的体验。
实测数据:单实例 Agent 在同步模式下大概能扛 50 QPS,异步化之后能到 300 QPS,水平扩展到 10 个实例后能稳定在 2500 QPS 以上。
3.5 Agent 安全与数据边界:生产环境不能碰的红线
Agent 进入生产,安全是底线。这里说的安全不是指模型本身的安全对齐,而是数据边界和权限控制。Agent 能访问哪些数据、能执行哪些操作,必须有明确的约束。
我们的做法是“最小权限加动态鉴权”。每个 Agent 有自己的身份,访问数据时带上身份凭证,上下文服务根据身份判断是否有权限。比如订单查询 Agent 只能读订单数据,不能读用户支付信息。动态鉴权是指权限不是静态配置的,而是根据请求的上下文动态判断,比如同一个 Agent 在处理不同用户请求时,只能访问该用户自己的数据。
还有一个容易忽略的点是数据脱敏。Agent 的上下文里如果包含敏感信息,比如手机号、地址,要在组装上下文时就做脱敏,而不是等模型输出后再处理。因为模型可能会把敏感信息记在中间状态里,事后处理来不及。
注意:Agent 的日志也要做脱敏。我们曾经在排查问题时发现日志里打印了完整的用户信息,这是很大的隐患。日志脱敏要在写入前做,不能事后清洗。
4. 实操过程与核心环节实现
4.1 环境准备与基础组件选型
动手之前,先把基础组件定下来。我们的选型原则是:成熟优先、托管优先、团队熟悉优先。下面是我们实际用的组合,供参考。
| 组件类型 | 选型 | 理由 |
|---|---|---|
| 消息队列 | Kafka | 吞吐高、生态成熟、支持分区有序 |
| 变更捕获 | Debezium | 支持多种数据库、社区活跃 |
| 流处理 | Flink | 事件时间语义完善、状态管理强 |
| 缓存 | Redis | 低延迟、支持多种数据结构 |
| 上下文服务 | 自研 + gRPC | 需要定制化组装逻辑 |
| Agent 运行时 | 容器化部署 | 弹性扩缩容、隔离性好 |
环境准备阶段有几个细节要注意。Kafka 的分区数要根据峰值吞吐预估,一般单分区能扛几 MB/s,留一倍余量。Flink 的并行度要和 Kafka 分区数对齐,否则会有消费倾斜。Redis 要配持久化和主从,避免单点故障。
4.2 数据管道的搭建与参数调优
数据管道从源库到 Agent,中间经过变更捕获、消息队列、流处理、缓存四个环节。每个环节都有参数要调。
变更捕获这边,快照模式要选对。首次启动时做全量快照,之后切到增量。快照期间要保证不丢变更,Debezium 会自动处理,但要确保数据库的二进制日志保留时间足够长,否则快照还没做完日志就被清理了。
消息队列这边,生产者的确认模式设成 acks=all,保证消息不丢。消费者的偏移量提交策略用自动提交加手动兜底,避免重复消费。分区数我们设的是 12,对应 12 个消费者实例,实测能扛住每秒 5 万条事件。
流处理这边,检查点间隔设成 1 分钟,状态后端用 RocksDB,保证大状态下也能稳定运行。水位线设成 30 秒,兼顾延迟和准确性。并行度设成 12,和 Kafka 分区对齐。
缓存这边,热数据的 TTL 设成 5 秒,温数据设成 5 分钟,冷数据不缓存直接回源。Redis 用集群模式,3 主 3 从,保证高可用。
4.3 Agent 上下文组装的完整代码路径
上下文组装是核心环节,我贴一段简化后的伪代码,说明整个流程。
def build_context(agent_id, request): # 1. 解析 Agent 的数据需求声明 schema = get_agent_schema(agent_id) # 2. 按优先级获取数据 context = {} for field in schema.fields: # 先查热缓存 value = hot_cache.get(field.key) if value is None: # 热缓存没有,查温缓存 value = warm_cache.get(field.key) if value is None: # 温缓存也没有,回源查询 value = source_query(field.key) # 回源结果写入温缓存 warm_cache.set(field.key, value, ttl=300) context[field.name] = value # 3. 数据脱敏 context = desensitize(context, schema.sensitive_fields) # 4. 裁剪上下文,按相关性排序 context = trim_context(context, max_tokens=3000) # 5. 组装成模型可消费的格式 return format_for_model(context)这段代码的关键点在于:分级缓存减少回源、脱敏在组装时做、裁剪控制上下文长度。每一步都有对应的监控指标,比如缓存命中率、回源耗时、裁剪比例,方便定位问题。
4.4 异步通信链路的实现与超时处理
异步通信的核心是任务队列加回调通知。Agent 接到请求后,生成一个任务 ID,把任务扔到队列里,立即返回任务 ID。后台工作进程从队列取任务,处理完了把结果写到结果存储,然后通过长连接推送通知。
超时处理是重点。我们设了三层超时:单步操作超时、任务总超时、客户端等待超时。单步操作超时比如查数据 500 毫秒没返回就放弃,任务总超时比如整个任务 10 秒没完成就标记失败,客户端等待超时比如客户端等 15 秒没收到通知就主动查询。
超时后的处理策略也不一样。单步超时可以降级,比如跳过这个数据源用默认值;任务超时要回滚,把已经产生的副作用清理掉;客户端超时要保证幂等,客户端重试时不会重复执行。
4.5 压测与容量规划的实际数据
上线前一定要压测。我们的压测分三轮:单组件压测、链路压测、全链路压测。
单组件压测是分别压 Kafka、Flink、Redis、Agent,找出各自的瓶颈。链路压测是把数据管道串起来压,看端到端延迟。全链路压测是模拟真实业务场景,包括并发用户、数据变更、Agent 调用。
实测数据:单条数据从源库变更到 Agent 可消费,P50 延迟 120 毫秒,P99 延迟 450 毫秒。Agent 端到端响应 P50 是 600 毫秒,P99 是 1.8 秒。峰值 QPS 2500 时,系统资源利用率在 70% 左右,还有余量。
容量规划按峰值 QPS 的 1.5 倍预留资源。比如峰值 2500 QPS,按 3750 QPS 规划。Kafka 分区、Flink 并行度、Agent 实例数都按这个来算。
5. 常见问题与排查技巧实录
5.1 数据延迟高:从源头到消费的逐段排查
数据延迟高是最常见的问题。排查思路是从源头开始,逐段看时间戳。
先看源库的变更时间,再看变更捕获的捕获时间,两者之差是捕获延迟。然后看消息队列的生产时间和消费时间,之差是队列延迟。再看流处理的接收时间和输出时间,之差是处理延迟。最后看缓存的写入时间和 Agent 的读取时间,之差是缓存延迟。
我们遇到过几次延迟高的情况。一次是源库的二进制日志磁盘满了,写入变慢,导致捕获延迟飙升。一次是 Kafka 某个分区热点,消费跟不上。还有一次是 Flink 的检查点时间太长,阻塞了数据处理。每次都是靠逐段排查定位的。
提示:给每一段都加上延迟监控指标,出问题时直接看哪个指标异常,比盲目排查快得多。
5.2 上下文不一致:缓存与源数据冲突的解决
上下文不一致是指 Agent 拿到的数据和源数据对不上。常见原因是缓存没及时失效。比如用户改了地址,源库更新了,但缓存还是旧值。
解决办法有两个。一是缩短 TTL,让缓存更快过期。但 TTL 太短会增加回源压力。二是用变更事件主动失效缓存。源库变更后,发一个失效事件,缓存收到后立即删除对应 key。我们用的是第二种,实测下来一致性能做到秒级。
还有一种不一致是流处理的状态和源数据不一致。比如流处理算出来的订单总数和源库对不上。这通常是乱序数据或者重复消费导致的。解决办法是用水位线加去重,保证计算逻辑的准确性。
5.3 Agent 响应超时:异步链路的断点定位
Agent 响应超时,要定位是哪个环节卡住了。我们的做法是给每个任务打上追踪 ID,从请求进入到结果返回,每个环节都记录时间戳。
常见断点有几个。一是数据获取卡住,某个数据源响应慢。二是模型推理卡住,上下文太长或者模型负载高。三是结果通知卡住,长连接断了或者客户端没收到。
排查时先看追踪日志,找到耗时最长的环节。如果是数据获取,看是哪个数据源,然后针对性优化。如果是模型推理,看上下文长度和模型队列深度。如果是通知,看连接状态和重试次数。
5.4 并发上不去:资源瓶颈与锁竞争的识别
并发上不去,通常是资源瓶颈或者锁竞争。资源瓶颈看 CPU、内存、网络、磁盘的利用率,哪个先到 100% 就是瓶颈。锁竞争看线程等待时间,如果大量线程在等锁,就是竞争严重。
我们遇到过一次并发上不去,排查发现是 Redis 的连接池太小,大量请求在等连接。把连接池从 50 调到 200 之后,并发直接翻倍。还有一次是 Agent 里的某个全局锁,改成无锁数据结构后解决。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 数据延迟高 | 捕获慢/队列积压/处理阻塞 | 逐段看时间戳 | 针对性扩容或调参 |
| 上下文不一致 | 缓存未失效/状态错误 | 对比缓存与源数据 | 变更事件失效缓存 |
| Agent 超时 | 数据慢/模型慢/通知慢 | 追踪 ID 定位 | 优化对应环节 |
| 并发上不去 | 资源瓶颈/锁竞争 | 看资源利用率和线程等待 | 扩容或改无锁 |
| 数据丢失 | 确认模式不对/重试不足 | 查死信队列 | 改 acks=all 加重试 |
| 重复消费 | 偏移量提交策略问题 | 查消费偏移量 | 幂等处理加去重 |
5.6 独家避坑经验:那些文档里不会写的事
最后分享几个我们踩过的坑,文档里基本不会写。
第一个坑是时区。源库、消息队列、流处理、Agent 可能在不同时区,时间戳对不上会导致窗口计算错误。我们的做法是全部用 UTC,展示时再转本地时区。
第二个坑是序列化。不同组件用的序列化格式可能不一样,JSON、Avro、Protobuf 混用会导致解析失败。我们统一用 Protobuf,性能好且向后兼容。
第三个坑是监控盲区。有些环节没有监控,出问题时只能靠猜。我们的原则是“没有监控的环节等于不存在”,每个环节都必须有指标。
第四个坑是压测数据不真实。用假数据压测和用真实数据压测,结果可能差很多。真实数据有热点、有长尾、有异常值,压测时一定要用真实数据或者高度仿真的数据。
第五个坑是忽略冷启动。系统刚上线时缓存是空的,所有请求都回源,压力巨大。我们的做法是上线前预热缓存,把热点数据提前加载进去。
这些经验都是真金白银换来的,希望能帮你少走一些弯路。实时数据智能这件事,说起来复杂,但拆开来看,无非是把数据接入、流处理、上下文服务、Agent 编排这几件事做扎实。每一件都有成熟的方案,关键是组合起来的时候要考虑清楚数据流、控制流、异常流。我在实际项目里的体会是,先把数据链路打通,再优化 Agent 逻辑,顺序反了会事倍功半。