风控指标体系:核心指标与数据采集
各位同学,咱们今天聊点实在的。做实时风控,你上来就写规则、上模型,那是空中楼阁。你得先搞清楚一件事——你在衡量什么?
说白了,风控体系的第一步,不是代码,是指标。没有指标,你连系统好不好用、规则有没有效都说不清楚。我见过太多团队,一上来就堆规则,结果上线后异常率飙升,但根本不知道是哪个环节出了问题。嗯,这就是典型的「指标缺失症」。
核心指标:PV、UV、转化率、异常率
实时风控里,有四个指标你绕不开。它们就像汽车的仪表盘,缺一个你都开不稳。
| 指标 | 全称 | 实时含义 | 典型用途 |
|---|---|---|---|
| PV | Page View / 事件次数 | 单位时间内事件发生的总次数 | 衡量流量规模、系统压力 |
| UV | Unique Visitor / 独立用户 | 单位时间内去重后的用户数 | 衡量用户覆盖、黑产团伙规模 |
| 转化率 | Conversion Rate | 目标事件数 / 总事件数 | 衡量业务健康度、规则误杀率 |
| 异常率 | Anomaly Rate | 被判定为异常的事件数 / 总事件数 | 衡量风控规则有效性、攻击强度 |
你可能会问:「PV 和 UV 不是运营指标吗?跟风控有什么关系?」
关系大了。举个例子:某个支付接口的 PV 在 5 秒内从 1000 飙到 10 万,但 UV 只从 800 涨到 900。这说明什么?说明同一批用户在疯狂刷接口——典型的撞库攻击特征。我当年在金融项目里就踩过这个坑,当时只看了 PV 没看 UV,结果被黑产用 200 个账号刷了 50 万次登录,直到数据库连接池被打爆才发现。
核心原则:PV 看规模,UV 看覆盖。两者结合,才能判断流量是「真用户」还是「假机器」。
指标计算口径:别让数字骗了你
指标定义清楚了,但计算口径不一致,照样白搭。我见过最离谱的事:同一个「转化率」,业务部门算的是「支付成功 / 下单」,风控部门算的是「通过风控 / 所有请求」,结果两个数字差了 30%,开会吵了一下午。
所以,计算口径必须统一。实时风控里,常见的口径问题有这几个:
- 时间窗口:是滑动窗口还是固定窗口?比如「过去 5 分钟」和「当前整点 5 分钟」,结果可能差一倍。
- 去重粒度:UV 是按设备 ID 去重,还是按用户 ID 去重?黑产可以伪造设备 ID,但用户 ID 通常更可靠。
- 分母定义:异常率的分母是「所有请求」还是「通过基础校验的请求」?如果包含被限流的请求,异常率会被稀释。
- 延迟容忍:实时计算允许多少延迟?5 秒内算「实时」,还是 1 分钟内也算?这直接影响指标准确性。
避坑指南:我曾经在一个项目中,因为时间窗口用了「固定整点窗口」,导致每分钟的异常率在整点切换时出现断崖式下跌。后来改成滑动窗口,才真正反映了实时趋势。记住:实时风控,一定要用滑动窗口。
埋点与数据采集:指标的源头
指标定义好了,口径统一了,接下来就是数据从哪来。埋点,就是风控系统的「眼睛」。
埋点分两种:
- 前端埋点:在 App、H5、小程序等客户端采集用户行为。比如点击、滑动、页面停留时间。
- 服务端埋点:在后端接口中采集业务数据。比如登录请求、下单请求、支付回调。
做实时风控,我个人习惯优先用服务端埋点。为什么?因为前端数据可以被篡改。黑产可以 hook App、伪造请求参数,你采集到的「用户行为」可能全是假的。服务端数据虽然也有伪造风险,但至少经过了你的业务逻辑校验,可信度高很多。
当然,前端埋点也不是完全没用。比如设备指纹、IP 归属地、浏览器特征,这些信息服务端拿不到,必须靠前端采集。我的建议是:前端埋点做辅助,服务端埋点做主力。
数据采集的实时链路
埋点数据怎么实时传到风控系统?这里有一条标准链路:
客户端/服务端 → 消息队列(Kafka) → 实时计算引擎(Flink) → 风控决策引擎 → 指标存储(Redis/ClickHouse)
每一步都有讲究:
- 消息队列:必须保证高吞吐和低延迟。Kafka 是标配,分区数要根据 PV 预估来设置。我一般按每秒 10 万事件来设计分区。
- 实时计算引擎:Flink 是主流。注意设置 checkpoint 和状态后端,防止任务重启后指标丢失。
- 指标存储:实时指标用 Redis,历史指标用 ClickHouse。Redis 的 key 要设计好过期时间,不然内存会爆。
小技巧:埋点数据一定要加「事件时间」和「采集时间」两个字段。事件时间是用户操作的真实时间,采集时间是服务端收到数据的时间。两者差值超过 10 秒的数据,建议直接丢弃或标记为「延迟数据」。否则你的实时指标会被历史数据污染。
指标体系知识图谱
下面这张图,帮你把本章的核心逻辑串起来。建议保存下来,做架构设计时对照着看。
埋点数据模型设计
最后,给你一个我常用的埋点数据模型模板。直接拿去用,少踩很多坑。
{
"event_id": "uuid", // 事件唯一ID,用于去重
"event_type": "login", // 事件类型:login、order、payment
"event_time": 1700000000000, // 事件发生时间(毫秒时间戳)
"collect_time": 1700000000500, // 服务端采集时间(毫秒时间戳)
"user_id": "u_12345", // 用户ID
"device_id": "d_abcde", // 设备ID
"ip": "192.168.1.1", // 客户端IP
"ua": "Mozilla/5.0...", // User-Agent
"extra": { // 扩展字段,按业务需求添加
"login_type": "password",
"risk_level": "low"
}
}
注意几个关键点:
- event_id 必须全局唯一,用于消息队列的去重。Kafka 虽然保证分区内有序,但不保证全局不重复。
- event_time 和 collect_time 的差值,就是你的「数据延迟」。我一般要求不超过 5 秒,超过的直接丢弃。
- extra 字段用 JSON 格式,方便扩展。但注意不要塞太多字段,否则 Flink 的序列化开销会很大。
个人经验:埋点字段不是越多越好。我见过一个团队,一个埋点事件塞了 50 个字段,结果 Flink 任务的内存直接爆了。我的建议是:核心字段不超过 10 个,扩展字段不超过 5 个。如果业务需要更多信息,用关联查询去补,别一股脑全塞进消息里。