4. 金融数据模型设计:实体、关系、属性与图Schema定义

金融图谱的核心在于将复杂的金融业务抽象为实体(Vertex)、关系(Edge)和属性(Property)。本章定义批流一体链路中的标准数据模型,确保图结构在离线批处理和实时流处理中保持一致。

设计原则:实体必须具有唯一标识(ID),关系必须具有方向性和时间戳,属性需区分静态属性(如客户姓名)和动态属性(如账户余额)。

4.1 核心实体定义

金融图谱包含三大核心实体:客户、账户和交易。每个实体都包含一组关键属性,用于支撑风控、营销和监管场景。

实体类型 唯一标识 核心属性 属性说明
客户 (Customer) customer_id name, id_card, phone, risk_level, create_time 静态身份信息 + 动态风险等级
账户 (Account) account_id account_type, balance, currency, open_date, status 账户类型(储蓄/信用/投资)、余额、状态
交易 (Transaction) txn_id amount, direction, timestamp, channel, counterparty 金额、方向(入/出)、时间戳、渠道
批流一致性要求:在批处理(Hive/Spark)和流处理(Flink/Kafka)中,实体的ID生成策略必须统一。建议使用UUID或雪花算法,避免ID冲突。

4.2 关系类型与语义

关系定义了实体之间的业务连接。在金融图谱中,关系必须携带时间窗口和权重,以支持时序分析和影响力计算。

  • 转账 (TRANSFER):从账户A到账户B的资金转移。属性包括金额、时间、渠道、交易备注。方向:有向。
  • 担保 (GUARANTEE):客户A为客户B提供担保。属性包括担保金额、起始日期、到期日期、担保类型。方向:有向。
  • 持股 (SHAREHOLDING):客户持有账户或公司股份。属性包括持股比例、持股数量、持股起始时间。方向:有向。
  • 亲属 (RELATIVE):客户之间的亲属关系。属性包括关系类型(配偶/子女/父母)。方向:无向。
  • 设备关联 (DEVICE_LINK):客户与设备(手机/电脑)之间的关联。属性包括设备ID、首次关联时间、关联频次。方向:无向。
避坑提示:关系属性中的时间戳必须使用UTC时间,避免因时区不一致导致图分析结果偏差。在流处理中,事件时间(Event Time)优先于处理时间(Processing Time)。

4.3 属性设计规范

属性设计遵循扁平化和可扩展原则。避免嵌套结构,优先使用基本数据类型。

属性类别 数据类型 示例 备注
标识属性 STRING customer_id: "CUST_20241001_001" 全局唯一,不可变
数值属性 DOUBLE / LONG balance: 150000.50 保留两位小数,单位统一
时间属性 LONG (Unix Timestamp) create_time: 1727712000000 毫秒级精度
枚举属性 STRING (枚举值) account_type: "SAVING" 使用大写常量,避免歧义
标签属性 ARRAY<STRING> tags: ["高风险", "大额交易"] 用于动态标注

4.4 图Schema定义

图Schema是图谱的元数据骨架。以下使用Apache TinkerPop / Gremlin风格的Schema定义,兼容JanusGraph、Neo4j等主流图数据库。

// 顶点标签定义
schema.vertexLabel('customer')
  .partitionBy('customer_id', Text)
  .property('name', Text)
  .property('id_card', Text)
  .property('phone', Text)
  .property('risk_level', Text)
  .property('create_time', Long)
  .create();

schema.vertexLabel('account')
  .partitionBy('account_id', Text)
  .property('account_type', Text)
  .property('balance', Double)
  .property('currency', Text)
  .property('open_date', Long)
  .property('status', Text)
  .create();

schema.vertexLabel('transaction')
  .partitionBy('txn_id', Text)
  .property('amount', Double)
  .property('direction', Text)
  .property('timestamp', Long)
  .property('channel', Text)
  .property('counterparty', Text)
  .create();

// 边标签定义
schema.edgeLabel('transfer')
  .from('account').to('account')
  .property('amount', Double)
  .property('timestamp', Long)
  .property('channel', Text)
  .create();

schema.edgeLabel('guarantee')
  .from('customer').to('customer')
  .property('guarantee_amount', Double)
  .property('start_date', Long)
  .property('end_date', Long)
  .create();

schema.edgeLabel('shareholding')
  .from('customer').to('account')
  .property('ratio', Double)
  .property('quantity', Long)
  .property('start_time', Long)
  .create();
批流Schema对齐:离线批处理(Spark GraphX)和实时流处理(Flink Gelly)必须使用同一份Schema定义。建议将Schema存储在Git仓库中,通过CI/CD自动同步到图数据库和计算引擎。

4.5 图结构可视化

下图展示了金融图谱的核心实体与关系结构,包含客户、账户、交易三大实体以及转账、担保、持股三种主要关系。

金融图谱核心实体与关系结构 客户 (Customer) customer_id, name, risk_level 账户 (Account) account_id, balance, status 交易 (Transaction) txn_id, amount, timestamp 持股 (SHAREHOLDING) ratio, quantity 转账 (TRANSFER) amount, timestamp 客户 (Customer) customer_id, name, risk_level 客户 (Customer) customer_id, name, risk_level 担保 (GUARANTEE) amount, start_date, end_date 说明: 实体节点使用不同颜色区分:蓝色=客户,绿色=账户,红色=交易,紫色=担保关联客户。 所有关系必须携带时间戳属性,支持时序回溯分析。

4.6 批流一体数据模型映射

在批流一体架构中,图数据模型需要同时适配离线存储(HDFS/Parquet)和实时存储(Kafka/Redis)。以下映射表展示了不同存储层的数据模型表示。

存储层 实体表示 关系表示 序列化格式
离线批处理 (Hive) Parquet表,按customer_id分区 Parquet表,包含from_id, to_id, edge_type Avro / Parquet
实时流处理 (Kafka) JSON消息,key为实体ID JSON消息,包含source, target, properties JSON / Protobuf
图数据库 (JanusGraph) Vertex Label Edge Label Gremlin / GraphSON
缓存层 (RedisGraph) Graph Entity Graph Edge RedisGraph Query
实践建议:在Kafka消息中,实体和关系使用统一的Schema Registry管理。建议使用Avro或Protobuf,确保批流两端的数据结构一致,避免反序列化失败。

4.7 属性演化与版本控制

金融业务模型会随时间变化,属性演化必须遵循向后兼容原则。推荐使用Schema版本号管理属性变更。

  • 新增属性:设置默认值,允许为空。例如新增"customer_segment"字段,默认值为"UNKNOWN"。
  • 删除属性:标记为废弃(deprecated),保留数据但不写入新值。避免直接删除导致历史数据断裂。
  • 修改属性类型:创建新属性并迁移数据,旧属性保留为别名。例如将"balance"从FLOAT改为DOUBLE。
  • 版本号管理:在实体和关系的属性中增加"schema_version"字段,用于区分不同版本的数据。
// 属性演化示例:客户实体增加schema_version
schema.vertexLabel('customer')
  .property('schema_version', Int)  // 当前版本: 2
  .property('customer_segment', Text)  // 新增字段,默认值: "UNKNOWN"
  .property('risk_level', Text)  // 原有字段,保持不变
  .update();  // 使用update而非create,保留已有数据
避坑提示:在流处理中,属性演化可能导致反序列化异常。建议在Flink或Spark Streaming中使用Schema Registry的兼容性检查(Backward/Forward),确保新旧版本数据都能正常处理。