3. 数据采集层:日志采集与消息队列选型
数据采集层,说白了就是风险监控系统的「耳朵」和「嘴巴」。耳朵负责听——从各个业务系统把日志捞上来;嘴巴负责说——把数据喂给下游处理引擎。这一层要是没搭好,后面再牛的算法也白搭。
我这些年做过的风险监控项目,踩坑最多的就是数据采集层。不是丢数据,就是数据格式乱七八糟,搞得下游解析逻辑改了一遍又一遍。今天咱们就把这块掰开揉碎了讲清楚。
3.1 日志采集工具:Filebeat vs Flume
选采集工具,我个人的习惯是先看场景。你想想看,如果你的业务系统全是容器化部署,日志都往标准输出打,那Filebeat就是天选之子。反过来,如果你们还在用老一套的Java应用,日志文件散落在各个服务器上,Flume可能更顺手。
核心区别一句话总结:
- Filebeat:轻量、Go语言编写、资源占用极低,适合容器和云原生场景
- Flume:Java生态、功能丰富、支持复杂的数据流拓扑,适合传统架构
我在项目中遇到过这么个事:有个金融客户,他们的交易日志每秒产生大概2万条,每条日志平均1.5KB。一开始他们用Flume,结果发现采集节点CPU飙到80%以上。后来换成Filebeat,CPU直接降到15%。为什么?Filebeat是Go写的,没有JVM那一套开销,而且它内部做了零拷贝优化。
Filebeat 配置示例
# filebeat.yml
filebeat.inputs:
- type: container
paths:
- /var/lib/docker/containers/*/*.log
json.keys_under_root: true
json.add_error_key: true
output.kafka:
hosts: ["kafka1:9092", "kafka2:9092"]
topic: "risk-log-topic"
partition.round_robin:
reachable_only: true
required_acks: 1
compression: gzip
max_message_bytes: 10485760
嗯,这里要注意:required_acks 我建议设成1,别设成all。设成all虽然数据更安全,但吞吐量会掉一半。风险监控场景下,偶尔丢几条日志是可以接受的,但延迟高了会直接影响实时性。
Flume 配置示例
# flume.conf
agent.sources = tailSource
agent.channels = kafkaChannel
agent.sinks = kafkaSink
agent.sources.tailSource.type = spooldir
agent.sources.tailSource.spoolDir = /var/log/risk-app
agent.sources.tailSource.fileHeader = true
agent.sources.tailSource.batchSize = 1000
agent.channels.kafkaChannel.type = memory
agent.channels.kafkaChannel.capacity = 10000
agent.channels.kafkaChannel.transactionCapacity = 1000
agent.sinks.kafkaSink.type = org.apache.flume.sink.kafka.KafkaSink
agent.sinks.kafkaSink.kafka.topic = risk-log-topic
agent.sinks.kafkaSink.kafka.bootstrap.servers = kafka1:9092,kafka2:9092
agent.sinks.kafkaSink.kafka.producer.acks = 1
我的经验之谈:Flume的channel选型很关键。memory channel快但会丢数据,file channel安全但慢。我一般建议用memory channel + 上游日志保留24小时,这样即使Flume挂了,还能从原始日志重新采集。
3.2 消息队列选型:为什么是Kafka?
你可能要问:消息队列那么多,RabbitMQ、RocketMQ、Pulsar,为什么风险监控系统几乎清一色选Kafka?
原因很简单:风险监控的核心诉求是「高吞吐」和「顺序性」。Kafka的日志结构存储,天生就是为这种场景设计的。我曾经做过压测,单机Kafka能扛住每秒50万条消息写入,延迟还在10ms以内。换成RabbitMQ,同样配置下5万条就开始丢消息了。
Kafka在风险监控中的关键配置:
- 分区数:建议等于消费组内消费者数量的2-3倍,方便水平扩展
- 副本因子:至少2,保证数据不丢
- 日志保留时间:72小时,给数据回溯留足空间
- 压缩方式:lz4或zstd,压缩比高且CPU开销小
Kafka 核心配置详解
# server.properties
# 分区数,根据业务量调整
num.partitions=12
# 日志保留72小时
log.retention.hours=72
# 每个分区日志最大1GB后滚动
log.segment.bytes=1073741824
# 副本因子
default.replication.factor=2
# 最小同步副本数
min.insync.replicas=1
# 消息最大大小,风险日志可能包含大payload
max.message.bytes=10485760
# 压缩类型
compression.type=lz4
我曾经踩过的坑:有次把 min.insync.replicas 设成了2,结果一台broker挂了之后,所有写入都报错。因为副本因子是2,最小同步副本也是2,那台broker一挂,就永远凑不齐2个同步副本了。正确的做法是:副本因子设3,最小同步副本设2,这样挂一台还能正常写。
3.3 数据格式标准化
这一节我觉得是最容易被忽视的。很多团队上来就搞采集、搞Kafka,结果数据格式五花八门——有的用JSON,有的用纯文本,有的甚至用自定义二进制。下游解析的时候,光适配格式就花了一半时间。
我建议统一用JSON格式,原因有三:
- 可读性强,调试方便
- 几乎所有语言都有原生支持
- schema可以灵活扩展,不影响老字段
标准日志格式定义
{
"timestamp": "2024-01-15T10:30:00.123Z",
"log_level": "ERROR",
"source": {
"service": "payment-service",
"host": "10.0.1.23",
"instance": "pod-payment-7f8b9c"
},
"event": {
"type": "transaction_failed",
"code": "ERR_TX_1001",
"message": "支付超时,交易ID: TX202401151030001"
},
"context": {
"user_id": "u_123456",
"transaction_id": "TX202401151030001",
"amount": 2999.00,
"currency": "CNY",
"risk_score": 0.85
},
"tags": ["high_risk", "payment", "timeout"],
"extra": {}
}
标准化要点:
- timestamp:必须用ISO 8601格式,带时区信息
- source:记录来源服务、主机、实例,方便定位问题
- event:事件类型和错误码,用于规则匹配
- context:业务上下文,风险评分等关键信息
- tags:标签系统,用于快速过滤和分类
你可能觉得这样定义太啰嗦。但我在项目中吃过亏——有一次线上出了个风险事件,日志里只有一条「交易失败」的消息,没有用户ID、没有交易金额,根本没法做根因分析。从那以后,我强制要求所有日志必须包含完整的context信息。
数据格式校验
光定义格式还不够,还得有校验机制。我一般会在采集层加一个轻量级的schema校验,用JSON Schema或者Avro都行。
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["timestamp", "log_level", "source", "event"],
"properties": {
"timestamp": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$"
},
"log_level": {
"type": "string",
"enum": ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"]
},
"source": {
"type": "object",
"required": ["service", "host"]
},
"event": {
"type": "object",
"required": ["type", "code"]
}
}
}
小技巧:校验不通过的日志不要直接丢弃,可以发到一个专门的「dead letter topic」里。我习惯保留7天,方便排查是哪个业务系统在乱打日志。等业务方修好了,再把这些数据重新处理一遍。
3.4 整体架构图
说了这么多,咱们用一张图把整个数据采集层的架构串起来。这张图我画了很多遍,每次给新团队培训都用它。
这张图把整个数据流讲清楚了:从数据源出发,经过采集层做格式校验和标准化,然后进入Kafka消息队列,最后被消费层取走处理。如果格式校验不通过,数据会被打入死信队列,不会影响主流程。
总结一下本章核心要点:
- 采集工具选型:容器场景用Filebeat,传统场景用Flume
- 消息队列选型:高吞吐场景首选Kafka,注意分区和副本配置
- 数据格式标准化:统一JSON格式,包含timestamp、source、event、context四个核心字段
- 异常处理:格式校验不通过的数据打入死信队列,不要直接丢弃
数据采集层搭好了,后面的实时计算和规则引擎才能跑得顺畅。下一层咱们聊聊怎么用Flink做实时风险计算,那又是另一番天地了。
公众号:蓝海资料掘金营,微信deep3321