3. 催收数据模型设计:客户画像表、逾期订单表、催收行为表、催收结果表
催收数据模型是催收系统的核心资产,它决定了催收策略的精准度、催收效率以及后续数据分析的深度。在 Spark 批流一体架构下,数据模型需要兼顾离线批处理的高吞吐与实时流处理的低延迟。本章围绕四个核心主题表展开:客户画像表、逾期订单表、催收行为表和催收结果表。
设计原则:催收数据模型遵循“宽表 + 拉链表 + 事件表”的混合模式。客户画像表采用宽表存储最新快照;逾期订单表采用拉链表记录状态变更历史;催收行为表和催收结果表采用事件表追加写入,便于 Spark Structured Streaming 实时消费。
3.1 客户画像表 (customer_profile)
客户画像表是催收分析的起点,聚合了客户的基础信息、信用评分、历史行为标签和实时风险等级。该表以 customer_id 为主键,每天由 Spark 批处理任务全量刷新。
| 字段名 | 类型 | 说明 |
|---|---|---|
customer_id |
STRING | 客户唯一标识,主键 |
age_group |
STRING | 年龄分段:20-30, 30-40, 40-50, 50+ |
credit_score |
INT | 外部征信评分(300-850) |
internal_risk_level |
STRING | 内部风险等级:A/B/C/D |
total_overdue_days |
INT | 历史累计逾期天数 |
max_overdue_amount |
DECIMAL(18,2) | 历史单笔最大逾期金额 |
contact_phone_count |
INT | 有效联系人数量 |
last_contact_date |
DATE | 最近一次联系日期 |
profile_version |
BIGINT | 数据版本号,用于乐观锁 |
Spark 优化点:客户画像表在 Spark 中采用
bucketBy(64, "customer_id") 分桶存储,配合 sortBy("internal_risk_level"),在后续 Join 逾期订单表时可显著减少 Shuffle 数据量。
3.2 逾期订单表 (overdue_order)
逾期订单表记录每一笔订单的逾期状态变化,采用拉链表设计,支持时间旅行查询。每个订单从逾期开始到结清或核销,会生成多条记录,通过 effective_date 和 expiration_date 标记有效期。
-- 逾期订单拉链表 DDL 示例 (Spark SQL)
CREATE TABLE overdue_order (
order_id STRING,
customer_id STRING,
loan_amount DECIMAL(18,2),
overdue_principal DECIMAL(18,2),
overdue_days INT,
overdue_stage STRING, -- M1, M2, M3, M4+
effective_date DATE,
expiration_date DATE,
is_current BOOLEAN
)
USING parquet
PARTITIONED BY (dt STRING)
CLUSTERED BY (customer_id) INTO 128 BUCKETS;
| 逾期阶段 | 逾期天数范围 | 催收策略倾向 |
|---|---|---|
| M1 | 1-30 天 | 短信/IVR 提醒,AI 外呼 |
| M2 | 31-60 天 | 人工电话催收,风险预警 |
| M3 | 61-90 天 | 高级催收员介入,法务准备 |
| M4+ | 90 天以上 | 委外催收,诉讼核销 |
3.3 催收行为表 (collection_action)
催收行为表是事件驱动的流水表,记录每一次催收触达行为,包括触达方式、时间、内容摘要和关联的催收员。该表由 Spark Structured Streaming 实时写入,同时供离线分析使用。
// Spark Structured Streaming 写入催收行为表示例
Dataset<Row> actions = spark
.readStream()
.format("kafka")
.option("subscribe", "collection_actions")
.load()
.selectExpr(
"CAST(value AS STRING) as json",
"CAST(timestamp AS TIMESTAMP) as action_time"
)
.select(
functions.from_json(
functions.col("json"),
actionSchema
).alias("data"),
functions.col("action_time")
)
.select("data.*", "action_time")
.writeStream()
.format("parquet")
.option("checkpointLocation", "/data/checkpoint/collection_action")
.partitionBy("action_date")
.start();
| 字段名 | 类型 | 说明 |
|---|---|---|
action_id |
STRING | 行为唯一 ID (UUID) |
order_id |
STRING | 关联订单 |
action_type |
STRING | SMS, IVR, MANUAL_CALL, EMAIL, VISIT |
contact_result |
STRING | CONTACTED, NO_ANSWER, BUSY, INVALID |
collector_id |
STRING | 催收员 ID(AI 外呼为 'AI_AGENT') |
action_duration_sec |
INT | 通话时长/交互时长(秒) |
action_date |
STRING | 分区字段,格式 yyyyMMdd |
3.4 催收结果表 (collection_result)
催收结果表记录每一次催收行为的最终结果,包括承诺还款金额、实际还款金额、还款日期等。该表与催收行为表通过 action_id 关联,形成行为-结果一对一的闭环。
数据一致性注意:催收结果表通常由催收系统在还款入账后异步写入,可能存在延迟。Spark 作业在关联行为表与结果表时,应使用
LEFT JOIN 并设置合理的 watermark,避免因结果延迟导致数据丢失。
3.5 模型关联与 Spark 实践
四个核心表通过外键形成星型模型,客户画像表作为维度表,逾期订单表作为事实表,催收行为表和催收结果表作为明细事件表。在 Spark 中,典型的 ETL 流程如下:
- 每日批处理:使用 Spark SQL 将客户画像表与逾期订单表 Join,生成催收任务宽表,写入催收行为表。
- 实时流处理:Spark Structured Streaming 消费 Kafka 中的催收行为事件,写入催收行为表分区。
- 结果回写:催收结果表通过异步消息队列更新,Spark 微批次作业每 5 分钟拉取结果,更新逾期订单表的
overdue_stage和客户画像表的internal_risk_level。
-- Spark SQL 催收任务宽表生成示例
CREATE OR REPLACE TEMP VIEW collection_task AS
SELECT
p.customer_id,
p.internal_risk_level,
o.order_id,
o.overdue_principal,
o.overdue_days,
o.overdue_stage,
CURRENT_TIMESTAMP() AS task_create_time
FROM customer_profile p
INNER JOIN overdue_order o
ON p.customer_id = o.customer_id
WHERE o.is_current = TRUE
AND o.overdue_days > 0;
性能建议:在 Spark 中 Join 客户画像表与逾期订单表时,建议使用
broadcast hash join(画像表通常小于 500MB),避免 Shuffle。如果画像表较大,可考虑使用 sort merge join 并确保两表按 customer_id 分桶。