3. 回放数据采集:请求/响应日志采集、消息队列(Kafka)接入、数据脱敏与存储策略
回放对比的基石是高质量、低延迟的流量采集。本章聚焦于生产环境下的数据采集链路:如何无侵入地捕获请求/响应日志、如何通过 Kafka 实现削峰填谷、以及如何在采集阶段完成合规脱敏与分层存储。
3.1 请求/响应日志采集:从网关到日志管道
风控网关通常基于 OpenResty、Kong、Spring Cloud Gateway 或自研 Netty 网关。采集层需要做到“业务零侵入”与“全量捕获”。
| 采集维度 | 典型字段 | 采集方式 | 注意事项 |
|---|---|---|---|
| 请求元数据 | traceId、userId、deviceId、IP、User-Agent | 网关拦截器/Filter 提取 | traceId 必须在入口处生成,透传至下游 |
| 请求体 | POST JSON、表单、文件上传元信息 | Body 缓存 + 异步写入 | 大 body 需截断(默认 64KB),防止 OOM |
| 响应体 | HTTP 状态码、响应 JSON、错误码 | 响应包装 ResponseWrapper | 仅记录非文件流响应,避免二进制污染 |
| 时间戳 | 请求到达时间、响应完成时间、耗时 ms | System.nanoTime() 打点 | 使用 monotonic clock,避免 NTP 跳变 |
| 上下文标签 | 风控版本号、策略命中标签、降级标记 | ThreadLocal / 请求上下文注入 | 标签必须在响应前完成赋值 |
采集原则: 日志采集必须放在网关过滤器的最后一个执行位(Post Filter),确保所有风控标签、降级标记都已写入上下文。不要在业务 Controller 中采集,避免漏采。
3.2 消息队列(Kafka)接入:高吞吐削峰
生产环境网关 QPS 可达数万至数十万,直接写入数据库或 Elasticsearch 会导致写入瓶颈。Kafka 作为日志总线,承担“缓冲 + 解耦”的核心角色。
3.2.1 架构位置
3.2.2 生产者配置(网关侧)
# 网关 logback-spring.xml 配置 Kafka Appender
<appender name="KAFKA" class="com.github.danielwegener.logback.kafka.KafkaAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
<topic>risk-traffic-raw</topic>
<keyingStrategy>org.apache.kafka.clients.producer.internals.DefaultPartitioner</keyingStrategy>
<deliveryStrategy>async</deliveryStrategy>
<producerConfig>
<key>bootstrap.servers</key>
<value>kafka01:9092,kafka02:9092,kafka03:9092</value>
</producerConfig>
<producerConfig>
<key>acks</key>
<value>1</value> <!-- 网关日志允许丢失少量,选 acks=1 提升吞吐 -->
</producerConfig>
<producerConfig>
<key>compression.type</key>
<value>snappy</value>
</producerConfig>
<producerConfig>
<key>max.request.size</key>
<value>1048576</value> <!-- 1MB -->
</producerConfig>
</appender>
关键参数说明:
- acks=1:只要 Leader 确认即返回,适合日志场景;不要用 acks=all,会大幅增加网关延迟。
- compression.type=snappy:CPU 开销低,压缩比约 3~5x,减少网络带宽。
- max.request.size=1MB:防止超大 body 导致 Kafka 报错,超限 body 应截断或丢弃。
3.2.3 消费者配置(回放服务侧)
spring:
kafka:
consumer:
bootstrap-servers: kafka01:9092,kafka02:9092,kafka03:9092
group-id: risk-replay-consumer
auto-offset-reset: earliest
enable-auto-commit: false
max-poll-records: 500
fetch-max-bytes: 52428800 # 50MB
properties:
isolation.level: read_committed
listener:
type: batch
concurrency: 6 # 与分区数一致
消费策略: 使用 Batch 监听 + 手动 commit。每批 500 条,处理完成后批量写入回放存储。如果某批处理失败,不 commit,等待重试或死信队列。避免单条 ack 带来的性能开销。
3.3 数据脱敏与存储策略
回放数据包含用户手机号、身份证、银行卡等敏感字段。在采集阶段必须完成脱敏,避免明文落入存储层。
3.3.1 脱敏规则
| 敏感字段 | 脱敏方式 | 示例(脱敏前 → 脱敏后) | 备注 |
|---|---|---|---|
| 手机号 | 保留前3后4,中间掩码 | 13812345678 → 138****5678 | 长度不变,便于索引 |
| 身份证号 | 保留前6后4,中间掩码 | 110101199001011234 → 110101****1234 | 保留出生地编码 |
| 银行卡号 | 保留前6后4,中间掩码 | 6222021234567890 → 622202****7890 | 保留发卡行标识 |
| 姓名 | 保留姓氏,名字掩码 | 张三丰 → 张** | 二字姓名保留首字 |
| 邮箱 | @前保留首字符,其余掩码 | test@example.com → t***@example.com | 保留域名 |
| 密码/Token | 直接丢弃 | — | 回放不需要密码 |
3.3.2 脱敏执行时机
最佳实践: 在网关 Filter 中注册一个
DesensitizationFilter,在日志序列化之前遍历 JSON 字段,匹配敏感 key 后执行掩码。使用 JSON Path 或正则匹配,避免反射性能开销。
3.3.3 存储策略:分层与生命周期
| 存储层 | 数据内容 | 保留周期 | 存储引擎 | 索引策略 |
|---|---|---|---|---|
| 热存储 | 最近 7 天全量回放日志 | 7 天 | Elasticsearch | 按 traceId、userId、时间范围建索引 |
| 温存储 | 7~30 天聚合回放日志 | 30 天 | ClickHouse | 按天分区,按 userId 分片 |
| 冷存储 | 30 天以上归档 | 180 天或按合规要求 | HDFS / 对象存储 S3 | 按年月分区,Parquet 列存 |
3.3.4 存储写入代码示例(ES 批量写入)
// 使用 Spring Data Elasticsearch 批量写入
@Autowired
private ElasticsearchRestTemplate esTemplate;
public void batchSaveReplayLogs(List<ReplayLog> logs) {
List<IndexQuery> queries = logs.stream()
.map(log -> new IndexQueryBuilder()
.withId(log.getTraceId())
.withObject(log)
.build())
.collect(Collectors.toList());
esTemplate.bulkIndex(queries, IndexCoordinates.of("risk-replay-logs"));
}
存储避坑:
- 不要使用
_update或upsert,回放日志只追加不修改。 - ES 索引设置
number_of_shards=3,number_of_replicas=1,避免过多分片导致写入变慢。 - ClickHouse 冷数据使用
TTL自动迁移,减少维护成本。
3.4 本章小结
- 采集层:网关 Filter 全量捕获请求/响应,异步写入 Kafka,避免阻塞业务线程。
- 消息队列:Kafka 作为日志总线,生产者 acks=1 + snappy 压缩,消费者 batch 拉取 + 手动 commit。
- 脱敏:在网关内完成,手机号/身份证/银行卡按规则掩码,密码直接丢弃。
- 存储:热 ES(7天)、温 ClickHouse(30天)、冷 HDFS/S3(180天),分层降低成本。
下一阶段预告: 第 4 章将讲解回放数据标准化——如何将采集到的异构日志转换为统一的回放事件模型,为对比引擎提供干净的输入。