3、数据采集与埋点:客户端埋点技术、服务端拦截器、日志采集与Agent部署

好,咱们进入第三个章节。数据采集与埋点。

说实话,这是整个风险链路追踪里最「脏」最「累」的活。你算法再牛,模型再准,数据采不上来,一切都是白搭。我见过太多项目,架构图画得漂漂亮亮,一到埋点环节就崩了——要么漏数据,要么性能扛不住。

这一章,咱们就把埋点这件事掰开揉碎。客户端怎么埋?服务端怎么拦?日志怎么采?Agent怎么部署?一个一个来。

3.1 客户端埋点技术:从用户行为到风险信号

客户端埋点,说白了就是在用户设备上「装个探头」。用户点了什么、滑了什么、输入了什么,这些行为数据是风险判断的第一手素材。

3.1.1 埋点的三种姿势

我个人习惯把客户端埋点分成三类,你想想看,其实就这三种:

类型 原理 优点 缺点
代码埋点 在业务代码里手动插入采集逻辑 精准、可控 侵入性强,维护成本高
可视化埋点 通过配置平台圈定页面元素,自动采集 无需发版,运营可配 灵活性差,复杂场景搞不定
无埋点(全埋点) 自动采集所有用户交互事件 覆盖全面,不漏数据 数据量大,噪音多

我在项目中遇到过最典型的场景:运营同学说「我要看用户点击这个按钮的转化率」,代码埋点要等排期,可视化埋点圈不准,最后用了全埋点——数据是有了,但每天几亿条事件,光清洗就搞了三天。嗯,这里要注意:全埋点不是银弹,它适合做「数据储备」,不适合做「精准分析」。

3.1.2 关键埋点事件清单

做风险链路追踪,哪些事件必须埋?我列个清单,你照着抄就行:

  • 页面生命周期:进入、离开、停留时长、页面切换路径
  • 交互行为:点击、长按、滑动、输入框聚焦/失焦
  • 敏感操作:登录、注册、支付、修改密码、绑定手机
  • 设备指纹:IP、UA、屏幕分辨率、时区、语言、电池状态
  • 异常事件:JS报错、网络请求失败、白屏、卡顿
我的小技巧:埋点事件命名一定要规范。我习惯用「模块_页面_动作」的格式,比如 pay_checkout_click。别用中文,别用拼音,不然后面数据分析的时候你会想骂人。

3.1.3 客户端SDK设计要点

客户端埋点通常封装成SDK。我设计SDK时,有几个原则:

  1. 异步非阻塞:采集逻辑不能卡主线程。用队列+定时器批量上报。
  2. 本地缓存:网络不好时,数据先存本地。我建议用SQLite或LevelDB,别用SharedPreferences,数据量大时会崩。
  3. 去重与采样:同一个事件在短时间内重复触发,要合并。高频事件(比如滑动)要采样,别全报。
  4. 隐私合规:敏感信息(身份证、手机号)必须脱敏。嗯,这个现在法律管得很严,别踩红线。
// 伪代码:客户端埋点SDK核心逻辑
class TrackerSDK {
    constructor() {
        this.queue = [];
        this.timer = setInterval(() => this.flush(), 5000);
    }

    track(eventName, params) {
        const event = {
            name: eventName,
            params: this.sanitize(params), // 脱敏
            timestamp: Date.now(),
            deviceId: this.getDeviceId()
        };
        this.queue.push(event);
        if (this.queue.length > 50) this.flush(); // 批量上报
    }

    flush() {
        if (this.queue.length === 0) return;
        // 发送到服务端
        navigator.sendBeacon('/track', JSON.stringify(this.queue));
        this.queue = [];
    }

    sanitize(params) {
        // 脱敏逻辑:手机号中间4位变*
        if (params.phone) params.phone = params.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
        return params;
    }
}

3.2 服务端拦截器:在请求入口处「截胡」

客户端埋点搞定了,但还不够。为什么?因为客户端数据可能被篡改。用户用个抓包工具,或者直接改代码,就能伪造事件。所以,服务端必须有自己的采集手段。

服务端拦截器,就是在请求进入业务逻辑之前,先「截」一道。把请求的元数据、参数、上下文都记录下来。

3.2.1 拦截器架构

我一般用过滤器(Filter)或拦截器(Interceptor)来实现。以Java为例:

