目录
1. Flink基础与核心设计
Flink是Apache软件基金会开源的流处理框架,核心定位是高效、可靠地处理大规模无界/有界数据流,以高性能、低延迟、灵活的窗口操作为核心优势,广泛应用于实时分析、金融风控、物联网等场景。
1.1 Flink简介及发展历程
- 核心定位 :开源流处理框架,支持无界流(实时流) 与有界流(批处理) 统一处理,打破"流批分离"的传统模式。
- 发展背景 :前身是德国柏林工业大学的
Stratosphere
项目,后捐献给Apache基金会并更名为Flink,经过多版本迭代成为大数据实时处理领域的核心工具。 - 典型应用场景 :
- 金融领域:实时交易监控、欺诈检测;
- 物联网:传感器数据流实时分析(如智能设备状态监控);
- 互联网:用户行为实时分析、实时推荐、日志实时处理。
1.2 架构设计概览
Flink采用主从(Master-Slave)架构 ,核心角色包括JobManager
(主节点)、TaskManager
(从节点),辅以Client
、Dispatcher
、ResourceManager
完成作业提交与资源调度,架构交互流程如下:
角色 | 核心职责 |
---|---|
Client | 负责将用户编写的Flink作业(JAR包)编译为数据流图(Dataflow Graph),提交给JobManager。 |
Dispatcher | 接收Client提交的作业,启动JobManager实例,并将作业分配给对应的JobManager。 |
ResourceManager | 管理集群资源(CPU、内存),为TaskManager注册资源,为JobManager分配任务所需的Slot(资源单元)。 |
JobManager | 1. 协调作业执行:将Dataflow Graph优化为执行图(Execution Graph) ,拆分任务并分配给TaskManager; 2. 容错与恢复:触发检查点(Checkpoint),故障时协调任务恢复; 3. 高可用性:支持多实例部署,避免单点故障。 |
TaskManager | 1. 执行具体任务:运行Dataflow中的算子子任务(Subtask),通过Slot 隔离资源; 2. 数据通信:通过Network Manager 实现跨TaskManager的数据传输; 3. 状态管理:存储本地任务的状态,参与检查点快照生成。 |
- 水平扩展能力:可通过增加/减少TaskManager节点动态调整集群处理能力,Slot数量决定并行任务的最大数量。
1.3 核心组件与功能
Flink作业的执行流程依赖"数据源-转换-输出"三大核心组件,形成完整的数据处理链路:
组件 | 功能描述 | 典型实现/场景 |
---|---|---|
Source(数据源) | 从外部系统读取数据,将其转换为Flink可处理的数据流(DataStream/DataSet)。 | - 消息队列:Kafka、RabbitMQ; - 文件系统:HDFS、本地文件; - 数据库:MySQL(CDC同步)、HBase。 |
Transformation(转换) | 对输入数据流进行计算处理,如过滤、映射、聚合、关联等,生成新的数据流。 | - 基础转换:map (映射)、filter (过滤)、flatMap (扁平映射); - 聚合操作:keyBy (按键分组)、sum (求和)、max (最大值); - 窗口操作:window (时间/数量窗口)、windowAll (全局窗口)。 |
Sink(接收器) | 将处理后的数据流输出到外部系统,完成数据落地或进一步消费。 | - 消息队列:Kafka(下游消费); - 数据库:MySQL、Hive、ClickHouse; - 文件系统:HDFS(批量存储); - 监控系统:Prometheus(指标上报)。 |
1.4 数据流模型与并行处理
Flink以无界数据流(Unbounded Stream) 为核心模型,同时支持有界数据流(Bounded Stream,即批处理),关键特性如下:
- 无界流模型:数据持续产生,无固定结束点,Flink通过"增量处理"实时消费数据,而非等待全部数据就绪。
- 并行处理机制 :
- 将数据流按分区(Partition) 拆分,每个分区由一个TaskManager的Slot独立处理;
- 分区规则可自定义(如按
keyBy
的键哈希分区、轮询分区),确保数据均匀分布,提升处理效率。
- 灵活的窗口操作 :
- 支持时间窗口(如滚动窗口、滑动窗口、会话窗口):基于事件时间或处理时间聚合数据(如"每5分钟统计一次订单量");
- 支持数量窗口:基于数据条数聚合(如"每100条数据计算一次平均值");
- 解决无界流中"无法等待全部数据再聚合"的问题,实现实时统计分析。
2. 时间语义在Flink中的应用
时间是流处理的核心维度,直接影响数据排序、窗口触发、状态更新的准确性,Flink提供明确的时间语义与配套机制解决乱序和延迟问题。
2.1 时间概念及重要性
- 核心价值:流数据具有"时序性",时间语义决定了"何时触发计算""如何判断数据完整性",直接影响处理结果的准确性(如"统计当天订单量"需基于事件发生时间,而非处理时间)。
- 时间分类:Flink支持两种核心时间语义,需根据业务场景选择:
时间类型 | 定义 | 优势 | 劣势 | 适用场景 |
---|---|---|---|---|
事件时间(Event Time) | 事件实际发生的时间,由事件自带的时间戳(如日志中的event_time 字段)标识。 |
结果准确,不受系统处理速度、网络延迟影响。 | 需额外携带时间戳,需处理乱序数据。 | 金融风控(精确时间窗口统计)、用户行为分析(按事件发生顺序归因)。 |
处理时间(Processing Time) | 事件被Flink系统接收并开始处理的时间,由TaskManager的系统时钟决定。 | 实现简单,无需时间戳,处理延迟低。 | 分布式环境中节点时钟可能不一致,结果不准确。 | 实时性要求极高、对时间精度要求低的场景(如实时日志打印、临时监控)。 |
2.2 Watermark(水印)机制
Watermark是Flink处理乱序事件的核心技术,本质是"数据流中的特殊标记",用于标识"某个时间点之前的所有事件已到达",触发窗口计算。
2.2.1 Watermark的核心作用
- 解决乱序问题:允许事件延迟到达,通过Watermark定义"数据就绪的时间边界",避免因个别延迟事件导致窗口过早触发。
- 触发窗口计算:当Watermark时间超过窗口的结束时间时,Flink认为窗口内所有事件已到达(或超过最大延迟),触发窗口聚合。
2.2.2 Watermark的生成与传递
- 生成方式 :
- 数据源端生成:Source算子根据事件时间戳,结合"最大允许延迟时间"生成Watermark(如
event_time - 5s
,表示允许5秒延迟); - 示例:若事件时间戳为
10:00:10
,最大延迟5秒,则Watermark为10:00:05
,表示10:00:05
前的事件已全部到达。
- 数据源端生成:Source算子根据事件时间戳,结合"最大允许延迟时间"生成Watermark(如
- 传递机制 :
- Watermark随数据流在算子间传递,下游算子接收Watermark后,更新本地Watermark,并继续向下传递;
- 若算子有多个上游分区,取所有上游Watermark的最小值作为本地Watermark(确保所有分区的数据都已就绪)。
2.3 延迟数据处理策略
2.3.1 延迟数据的产生原因
- 网络延迟:事件从产生端(如传感器、APP)传输到Flink的过程中因网络拥堵延迟;
- 系统故障:数据源(如Kafka)临时下线,恢复后批量补发历史事件;
- 数据积压:TaskManager负载过高,导致事件处理排队延迟。
2.3.2 核心处理策略
Flink提供3种主流延迟数据处理方式,可根据业务需求组合使用:
- 设置最大延迟时间 :
- 在Watermark生成时指定
allowedLateness
(如5秒),窗口触发后仍接收5秒内的延迟事件,更新窗口结果; - 超过最大延迟的事件将被丢弃或路由到侧输出流。
- 在Watermark生成时指定
- 侧输出流(Side Output) :
- 将超过最大延迟的事件路由到独立的"侧输出流",而非直接丢弃;
- 可对侧输出流单独处理(如存储到异常表、人工排查),避免数据丢失。
- 状态更新 :
- 对于有状态计算(如累计计数),延迟事件到达后更新历史状态(需确保状态保留时间足够长);
- 示例:用户当天的登录次数统计,延迟的登录事件仍需更新当天的累计次数。
2.3.3 延迟数据的影响与应对措施
- 主要影响:导致窗口结果临时不准确(如窗口触发时少算延迟事件)、状态不一致(如累计值未更新)。
- 应对措施 :
- 合理设置最大延迟时间(基于历史延迟数据统计,如99%的事件延迟在3秒内,则设为5秒);
- 延长状态保留时间(与最大延迟时间匹配),确保延迟事件可更新历史状态;
- 监控延迟事件比例,若比例过高(如超过10%),需优化网络或扩容TaskManager。
3. 状态管理与检查点机制
流处理中"状态"是记录中间计算结果的核心(如累计计数、会话信息),Flink提供完善的状态管理与检查点机制,确保故障后状态可恢复,数据处理不中断。
3.1 状态管理基本概念及分类
3.1.1 状态的定义
- 状态是Flink算子在处理数据流过程中需要"记住"的中间数据(如"用户A的累计登录次数""窗口内的订单总金额"),用于后续计算或故障恢复。
3.1.2 状态分类
根据状态与"数据键(Key)"的关联关系,Flink将状态分为两类:
状态类型 | 定义 | 支持的状态类型 | 适用场景 |
---|---|---|---|
键控状态(Keyed State) | 与特定"键(Key)"绑定的状态,仅在keyBy 后的算子中使用(如sum 、max ); 每个Key对应独立的状态实例,不同Key的状态相互隔离。 |
- ValueState :存储单个值(如用户余额); - ListState :存储列表(如用户最近10次操作); - MapState :存储键值对(如用户的订单ID与金额映射); - ReducingState /AggregatingState :存储聚合结果。 |
按Key分组的聚合计算(如"按用户ID统计登录次数""按商品ID统计销量")。 |
操作符状态(Operator State) | 与算子实例(Subtask)绑定的状态,不依赖Key,每个算子实例对应一个状态实例; 算子并行度调整时,状态会在新实例间重新分配。 | - ListState :最常用,如Kafka Source的"已消费偏移量"; - UnionListState :合并所有实例的状态(如广播状态)。 |
非Key关联的全局状态(如Kafka消费者的偏移量管理、广播配置信息)。 |
3.2 检查点(Checkpoint)机制
检查点是Flink实现容错的核心技术,通过"定期保存全量状态快照",确保故障后从最近的快照恢复,避免数据重复处理或丢失。
3.2.1 检查点的核心原理
- 定义:按配置的时间间隔(如1分钟)对所有算子的状态、数据流中的Watermark进行快照,存储到持久化介质(如HDFS);
- 目标:故障(如TaskManager宕机)后,Flink从最近的检查点加载所有算子的状态,恢复到故障前的一致状态,重新处理故障后的数据流。
3.2.2 检查点的工作流程
- 触发检查点 :
- JobManager的
CheckpointCoordinator
按配置的间隔(如checkpoint.interval=60000ms
)触发检查点,向所有Source算子发送"检查点屏障(Checkpoint Barrier)"。
- JobManager的
- 同步/异步快照 :
- 算子接收屏障后,对当前状态生成快照:
- 同步快照:暂停数据处理,生成快照后继续处理(简单但影响吞吐量);
- 异步快照:后台生成快照,不暂停数据处理(推荐,Flink默认方式)。
- 算子接收屏障后,对当前状态生成快照:
- 状态存储 :
- 将快照数据写入状态后端(如HDFS、RocksDB),算子向
CheckpointCoordinator
汇报快照成功。
- 将快照数据写入状态后端(如HDFS、RocksDB),算子向
- 确认与恢复 :
- 所有算子快照成功后,
CheckpointCoordinator
标记该检查点为"完成"; - 故障时,JobManager通知所有算子从最近的完成检查点加载状态,恢复处理。
- 所有算子快照成功后,
3.3 状态后端存储选择及配置
状态后端决定状态的存储位置和方式,Flink提供3种状态后端,需根据业务规模选择:
状态后端类型 | 存储位置 | 优势 | 劣势 | 适用场景 |
---|---|---|---|---|
MemoryStateBackend | 存储在TaskManager的JVM堆内存中,检查点快照存储在JobManager内存中。 | 读写速度最快,无I/O开销。 | 状态规模有限(受堆内存限制),JobManager内存不足时易OOM,不支持大状态。 | 小规模测试、非关键性任务(如临时数据过滤)。 |
FsStateBackend | 状态存储在TaskManager堆内存中,检查点快照存储在分布式文件系统(如HDFS)。 | 支持中等规模状态,检查点持久化安全。 | 状态过大时TaskManager堆内存易OOM,不支持超大规模状态。 | 中等数据量的生产任务(如日处理千万级事件)。 |
RocksDBStateBackend | 状态存储在本地RocksDB(嵌入式KV数据库)中,检查点快照存储在分布式文件系统。 | 支持超大规模状态(TB级),本地存储不占用JVM堆内存。 | 读写需序列化/反序列化,性能略低于内存后端。 | 大规模生产任务(如日处理亿级事件、大窗口聚合)。 |
3.3.1 关键配置参数
配置项 | 说明 | 示例值 |
---|---|---|
state.backend |
指定状态后端类型(memory /filesystem /rocksdb ) |
rocksdb |
state.checkpoints.dir |
检查点快照的存储路径(分布式文件系统路径) | hdfs:///flink/checkpoints/ |
checkpoint.interval |
检查点触发间隔(毫秒) | 60000 (1分钟) |
state.ttl.config |
状态保留时间(确保覆盖最大延迟时间,避免延迟事件无法更新状态) | 86400000 (24小时) |
state.backend.rocksdb.localdir |
RocksDB本地数据存储路径(需配置在高性能磁盘如SSD) | /data/flink/rocksdb/ |
3.4 检查点性能优化建议
- 使用异步快照 :
- 配置
state.backend.async=true
(Fs/RocksDB后端默认开启),避免快照生成阻塞数据处理,提升吞吐量。
- 配置
- 优化状态数据结构 :
- 减少状态数据大小(如用
Long
存储计数而非String
,序列化后体积更小); - 避免存储冗余数据(如仅存储必要字段,而非完整事件)。
- 减少状态数据大小(如用
- 选择高性能存储 :
- 检查点快照存储路径选择高性能分布式文件系统(如HDFS的SSD节点),减少快照写入延迟;
- RocksDB本地存储使用SSD,提升状态读写速度。
- 合理调整检查点间隔 :
- 间隔过短(如10秒):快照频率过高,占用I/O和CPU资源;
- 间隔过长(如10分钟):故障后重新处理的数据量过大,恢复时间长;
- 推荐:基于业务容忍的恢复时间设置(如恢复时间不超过5分钟,则间隔设为1-2分钟)。
4. Exactly-Once语义保障机制
"Exactly-Once"是流处理的最高一致性级别,指"每条数据仅被处理一次,无论故障发生与否",Flink通过"事务+快照"结合的方式实现该语义。
4.1 核心前提:幂等性与事务性
4.1.1 幂等性(Idempotence)
- 定义:操作一次与多次执行的效果完全相同,无副作用(如"将用户余额设为100"是幂等的,"给用户余额加100"是非幂等的);
- 作用:即使数据重复处理(如故障恢复后重发),也不会导致结果错误。
4.1.2 事务性(Transactionality)
- 定义:操作要么"完全成功"(所有步骤完成),要么"完全失败"(回滚到初始状态),无中间状态;
- 作用:确保分布式环境中,多算子、多节点的操作一致性(如"更新状态+输出结果"需同时成功或同时失败)。
4.2 Exactly-Once的实现方法
Flink通过两种核心机制实现Exactly-Once,可根据外部系统特性选择:
4.2.1 两阶段提交协议(Two-Phase Commit, 2PC)
适用于支持事务的外部系统(如Kafka、Hive、ClickHouse),核心流程如下:
- 预提交阶段(Prepare) :
- Flink算子处理数据后,将结果写入外部系统的"预提交区"(如Kafka的临时分区、Hive的临时表),不对外可见;
- 所有算子完成预提交后,向
CheckpointCoordinator
汇报"预提交成功"。
- 提交阶段(Commit) :
CheckpointCoordinator
确认所有算子预提交成功后,向所有算子发送"提交指令";- 算子将预提交区的结果"转正"(如Kafka临时分区数据合并到正式分区、Hive临时表重命名为正式表),对外可见;
- 若任一算子预提交失败,
CheckpointCoordinator
发送"回滚指令",算子删除预提交区数据,避免脏数据。
4.2.2 利用外部系统的幂等性
适用于不支持事务但支持幂等写入的系统(如MySQL、Redis):
- 依赖外部系统的幂等特性,即使Flink重复写入数据,系统也会自动去重;
- 示例:
- MySQL:通过主键约束(如订单ID),重复插入时触发主键冲突,忽略重复数据;
- Redis:通过
SET
命令(如SET user:1:login_count 5
),重复执行时覆盖旧值,结果一致。
4.3 分布式快照算法:Chandy-Lamport
Flink检查点的底层实现基于Chandy-Lamport算法,确保分布式环境中"所有算子的状态+数据流位置"生成全局一致的快照:
- 快照触发 :
CheckpointCoordinator
向所有Source算子发送"检查点屏障",屏障随数据流向下游传递; - 局部快照 :
- 算子接收屏障后,暂停数据处理,对当前状态生成局部快照;
- 快照包含"算子状态"和"屏障在数据流中的位置"(如Kafka的消费偏移量);
- 全局一致性 :
- 算子生成局部快照后,将屏障继续传递给下游算子,下游算子重复局部快照流程;
- 所有算子完成局部快照后,全局快照生成成功,确保"状态与数据流位置"一一对应。
4.3.1 异步快照优化
- 为避免快照阻塞数据处理,Flink在Chandy-Lamport算法基础上优化为"异步快照":
- 算子接收屏障后,继续处理数据,同时后台线程生成状态快照;
- 快照生成期间的新数据暂存到"缓冲区",快照完成后再处理缓冲区数据,确保快照一致性与处理吞吐量平衡。
4.4 失败恢复与一致性保证
- 恢复流程:TaskManager宕机后,JobManager从最近的检查点加载所有算子的状态,重置数据流位置(如Kafka消费偏移量回滚到检查点时的位置),重新处理检查点后的数据流;
- 一致性保证 :
- 检查点确保"状态与数据流位置"一致,恢复后重处理的数据不会重复计算(因检查点前的数据已处理并记录状态);
- 结合两阶段提交或外部幂等性,最终实现"每条数据仅被处理一次"的Exactly-Once语义。
5. Flink配置调优策略
合理的配置调优可显著提升Flink作业的吞吐量、降低延迟,避免资源浪费或故障,核心从"集群资源""作业并行度""内存""网络"四个维度优化。
5.1 集群资源配置建议
-
分离计算与存储资源:
- 计算资源(TaskManager):部署在CPU密集型节点,确保算子处理速度;
- 存储资源(状态后端、检查点):部署在I/O密集型节点(如SSD),减少快照写入和状态读写延迟;
- 避免计算与存储资源争用(如TaskManager与HDFS DataNode混部导致磁盘I/O瓶颈)。
-
合理规划集群规模:
- 根据数据量估算TaskManager数量:如日处理10亿条数据,单TaskManager每秒处理1000条,则需
10^9 / (86400 * 1000) ≈ 116
个TaskManager; - 每个TaskManager的Slot数量建议与CPU核心数匹配(如4核CPU设4个Slot),避免CPU上下文切换频繁。
- 根据数据量估算TaskManager数量:如日处理10亿条数据,单TaskManager每秒处理1000条,则需
-
选择高性能存储:
- 状态后端:大规模作业用RocksDB+SSD,中小规模用FsStateBackend+HDFS(SSD节点);
- 检查点:存储到HDFS或对象存储(如S3),确保高可用且I/O性能稳定。
5.2 作业并行度设置技巧
并行度(Parallelism)决定作业的最大并发处理能力,需根据"数据量""算子类型""资源规模"动态调整:
- 按数据量调整 :
- 大数据量(如每秒10万条):提高并行度(如32),分散任务负载;
- 小数据量(如每秒100条):降低并行度(如4),避免资源浪费。
- 避免数据倾斜 :
- 并行度设置需与
keyBy
的键分布匹配,若某Key的数据量过大(如占比超过30%),需拆分Key或调整并行度(如并行度为质数,减少哈希碰撞); - 示例:用户ID哈希分区,并行度设为16,避免某Slot处理过多用户的数据。
- 并行度设置需与
- 动态调整并行度 :
- Flink支持运行时动态调整并行度(通过
Flink UI
或REST API
),无需重启作业; - 高峰期(如电商大促)临时提高并行度,低谷期降低并行度,节省资源。
- Flink支持运行时动态调整并行度(通过
5.3 内存管理与垃圾回收(GC)优化
Flink作业的内存溢出(OOM)和GC频繁是常见问题,需从"内存分配""GC配置""对象管理"三方面优化:
- 合理分配堆内存 :
- 区分"Flink内存"与"JVM内存":
Total Process Memory = Flink Memory + JVM Metaspace + JVM Overhead
; - Flink内存分配:
- 状态密集型作业(如大窗口聚合):增加
state.backend.memory
(RocksDB后端); - 计算密集型作业(如复杂聚合):增加
taskmanager.memory.process.size
(JVM堆内存)。
- 状态密集型作业(如大窗口聚合):增加
- 区分"Flink内存"与"JVM内存":
- 选择高效的垃圾回收器 :
-
JDK 8及以上推荐使用G1 GC (低延迟、高吞吐量),配置:
taskmanager.env.java.opts: "-XX:+UseG1GC -XX:MaxGCPauseMillis=100"
-
避免使用CMS GC(并发标记清除,碎片化严重)或Serial GC(单线程GC,吞吐量低)。
-
- 优化对象分配与回收 :
- 减少频繁创建临时对象(如在
map
算子中避免每次创建新HashMap
); - 使用对象池复用频繁创建的对象(如序列化器、临时缓冲区),降低GC压力。
- 减少频繁创建临时对象(如在
5.4 网络通信与序列化性能提升
网络通信是分布式作业的常见瓶颈(如跨TaskManager的数据传输),需从"缓冲区""序列化""压缩"三方面优化:
- 优化网络缓冲区 :
- 调整网络缓冲区大小:
taskmanager.network.memory.fraction=0.3
(分配30%的TaskManager内存给网络缓冲区); - 增加缓冲区数量:
taskmanager.network.memory.buffers-per-channel=2
,避免数据传输时缓冲区不足导致阻塞。
- 调整网络缓冲区大小:
- 选择合适的序列化方式 :
-
默认Java序列化效率低,推荐使用Kryo序列化(支持自定义序列化器,性能比Java高5-10倍);
-
对复杂数据类型(如自定义POJO),注册Kryo序列化器:
javaenv.getConfig().registerKryoSerializer(User.class, UserKryoSerializer.class);
-
简单数据类型(如String、Long)可使用Avro序列化(Schema化,兼容性好)。
-
- 压缩数据传输 :
- 开启网络数据压缩:
taskmanager.network.compression.enable=true
; - 选择高效压缩算法(如Snappy,压缩比适中,速度快):
taskmanager.network.compression.codec=snappy
; - 减少跨TaskManager的数据传输(如通过
rebalance
调整分区策略,避免数据倾斜导致的大量跨节点传输)。
- 开启网络数据压缩:
6. 总结
6.1 Flink核心优势
- 实时数据处理能力:原生支持无界流处理,可实时消费并处理大规模数据流(如日志、传感器数据),满足实时业务需求。
- 高吞吐量与低延迟 :
- 通过数据并行、流水线执行、异步快照等机制,实现每秒百万级事件处理(Throughput);
- 基于事件时间和Watermark,延迟可控制在亚秒级(Latency),优于Spark Streaming(秒级延迟)。
- 灵活的窗口操作:支持时间窗口、数量窗口、会话窗口等多种窗口类型,可自定义窗口触发逻辑,满足复杂实时分析场景(如实时报表、监控告警)。
- 强大的容错与状态管理 :
- 检查点机制确保故障后状态可恢复,结合Exactly-Once语义,数据处理一致性高;
- 支持Keyed State和Operator State,可灵活管理中间计算结果,适配有状态计算场景(如累计统计、会话保持)。
6.2 典型应用场景
场景 | 应用描述 | Flink优势体现 |
---|---|---|
实时日志分析 | 处理系统/应用的实时日志,监控异常(如ERROR日志告警)、分析用户行为(如页面访问路径)。 | 高吞吐量处理日志流,低延迟触发告警。 |
物联网(IoT)数据流处理 | 接收传感器(如温度、湿度、位置传感器)的实时数据,实时监控设备状态、预测故障。 | 支持无界流处理,可处理海量传感器数据。 |
金融科技风控 | 实时监测交易数据(如信用卡消费、转账),识别欺诈行为(如异地登录、大额异常转账)。 | Exactly-Once语义确保交易数据不重复处理,低延迟满足风控实时性要求。 |
电商实时推荐系统 | 处理用户实时行为(如浏览、加购、下单),结合机器学习模型生成个性化推荐。 | 灵活的窗口操作统计用户近期行为,低延迟更新推荐结果。 |