3. 数据采集层架构:实时与离线数据源接入、API网关设计、数据清洗策略

数据采集层是商户风险评分通道的“感官系统”,负责从分散的异构数据源中获取原始信息,并完成格式统一、质量校验与初步加工。本章聚焦三类核心设计:实时与离线数据源的接入方式、API网关的流量治理与协议转换、数据清洗的策略与工程实现。

3.1 实时与离线数据源接入

商户风险评分需要同时依赖实时事件(如交易行为、登录异常)与离线特征(如工商信息、历史黑名单)。两类数据在时效性、吞吐量、接入协议上差异显著,需采用不同的架构组件。

维度 实时数据源 离线数据源
典型来源 交易流水、风控事件、设备指纹、实时征信 工商注册、司法涉诉、历史逾期、商户画像
接入协议 HTTP/REST、WebSocket、Kafka、gRPC SFTP、JDBC、S3对象存储、HDFS
时效要求 毫秒~秒级 T+1 或小时级
数据量级 高吞吐、小消息(KB级) 批量大文件(GB~TB级)
处理模式 流式处理(Flink/Spark Streaming) 批处理(Spark/MapReduce)
容错策略 消息重试、死信队列、幂等消费 断点续传、校验和比对、分区重跑
架构建议:实时通道采用“生产者-消费者”模式,通过Kafka解耦数据生产与消费;离线通道建议使用调度框架(如Airflow)统一管理ETL任务,并设置数据质量监控规则。

3.2 API网关设计

API网关是数据采集层的统一入口,负责协议转换、流量控制、鉴权与路由。在商户风险评分场景中,网关需要同时支持外部数据源(如第三方征信)与内部微服务的数据请求。

3.2.1 网关核心功能

  • 协议适配:将外部HTTP/JSON请求转换为内部gRPC或Thrift调用,或反向转换。
  • 限流熔断:按商户等级、API Key、IP维度设置QPS阈值,防止突发流量击穿下游。
  • 鉴权与签名校验:验证请求的AppId、Secret、时间戳与签名,防止重放攻击。
  • 请求/响应转换:统一字段命名规范(如 snake_case 转 camelCase),过滤敏感字段。
  • 链路追踪:注入TraceId,串联网关→采集服务→清洗管道→评分引擎的全链路日志。

3.2.2 网关路由策略

路由规则 说明 示例
按数据源类型 根据请求路径前缀分发到不同采集服务 /v1/credit/* → 征信采集集群
按商户等级 高等级商户走独立资源池,保障SLA X-Merchant-Level: platinum → 高优队列
按请求时效 实时请求走短连接,离线请求走异步回调 sync=true → 直连;sync=false → 消息队列
灰度/蓝绿部署 通过Header或百分比流量切到新版本采集服务 X-Canary: v2 → 新版清洗管道
避坑提示:网关层不要做业务逻辑(如特征计算、评分预判),只做流量治理与协议转换。业务逻辑放在采集服务内部,保持网关的轻量与高可用。
API网关数据采集架构 API网关数据采集架构 第三方征信 商户交易事件 工商/司法数据 API 网关(Kong / APISIX / Zuul) 协议适配 · 限流熔断 · 鉴权签名 · 路由分发 · 链路追踪 实时采集服务 离线采集服务 文件采集服务 Kafka / HDFS / 数据湖

3.3 数据清洗策略

原始数据存在缺失、重复、格式错误、异常值等问题,必须经过清洗才能进入特征工程。清洗策略按处理阶段分为前置过滤、字段级清洗与后置校验三层。

3.3.1 前置过滤(丢弃无效数据)

  • 空记录过滤:关键字段(商户ID、交易金额、时间戳)全部为空的记录直接丢弃。
  • 格式校验:检查手机号、身份证号、银行卡号的正则格式,不符合则标记为“格式异常”并进入人工审核队列。
  • 时间范围过滤:时间戳超出当前时间前后24小时的记录视为异常,拒绝入库。

3.3.2 字段级清洗(标准化与补全)

字段类型 常见问题 清洗策略
商户名称 前后空格、全半角混用、特殊符号 trim() + 全角转半角 + 正则去除非汉字/字母/数字
金额字段 负值、超过阈值、单位不一致(分/元) 统一转为“分”单位,负值取绝对值并标记异常
时间字段 格式不统一(yyyyMMdd / yyyy-MM-dd) 统一转为 Unix 时间戳(毫秒)
枚举字段 大小写不一致、缩写与全称混用 映射为标准枚举值(如 MALE/FEMALE/UNKNOWN)
地址字段 省市区缺失、乱码 调用地址解析服务补全,失败则标记“地址解析失败”

3.3.3 后置校验(质量监控与告警)

  • 完整性校验:每条记录必填字段的填充率是否达到99.9%以上。
  • 唯一性校验:按商户ID+交易流水号去重,重复记录只保留第一条。
  • 波动性校验:单日数据量环比波动超过±30%时触发告警,排查采集链路是否异常。
工程实践:清洗逻辑建议封装为独立的“清洗算子”微服务,每个算子只负责一种清洗规则(如“手机号清洗算子”、“金额标准化算子”),通过DAG编排引擎(如DolphinScheduler)组合执行,便于复用与灰度发布。
数据清洗管道流程 数据清洗管道流程 原始数据流入 前置过滤:空记录 / 格式 / 时间范围 字段级清洗:标准化 / 补全 / 映射 后置校验:完整性 / 唯一性 / 波动性

3.4 数据清洗代码示例(Python)

以下是一个简化的清洗算子实现,展示字段标准化与异常标记逻辑:

import re
import time
from datetime import datetime

def clean_merchant_record(record: dict) -> dict:
    """清洗单条商户记录,返回清洗后数据与异常标记列表"""
    anomalies = []
    cleaned = record.copy()

    # 1. 商户名称清洗:去空格、全角转半角
    name = cleaned.get('merchant_name', '')
    name = name.strip()
    name = name.replace(' ', ' ')  # 全角空格转半角
    name = re.sub(r'[^\w\u4e00-\u9fff]', '', name)  # 只保留汉字、字母、数字
    cleaned['merchant_name'] = name

    # 2. 金额标准化:统一转为分(单位:元转分)
    amount = cleaned.get('amount', 0)
    if isinstance(amount, float) or isinstance(amount, str):
        try:
            amount = int(float(amount) * 100)
        except (ValueError, TypeError):
            anomalies.append('amount_format_error')
            amount = 0
    cleaned['amount_cents'] = amount

    # 3. 时间字段统一为Unix毫秒
    ts = cleaned.get('event_time', '')
    if isinstance(ts, str):
        try:
            dt = datetime.strptime(ts, '%Y-%m-%d %H:%M:%S')
            cleaned['event_time_ms'] = int(dt.timestamp() * 1000)
        except ValueError:
            anomalies.append('time_format_error')
            cleaned['event_time_ms'] = int(time.time() * 1000)
    else:
        cleaned['event_time_ms'] = int(ts) if ts else int(time.time() * 1000)

    # 4. 枚举字段映射
    gender_map = {'男': 'MALE', '女': 'FEMALE', 'M': 'MALE', 'F': 'FEMALE'}
    raw_gender = cleaned.get('gender', 'UNKNOWN')
    cleaned['gender'] = gender_map.get(raw_gender.upper(), 'UNKNOWN')

    cleaned['anomalies'] = anomalies
    return cleaned
生产建议:清洗算子应设计为无状态函数,便于水平扩展。异常记录不要直接丢弃,应写入“异常数据表”供事后分析与模型调优。