// Spring Boot 拦截器示例
@Component
public class RiskTraceInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 采集请求元数据
        RiskContext context = new RiskContext();
        context.setIp(getClientIp(request));
        context.setUserAgent(request.getHeader("User-Agent"));
        context.setRequestUri(request.getRequestURI());
        context.setRequestParams(request.getParameterMap());
        context.setTimestamp(System.currentTimeMillis());

        // 存入ThreadLocal,供后续业务使用
        RiskContextHolder.set(context);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 请求结束,清理上下文
        RiskContextHolder.clear();
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) ip = request.getRemoteAddr();
        return ip;
    }
}
我曾经踩过的坑:拦截器里千万别做耗时操作。有一次我在preHandle里调了外部接口查用户信息,结果接口超时,所有请求都堵住了。记住,拦截器只做采集,不做判断。判断逻辑放到后面的风控引擎里去。

3.2.2 采集哪些数据?

服务端拦截器采集的数据,我分成三类:

  • 网络层:IP、端口、协议、请求方法、Content-Type
  • 身份层:用户ID、SessionID、Token、Cookie
  • 业务层:请求参数、响应状态码、处理耗时、异常堆栈

你想想看,这些数据组合起来,能干什么?比如:同一个IP在1秒内发了100个不同用户的登录请求——这明显是撞库攻击。没有服务端拦截器,你根本抓不到这个特征。

3.3 日志采集与Agent部署:让数据「流」起来

客户端和服务端的数据都采了,但它们是分散的。怎么汇聚到一起?靠日志采集和Agent。

3.3.1 日志采集的经典架构

我画个图,你一看就明白:

日志采集与Agent部署架构 应用服务器 A 日志文件 /var/log/app.log 应用服务器 B 日志文件 /var/log/app.log 应用服务器 C 日志文件 /var/log/app.log Filebeat Agent Filebeat Agent Filebeat Agent Kafka / 消息队列 Logstash / Flink 消费处理 采集 传输 消费

这个架构,说白了就是三个环节:采集 -> 传输 -> 消费。每个环节都有讲究。

3.3.2 Agent部署的注意事项

Agent部署,我踩过的坑比吃过的盐还多。说几个关键点:

  • 资源占用:Agent不能吃太多CPU和内存。我建议限制在单核5%以内,内存不超过200MB。否则业务方会找你拼命。
  • 日志轮转:应用日志必须配置轮转(log rotation),不然Agent会把磁盘读爆。我曾经遇到过日志文件100GB,Agent直接OOM。
  • 断点续传:Agent挂了重启,要能接着上次的位置读,不能重复也不能遗漏。Filebeat的registry文件就是干这个的。
  • 安全认证:Agent到消息队列之间要加密。别裸传,万一被人截了,用户数据就泄露了。
核心原则:Agent是「哑巴」——只采集,不处理。所有清洗、转换、聚合的逻辑,都放到下游的消费端去做。这样Agent轻量,出了问题也好排查。

3.3.3 日志格式标准化

我强烈建议统一日志格式。不然每个团队写日志的格式都不一样,解析起来想死。我推荐用JSON格式:

{
    "timestamp": "2025-01-15T10:30:00.123Z",
    "level": "INFO",
    "traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "spanId": "span-001",
    "service": "user-service",
    "method": "com.risk.user.service.login",
    "params": {"userId": "u_10086", "ip": "192.168.1.1"},
    "cost": 45,
    "status": 200,
    "message": "用户登录成功"
}

你看,traceId和spanId都有了,这就是链路追踪的基础。后面讲到「分布式追踪」的时候,你会更深刻地理解这两个字段的价值。

3.4 本章小结

数据采集与埋点,是整个风险链路追踪的「地基」。地基不稳,上面盖什么都是危楼。

客户端埋点要轻量、异步、合规;服务端拦截器要快、准、不阻塞;日志采集要标准化、Agent要轻量化。这三板斧砍下去,数据就能稳稳地流到你的风控引擎里。

嗯,下一章咱们聊聊数据到了之后怎么存、怎么查。不过那是后话了,先把今天的代码写出来跑通再说。

课后作业:在你的项目里找一个接口,加上服务端拦截器,把请求的IP、URI、耗时打印到日志里。然后部署一个Filebeat,把日志送到Kafka。试试看,不难。

公众号:蓝海资料掘金营,微信deep3321