侧边栏壁纸
  • 累计撰写 85 篇文章
  • 累计创建 93 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

供热经营收费管理平台 — 微服务架构设计

温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. 平台概述

1.1 项目背景

供热行业作为市政公用事业的重要组成部分,承担着冬季居民供暖的核心职能。传统供热收费管理多依赖人工台账或单体信息系统,存在数据孤岛严重、业务流程割裂、缴费渠道单一、报表统计滞后等突出问题。随着供热面积持续扩大、用户规模增长以及精细化管理要求的提升,原有系统架构已无法支撑高效的经营管理和优质的用户服务。

本平台采用微服务架构,将供热经营收费全流程拆分为独立部署、独立扩展的业务服务单元,通过服务编排实现新用户开户、热费缴纳、停复供申请、退费处理、报修投诉、报表统计六大核心业务场景的端到端闭环,为供热企业提供高可用、可扩展、易维护的经营收费管理底座。

1.2 设计目标

指标 目标值
业务微服务数量 11 个
核心业务场景 6 个
系统可用性 99.9%
用户规模支撑 10 万+

业务解耦 — 按业务领域拆分服务边界,各服务独立开发、部署和扩展,消除单体架构中的功能耦合。

弹性伸缩 — 缴费高峰期对收费服务和支付服务弹性扩容,IoT 控制服务按设备分片独立扩展,非高峰期自动缩容。

数据贯通 — 通过事件驱动和 API 编排实现跨服务数据流转,打破信息孤岛,支撑经营分析决策。

渐进演进 — 支持新服务独立上线和旧服务逐步替换,降低系统重构风险,保障业务连续性。

1.3 设计原则

原则 说明
领域驱动拆分 以业务领域为边界划分微服务,每个服务对应一个限界上下文(Bounded Context),服务内高内聚、服务间低耦合
无状态化 业务服务不持有会话状态,状态信息下沉至 Redis 集群或数据库,任意服务实例可被替换(IoT 控制服务除外,按设备分片维持长连接)
最终一致性 跨服务数据操作采用 Saga 模式和事件驱动机制保证最终一致性,核心交易链路通过本地消息表+对账机制兜底
接口契约优先 服务间通过 OpenAPI/Swagger 定义接口契约,消费方基于契约编程,服务端变更需保持向后兼容
故障隔离 通过熔断、降级、限流和舱壁模式实现故障隔离,单个服务异常不影响全局可用性

1.4 适用范围

本架构设计适用于中等规模以上的供热企业(供热面积 100 万平方米以上、用户 5 万户以上),支持多热源、多片区、多收费标准的经营场景。对于小型供热企业,可基于本架构裁剪为基础版(合并部分微服务为模块化单体),在业务增长后平滑演进为完整微服务架构。

2. 整体架构设计

2.1 架构总览

平台采用分层架构,自上而下分为接入层、网关层、业务服务层、基础服务层、数据存储层五个层次,辅以监控运维和安全管理两大横向支撑体系。各层职责清晰,依赖方向自上而下单向流转。

blog-mermaid-01-flowchart.png

2.2 架构分层说明

层次 核心组件 职责描述
接入层 Web 管理端、移动端 APP、微信小程序、第三方 API 面向不同用户角色提供差异化交互入口,统一通过 HTTPS 接入
网关层 Spring Cloud Gateway / APISIX 统一入口,承担身份认证、权限校验、请求路由、限流熔断、灰度发布、日志追踪和跨域处理
业务服务层 11 个核心业务微服务 按供热经营领域拆分的业务服务,每个服务拥有独立数据库实例,通过 RESTful API 或事件消息协同
基础服务层 认证中心、配置中心、注册中心、消息队列、文件服务、规则引擎 为业务服务提供基础设施能力
数据存储层 MySQL、Redis、Elasticsearch、数据仓库、InfluxDB 多模态存储:MySQL 存核心业务数据,Redis 缓存热点数据,ES 支撑全文检索,数据仓库服务报表,InfluxDB 存储 IoT 时序数据

2.3 技术选型

技术领域 选型 选型理由
开发框架 Spring Boot 3.x + Spring Cloud Java 生态成熟,微服务组件齐全,社区活跃
服务注册发现 Nacos 同时提供注册与配置中心能力,运维成本低
API 网关 Spring Cloud Gateway 与 Spring 生态深度集成,支持路由、限流、熔断
服务通信 OpenFeign(同步)+ RocketMQ(异步) Feign 声明式调用简化开发,RocketMQ 保证消息可靠投递
分布式事务 Seata(AT 模式)+ 本地消息表 核心交易用 Seata 保证强一致性,非核心链路用本地消息表实现最终一致性
数据库 MySQL 8.0(主从) 关系型数据存储,成熟稳定,支持事务
缓存 Redis 7.x Cluster 热点数据缓存、分布式锁、会话管理
搜索引擎 Elasticsearch 用户/房产全文检索、工单搜索、日志聚合分析
时序数据库 InfluxDB IoT 设备热量表读数、设备状态时序数据存储
消息队列 RocketMQ 事务消息支持完善,适合收费/退费等高可靠性场景
容器编排 Kubernetes + Docker 容器化部署,弹性伸缩,滚动更新,故障自愈
监控告警 Prometheus + Grafana + AlertManager 指标采集、可视化看板、多渠道告警通知
链路追踪 SkyWalking 分布式调用链追踪,性能分析,拓扑图可视化
日志管理 ELK Stack 日志采集、存储、检索、可视化分析
CI/CD GitLab CI + ArgoCD 代码托管、自动化构建、GitOps 模式持续部署

以上选型基于供热行业业务特点(交易金额敏感、季节性峰值明显、设备交互实时性要求)综合评估。如团队已有替代方案,在满足可靠性和事务消息需求的前提下可灵活替换。

3. 微服务拆分与职责

3.1 服务全景图

平台共拆分为 11 个业务微服务6 个基础支撑服务。业务服务按供热经营领域划分为用户域、计费收费域、运营管控域、分析域四个限界上下文。

blog-mermaid-02-flowchart.png

3.2 核心业务服务

用户服务(User Service)

维度 说明
服务定位 供热用户全生命周期管理,覆盖个人用户和单位用户的开户、变更、注销
核心职责 用户信息录入与档案管理、用户状态管理(正常/停供/注销)、用户分类管理(居民/非居民)、用户标签与信用评级
主要接口 创建用户、查询用户档案、更新用户信息、变更用户状态、用户信用分查询
数据存储 独立 MySQL 库(user_db),存储用户主表、用户扩展属性表、用户操作日志表
关键依赖 房产服务(通过房产服务查询用户-房产绑定关系)、合同服务(查询用户合同)、通知服务(开户成功通知)、认证中心(用户登录鉴权)

V2.0 变更:原用户服务中的合同管理功能(模板管理、合同生成、签署流程、归档存储)已抽取为独立的合同服务。用户服务回归纯粹的用户档案管理,迭代频率降低。用户与房产的绑定关系迁移至房产服务管理,用户服务仅保存 property_id 引用。

房产服务(Property Service)

维度 说明
服务定位 供热房产(楼栋、单元、户室)的档案管理与用户-房产绑定关系维护
核心职责 房产信息录入(小区/楼栋/单元/户室四级结构)、建筑面积与供热面积管理、房产状态管理、供热入网登记、用户-房产绑定关系管理
主要接口 创建房产、查询房产信息、更新房产面积、房产状态变更、按小区/楼栋批量查询、房产入网登记、用户房产绑定/解绑
数据存储 独立 MySQL 库(property_db),存储房产主表、面积信息表、房产变更记录表、入网登记表、用户房产关系表
关键依赖 计费账单服务(提供面积数据用于热费计算)、用户服务(查询用户基本信息)

V2.0 变更:用户-房产绑定关系从用户服务迁移至房产服务管理。这一调整使房产域的数据完整性由房产服务统一负责,避免跨服务的数据一致性问题。

合同服务(Contract Service)【新增】

维度 说明
服务定位 供热合同的模板管理、生成、签署、归档与到期管理
核心职责 合同模板版本管理、基于模板自动生成供热合同 PDF、电子签章对接与签署流程、合同归档存储、合同到期续签提醒
主要接口 创建合同模板、生成合同、查询合同信息、合同签署、合同归档、到期合同查询
数据存储 独立 MySQL 库(contract_db),存储合同模板表、合同主表、签署记录表、归档文件引用表
关键依赖 用户服务(获取用户信息)、房产服务(获取房产信息)、文件服务(合同 PDF 存储与检索)

V2.0 新增:从用户服务中抽取。合同管理有独立的业务生命周期——模板版本管理、电子签章对接、到期续签提醒——随着业务发展会不断膨胀(合同变更、多方签署、合同审计),独立后可按自身节奏迭代,不影响用户核心数据稳定性。

计费账单服务(Billing & Bill Service)

维度 说明
服务定位 根据收费标准和用热量计算应收热费,并生成、管理、查询供热费账单
核心职责 计费规则管理(面积单价/计量单价/分时段单价)、热量数据接入与校验、热费计算(基本热费+计量热费)、费用调整与优惠计算、按供热季批量生成账单、账单明细管理、账单状态流转(待缴/部分缴/已缴/逾期)、账单查询与导出、欠费账单标记
主要接口 创建计费规则、执行批量计费、查询计费结果、费用调整、批量生成账单、查询用户账单列表、查询账单明细、更新账单状态、欠费账单查询、账单导出
数据存储 独立 MySQL 库(billing_bill_db),存储计费规则表、计费结果表、热量数据表、费用调整记录表、账单主表、账单明细表、账单状态变更日志表
关键依赖 房产服务(获取供热面积)、IoT 控制服务(获取热量表读数)、规则引擎(复杂计费规则计算)、收费服务(接收缴费回写账单状态)

V2.0 变更:由原计费服务与账单服务合并而来。两者在业务上高度耦合——计费的输出就是账单的输入,共享"供热季"“用户编号”"房产面积"等同一套领域概念。合并后内部拆分为计费引擎模块(规则计算、热量数据处理)和账单管理模块(账单生成、状态流转、查询导出),模块间通过进程内调用替代网络调用,延迟从约 15ms 降至约 1ms。原跨服务的事务协调(Saga/Seata)简化为本地事务。计费引擎模块通过批量任务异步执行,账单模块处理在线查询,两者资源隔离通过线程池实现。

收费服务(Charge Service)

维度 说明
服务定位 供热费收取的核心枢纽,协调支付、账单、发票完成缴费闭环
核心职责 缴费订单创建与管理、收费记录管理、退费申请发起与处理、收费方式管理(线上/线下/银行代扣)、对账管理、欠费催收记录
主要接口 创建缴费订单、查询缴费记录、发起退费申请、退费审核处理、收费对账查询、欠费催收记录
数据存储 独立 MySQL 库(charge_db),存储收费订单表、收费记录表、退费申请表、对账记录表
关键依赖 支付服务(调用支付渠道)、计费账单服务(回写缴费状态)、发票服务(触发开票)、审批服务(退费审批)、通知服务(缴费成功通知)

支付服务(Payment Service)

维度 说明
服务定位 对接外部支付渠道,完成资金收付操作
核心职责 多支付渠道管理(微信/支付宝/银行代扣/POS/现金)、支付订单管理、支付回调处理、退款执行、支付渠道对账、支付安全(签名验签/防重放)
主要接口 发起支付、支付回调通知、查询支付状态、发起退款、查询退款状态、支付渠道对账
数据存储 独立 MySQL 库(payment_db),存储支付订单表、支付流水表、退款记录表、支付渠道配置表
关键依赖 收费服务(接收支付请求)、通知服务(支付结果通知)、外部支付渠道

发票服务(Invoice Service)

维度 说明
服务定位 电子发票和纸质发票的开具与管理,对接税务系统
核心职责 发票开具(电子/纸质)、发票冲红、发票查询与下载、发票抬头管理、税控设备管理、对接税务局开票接口
主要接口 开具发票、发票冲红、查询发票信息、下载电子发票、发票抬头维护
数据存储 独立 MySQL 库(invoice_db),存储发票主表、发票明细表、发票冲红记录表、税控设备配置表
关键依赖 收费服务(获取开票金额和税额)、外部税务系统

审批服务(Approval Service)

维度 说明
服务定位 统一的工作流审批引擎,支撑停复供、退费等业务的审批流程
核心职责 审批流程定义与管理、审批任务分发与流转、审批意见记录、多级审批支持(初审/复审/终审)、审批超时提醒、审批历史查询
主要接口 发起审批、查询待审批任务、审批通过/驳回、查询审批历史、配置审批流程
数据存储 独立 MySQL 库(approval_db),存储审批流程定义表、审批任务表、审批记录表、审批人配置表
关键依赖 收费服务(退费审批)、IoT 控制服务(停复供审批通过后执行阀门控制)、通知服务(审批结果通知)

粒度说明:当前审批服务仅用于停复供和退费两个场景,作为独立微服务略重。初期可考虑作为审批模块嵌入收费服务或设备档案服务,待审批场景增至 5 个以上时再抽取为独立服务。

设备档案服务(Device Archive Service)【拆分新增】

维度 说明
服务定位 供热设备(热量表、阀门、温度传感器)的档案与生命周期管理
核心职责 设备档案管理(注册/变更/报废)、设备分类管理、设备与房产绑定关系、设备状态查询(在线/离线/故障)、设备维护记录
主要接口 设备注册、查询设备档案、更新设备信息、设备报废、查询设备状态、设备维护记录
数据存储 独立 MySQL 库(device_archive_db),存储设备档案表、设备状态表、设备房产关系表、维护记录表
关键依赖 房产服务(设备关联房产)、IoT 控制服务(查询设备实时状态)、计费账单服务(提供热量表档案信息)

V2.0 新增:由原设备服务拆分而来,负责设备生命周期管理(CRUD 操作,低频写入,查询为主),是典型的 OLTP 负载。

IoT 控制服务(IoT Control Service)【拆分新增】

维度 说明
服务定位 供热设备的物联网实时通信与控制
核心职责 阀门远程控制(开阀/关阀)、热量表数据采集、设备告警实时推送、设备长连接维持与管理、控制指令日志
主要接口 远程控制阀门、采集热量数据、设备告警订阅、查询控制指令日志
数据存储 InfluxDB(热量表读数时序数据、设备状态时序数据)+ Redis(设备连接状态缓存),无 MySQL
关键依赖 审批服务(接收停复供审批通过后的阀门控制指令)、计费账单服务(推送热量表读数)、IoT 平台(MQTT/TCP 协议适配)
伸缩策略 按设备编号分片(Hash 分片),每个分片维持一部分设备长连接,有状态服务

V2.0 新增:由原设备服务拆分而来,专注设备通信与实时控制。拆分原因是档案管理(OLTP)与 IoT 控制(长连接、实时性敏感)的负载特征完全不同——混合部署导致 IoT 连接高峰时档案查询响应变慢,批量档案导入时 IoT 指令延迟升高。拆分后 IoT 控制服务可按设备分片独立水平扩展,不影响档案查询性能。

客服服务(Customer Service)

维度 说明
服务定位 用户报修、投诉、咨询的工单受理与处理管理
核心职责 工单受理与分类(报修/投诉/咨询)、工单派单与流转、维修人员调度、工单状态跟踪、处理结果回访、工单统计与评价
主要接口 创建工单、查询工单列表、派单、更新工单状态、工单回访评价、工单统计查询
数据存储 独立 MySQL 库(customer_db),存储工单主表、工单处理记录表、维修人员表、工单评价表
关键依赖 通知服务(派单通知、处理结果通知)、设备档案服务(关联设备维修)、用户服务(查询用户信息)

通知服务(Notification Service)

维度 说明
服务定位 统一的多渠道消息通知中心,为各业务服务提供消息发送能力
核心职责 多渠道通知(短信/微信推送/APP 推送/站内信)、通知模板管理、通知记录与追踪、通知策略管理(即时/定时/批量)、通知失败重试
主要接口 发送通知、查询通知记录、通知模板管理、批量通知发送
数据存储 独立 MySQL 库(notification_db)+ Redis(通知队列),存储通知记录表、通知模板表、通知配置表
关键依赖 外部渠道(短信网关、微信接口、APP 推送服务)、消息队列(异步通知消费)

数据分析服务(Analytics Service)【合并新增】

维度 说明
服务定位 经营数据统计分析与报表生成,含数据管道与查询服务,为管理决策提供数据支撑
核心职责 数据管道组:CDC 数据变更捕获、数据清洗转换、ETL 调度管理、数据质量校验、同步任务监控;查询服务组:收费统计报表(日/月/季/年)、欠费分析报表、经营分析报表(收入/成本/收缴率)、用户分析报表、设备运行报表、自定义报表设计
主要接口 查询同步任务状态、手动触发同步、查询收费统计、查询欠费分析、查询经营分析、导出报表、自定义报表查询
数据存储 数据仓库(ClickHouse/Doris),存储各维度汇总数据、预聚合报表数据、同步日志、ETL 任务配置、数据质量规则
关键依赖 各业务服务 MySQL(数据源)、消息队列(CDC 事件订阅)

V2.0 变更:由原报表服务与数据同步服务合并而来。两者共享同一套数据仓库基础设施,数据同步的表结构变更必然影响报表查询,报表的新增指标需求也驱动同步逻辑调整。合并后数据管道与查询共享数据仓库的元数据管理,表结构变更一处修改即可。数据管道和查询服务仍可通过 K8s 的不同 Deployment 独立伸缩。

3.3 基础支撑服务

服务 技术实现 职责
认证中心 Spring Security + JWT + OAuth2 统一身份认证、Token 签发与刷新、RBAC 权限模型、多端登录管理
配置中心 Nacos Config 集中管理各服务配置,支持动态刷新、灰度发布、环境隔离
注册中心 Nacos Discovery 服务注册与发现、健康检查、负载均衡
消息队列 RocketMQ 异步通信、事件驱动、事务消息、削峰填谷
文件服务 MinIO / FastDFS 合同文件、发票文件、附件存储与管理
规则引擎 Drools / LiteFlow 计费规则、优惠政策、审批流程等复杂规则的集中管理与执行

3.4 服务拆分原则

本平台服务拆分遵循领域驱动设计(DDD)方法,以供热经营的业务流程为线索,按限界上下文划分服务边界。同一业务领域内的强一致性需求归属同一服务(如计费与账单同属计费账单服务),跨领域的弱一致性需求通过事件驱动异步协同。拆分粒度避免过细(导致服务间调用链路过长)和过粗(失去独立扩展能力),以一个团队可独立维护 2-3 个服务为基准。

4. 业务场景与服务编排

本章对供热经营的六大核心业务场景逐一展开,说明每个场景涉及的微服务、核心功能以及服务间协作流程。

4.1 新用户开户

涉及服务:用户服务、房产服务、合同服务、文件服务、通知服务

营业员在管理端录入新用户基本信息,绑定供热房产,上传身份证件和房产证明,系统自动生成用户档案并创建供热合同。开户完成后,通知服务向用户发送开户成功短信,同时触发计费账单服务将该房产纳入当季计费范围。

功能 说明
信息录入 录入用户姓名、证件号、联系方式、用户类型(居民/非居民),系统校验证件格式唯一性
档案建立 自动生成用户编号和档案,房产服务关联供热房产(小区/楼栋/单元/户室),记录供热面积和入网信息
合同签署 合同服务基于模板自动生成供热合同 PDF,用户线上签字或线下签署,合同文件存储至文件服务

blog-mermaid-03-sequenceDiagram.png

编排要点:用户服务作为开户流程的编排者,同步调用房产服务验证房产有效性并建立绑定关系,调用合同服务生成合同。合同文件生成通过文件服务异步处理,不阻塞开户主流程。用户-房产绑定关系由房产服务管理,绑定操作与用户档案创建通过 Saga 模式协调——任一步骤失败则整体回滚。

4.2 热费缴纳

涉及服务:计费账单服务、收费服务、支付服务、发票服务、通知服务

用户通过小程序或 APP 查询应缴账单,选择缴费方式和支付渠道完成在线支付。支付成功后,收费服务回写账单状态(调用计费账单服务),触发发票服务自动开具电子发票,通知服务向用户推送缴费成功消息和发票链接。

功能 说明
账单查询 按用户编号和供热季查询待缴账单,展示基本热费、计量热费、滞纳金等明细
在线支付 支持微信、支付宝、银联等渠道,支付服务对接第三方支付平台完成扣款
发票开具 支付成功后自动开具增值税电子普通发票,用户可在线查看和下载

blog-mermaid-04-sequenceDiagram.png

一致性保障:缴费链路涉及收费服务、支付服务、计费账单服务三个数据源,采用 Seata AT 模式保证缴费订单创建与账单状态回写的分布式事务一致性。支付回调采用幂等设计(基于订单号去重),防止重复回调导致重复入账。发票开具为异步操作,通过消息队列解耦,开票失败不影响缴费主流程,由对账任务补偿。

4.3 停复供申请

涉及服务:审批服务、计费账单服务、IoT 控制服务、通知服务

用户因房屋空置、装修等原因申请停止或恢复供热。系统根据申请类型发起审批流程,审批通过后,计费账单服务调整费用计算方式(停供期间按基本热费收取),IoT 控制服务远程控制入户阀门关闭或开启,全程通过通知服务向用户和运维人员推送节点状态。

功能 说明
申请审批 用户提交停供/复供申请,系统根据业务规则自动流转至对应审批人(片区管理员→计量管理员)
费用调整 审批通过后,计费账单服务按停供日期重新计算热费,停供期间仅收取基本热费(通常为全额的 30%-50%)
阀门控制 IoT 控制服务通过 IoT 平台远程控制入户阀门开关,记录操作日志,支持控制失败自动重试和人工介入

blog-mermaid-05-sequenceDiagram.png

异常处理:阀门控制是物理设备操作,可能因网络中断、设备离线等原因失败。设计三级容错策略:一级自动重试(3 次,间隔递增),二级转人工处理(生成运维工单),三级告警通知(IoT 控制服务向运维人员推送告警)。费用调整在审批通过即刻生效,不依赖阀门控制结果——即使阀门控制失败,费用已按停供计算,避免用户因设备故障多缴费用。

4.4 退费处理

涉及服务:收费服务、审批服务、支付服务、通知服务

用户因多缴、停供退费、计费错误等原因申请退费。收费服务受理退费申请并冻结原收费记录,审批服务按金额分级审批(500 元以下片区审批,500 元以上需财务审批),审批通过后支付服务执行原路退款,收费服务更新账单和发票状态。

功能 说明
退费申请 用户或营业员发起退费申请,关联原缴费记录,填写退费原因和退费金额
审核审批 按退费金额分级审批,系统校验退费金额不超过原缴金额,审批通过后冻结原收费记录
退款处理 支付服务按原支付渠道执行退款(微信/支付宝原路退回,现金退费生成退费单据),退款成功后更新账单和发票

blog-mermaid-06-sequenceDiagram.png

资金安全:退费流程涉及资金退还,采用先冻结后退款策略——退费申请提交即冻结原收费记录,防止在此期间再次发起退费。退款操作通过支付服务的退款接口执行,原路退回至用户原支付账户。退款金额超过原缴金额时系统拒绝,退款与发票冲红通过消息队列异步联动,保证财务数据一致性。

4.5 报修投诉

涉及服务:客服服务、通知服务、设备档案服务

用户通过热线电话、小程序或 APP 提交报修或投诉工单。客服服务创建工单并自动分类派单,通知服务向维修人员推送派单信息。维修人员现场处理完毕后回填处理结果,系统触发回访评价通知。如涉及设备故障,工单关联设备档案服务记录维修日志。

功能 说明
工单受理 多渠道受理(电话/小程序/APP/现场),自动分类(报修/投诉/咨询),关联用户和房产信息
派单通知 按片区和工单类型自动匹配维修人员,通过短信和 APP 推送派单通知,支持抢单和指派两种模式
维修处理 维修人员现场处理,填写处理结果和维修材料,上传现场照片,如涉及设备故障同步至设备档案服务

blog-mermaid-07-sequenceDiagram.png

SLA 管理:工单按紧急程度设置不同的响应时效(紧急 2 小时、普通 24 小时、咨询 48 小时),超时未接单自动升级并通知主管。维修人员现场无法解决的工单可申请转单或技术支援,工单全程状态可追溯,形成完整的服务闭环。

4.6 报表统计

涉及服务:数据分析服务

管理层通过数据分析服务查看收费统计、欠费分析和经营分析等报表。数据分析服务内部的数据管道组通过 CDC 实时捕获各业务服务的 MySQL 数据变更,清洗转换后写入数据仓库;查询服务组基于数据仓库的预聚合数据生成多维分析报表,支持按时间、片区、用户类型等维度灵活筛选。

功能 说明
收费统计 按日/月/季/年统计收费金额、收缴率、缴费笔数,支持按片区和收费渠道下钻
欠费分析 统计欠费用户数、欠费金额、账龄分布,识别长期欠费用户,支撑催收策略
经营分析 收入趋势、成本对比、收缴率同比环比、用户增长分析,为经营决策提供数据支撑

blog-mermaid-08-flowchart.png

数据时效性:收费统计和欠费分析报表基于数据仓库的预聚合数据,数据延迟控制在 5 分钟以内(准实时)。经营分析报表因涉及跨表关联和复杂计算,采用 T+1 批量预计算。实时性要求高的场景(如当日收费看板),查询服务组直接查询业务服务 MySQL 的只读副本,通过缓存减少压力。

4.7 场景与服务对照总览

业务场景 涉及服务 核心功能
新用户开户 用户服务、房产服务、合同服务、文件服务、通知服务 信息录入、档案建立、合同签署
热费缴纳 计费账单服务、收费服务、支付服务、发票服务、通知服务 账单查询、在线支付、发票开具
停复供申请 审批服务、计费账单服务、IoT 控制服务、通知服务 申请审批、费用调整、阀门控制
退费处理 收费服务、审批服务、支付服务、通知服务 退费申请、审核审批、退款处理
报修投诉 客服服务、通知服务、设备档案服务 工单受理、派单通知、维修处理
报表统计 数据分析服务 收费统计、欠费分析、经营分析

5. 服务间通信与数据一致性

5.1 通信模式

平台采用同步与异步混合的通信策略。同步调用用于需要即时响应的业务操作(如账单查询、缴费下单),异步消息用于非阻塞的事件通知和解耦操作(如开票通知、数据同步)。

通信模式 技术方案 适用场景 说明
同步调用 OpenFeign + Ribbon 账单查询、缴费下单、用户信息校验、审批发起 声明式 HTTP 调用,集成负载均衡和熔断降级。超时设置 3s(查询)/ 5s(写入)
异步消息 RocketMQ 缴费成功通知、开票触发、数据同步事件、工单状态变更 生产者发送消息后不等待消费结果,事务消息保证本地事务与消息发送的原子性
事件驱动 RocketMQ + 事件总线 用户开户事件、退费完成事件、阀门控制结果事件 领域事件通过消息广播,多个消费方独立订阅,生产者与消费者完全解耦

5.2 容错与降级策略

  • 熔断(Circuit Breaker):基于 Sentinel 实现,当某服务错误率超过 50% 或响应时间超过 2s,自动熔断 10s,快速失败避免级联雪崩。
  • 降级(Fallback):非核心服务不可用时返回降级数据(如通知服务宕机时缴费仍可完成,通知延迟补偿发送)。
  • 限流(Rate Limiting):网关层和服务层双重限流。缴费高峰期支付服务限流 500 QPS,超出请求排队等待。
  • 重试(Retry):幂等接口支持自动重试(最多 3 次,指数退避),非幂等接口通过请求 ID 去重。

5.3 数据一致性策略

微服务架构下,每个服务拥有独立数据库,跨服务的数据一致性是架构设计的核心挑战。平台根据业务场景的一致性要求强度,采用分层一致性策略:

一致性级别 技术方案 适用场景 实现要点
强一致性 Seata AT 模式 缴费下单+账单回写、退费+账单恢复 通过全局事务 ID 协调多个服务本地事务,任一分支失败全局回滚。适用于短事务(< 3s)
最终一致性 本地消息表 + 对账 发票开具、通知发送、数据同步 业务操作与消息记录在同一本地事务中写入,后台定时扫描消息表投递至 MQ。消费方幂等处理,每日对账补偿差异
事件最终一致性 Saga 模式(编排式) 开户流程、停复供流程 每一步操作定义正向动作和补偿动作,任一步骤失败按逆序执行补偿

blog-mermaid-09-flowchart.png

5.4 幂等性设计

所有写操作接口均需保证幂等性,防止网络重试或消息重复消费导致数据不一致。平台统一采用请求 ID + 状态机校验的双重幂等方案:客户端每次请求携带唯一 requestId,服务端基于 Redis 记录已处理的 requestId(TTL 24h),同时通过业务状态机校验当前状态是否允许该操作。

场景 幂等键 实现方式
支付回调 支付订单号 Redis SETNX 去重 + 订单状态校验(仅"支付中"可变更为"已支付")
退费执行 退费申请单号 数据库唯一索引 + 状态校验(仅"审批通过"可执行退款)
消息消费 消息 ID 消费方维护消息处理记录表,消费前查询是否已处理
阀门控制 指令流水号 设备指令表唯一索引 + 设备当前状态校验(已关阀不重复关阀)

6. 数据架构

6.1 数据库分库策略

平台遵循每个微服务独立数据库的原则(Database per Service),禁止跨服务直接访问数据库,所有跨服务数据交互通过 API 或消息完成。

服务 数据库 存储引擎 核心表
用户服务 user_db InnoDB 用户主表、用户扩展表、操作日志表
房产服务 property_db InnoDB 房产主表、面积信息表、变更记录表、入网登记表、用户房产关系表
合同服务 contract_db InnoDB 合同模板表、合同主表、签署记录表、归档文件引用表
计费账单服务 billing_bill_db InnoDB 计费规则表、计费结果表、热量数据表、调整记录表、账单主表、账单明细表、状态变更日志表
收费服务 charge_db InnoDB 收费订单表、收费记录表、退费申请表、对账记录表
支付服务 payment_db InnoDB 支付订单表、支付流水表、退款记录表、渠道配置表
发票服务 invoice_db InnoDB 发票主表、发票明细表、冲红记录表
审批服务 approval_db InnoDB 流程定义表、审批任务表、审批记录表
设备档案服务 device_archive_db InnoDB 设备档案表、设备状态表、设备房产关系表、维护记录表
IoT 控制服务 InfluxDB + Redis 时序 + KV 热量读数时序数据、设备状态时序数据、连接状态缓存
客服服务 customer_db InnoDB 工单主表、处理记录表、维修人员表、评价表
通知服务 notification_db + Redis InnoDB 通知记录表、模板表、配置表(Redis 存通知队列)
数据分析服务 ClickHouse/Doris 列式存储 汇总事实表、预聚合报表表、同步日志表、ETL 任务配置表

6.2 分库分表策略

对于数据量增长较快的账单表和收费记录表,采用分表策略缓解单表性能瓶颈。分表键选择用户编号,采用一致性哈希分片,保证同一用户的账单和收费记录落在同一分片。分表中间件使用 ShardingSphere。

分表策略 分表数量 说明
账单主表 用户编号取模 16 表 单表数据量控制在 500 万行以内,按供热季归档历史数据
收费记录表 用户编号取模 16 表 与账单表同一分片键,支持 JOIN 查询
支付流水表 订单号哈希 8 表 按月分区,超 6 个月数据迁移至归档库
工单表 创建时间按月 按月分表 历史工单按年归档,活跃数据保持热查询

6.3 缓存策略

缓存场景 缓存方案 TTL 说明
用户信息 Redis Hash 30 分钟 用户基础信息缓存,写操作后主动更新缓存
房产信息 Redis Hash 60 分钟 房产数据变更频率低,缓存命中率预计 > 95%
计费规则 Redis String 永久(主动失效) 规则变更时主动刷新,避免热季计费时频繁查库
账单列表 Redis List + ZSet 10 分钟 按用户编号缓存,缴费后主动失效
报表看板 Redis String 5 分钟 预聚合结果缓存,降低数据仓库查询压力
分布式锁 Redis SETNX 30 秒 防重复提交、防并发操作(如同一账单不可重复缴费)
设备连接状态 Redis Hash 60 秒 IoT 控制服务维护设备在线状态,心跳续期

6.4 数据归档与备份

归档策略:账单和收费记录按供热季归档,超过 3 个供热季的数据迁移至归档库(独立 MySQL 实例),业务库仅保留近 3 季数据。归档库可独立扩容,不影响在线业务性能。报表查询跨历史数据时,通过联邦查询合并在线库和归档库结果。

备份类型 频率 保留周期 说明
全量备份 每日凌晨 30 天 Xtrabackup 物理备份,备份文件上传至对象存储
增量备份 每小时 7 天 基于 Binlog 增量备份,支持任意时间点恢复
Binlog 归档 实时 90 天 Binlog 实时上传至对象存储,用于数据同步和灾难恢复
跨地域备份 每日 1 年 异地容灾备份,主备机房网络隔离

7. 部署与运维架构

7.1 容器化部署架构

平台基于 Kubernetes 集群部署,所有微服务容器化运行。集群按环境隔离分为开发、测试、预发、生产四套环境,生产环境采用多节点高可用部署。每个微服务通过 Deployment 管理 Pod 副本,配合 HPA 根据指标自动伸缩。IoT 控制服务因需维持设备长连接,采用 StatefulSet 部署并按设备分片。

blog-mermaid-10-flowchart.png

7.2 弹性伸缩策略

服务 最小副本 最大副本 伸缩触发条件
API 网关 2 8 CPU > 70% 扩容,CPU < 30% 缩容
收费服务 2 10 缴费季(11-3 月)预热扩容至 5 副本,CPU > 60% 继续扩容
支付服务 2 8 QPS > 300 扩容,与收费服务同步伸缩
计费账单服务 2 6 账单生成期批量任务高峰扩容
IoT 控制服务 3 12 按设备连接数分片扩容,每分片承载 ≤ 5000 设备连接
数据分析服务 1 4 月初报表查询高峰扩容(查询组),数据管道组固定 2 副本
其他服务 2 4 标准伸缩策略

7.3 监控告警体系

  • 指标监控(Prometheus):采集各服务 JVM 指标、HTTP 请求指标、自定义业务指标(缴费成功率、账单生成量、设备在线率等),15s 采集间隔。
  • 可视化看板(Grafana):服务健康总览、缴费实时看板、数据库性能看板、IoT 设备状态看板。
  • 分级告警(AlertManager):P0 电话+短信(服务宕机)、P1 短信+钉钉(错误率突增)、P2 钉钉(资源预警)。
  • 链路追踪(SkyWalking):分布式调用链追踪,慢请求自动标记,服务依赖拓扑图,帮助快速定位性能瓶颈。

7.4 CI/CD 流水线

采用 GitOps 模式持续部署,代码提交后自动触发构建、测试、镜像打包,通过 ArgoCD 将镜像部署至 K8s 集群。

阶段 工具 说明
代码提交 GitLab 分支策略:develop → test,release → pre-prod,master → prod
构建测试 GitLab CI + Maven 单元测试覆盖率 ≥ 70%,SonarQube 代码质量扫描
镜像打包 Docker + Harbor 多阶段构建,镜像按 服务名:分支-commit时间 标签管理
部署预发 ArgoCD 自动同步至预发环境,跑集成测试和回归测试
灰度发布 Argo Rollouts 生产环境按 10% → 30% → 100% 流量逐步切换,异常自动回滚
回滚 ArgoCD 一键回滚至上一版本镜像,回滚时间 < 30s

7.5 日志管理

各服务采用 Logback 输出结构化 JSON 日志,通过 Filebeat 采集至 ELK Stack。日志统一包含 TraceID(SkyWalking 生成),支持按 TraceID 贯穿查询完整调用链。敏感信息(证件号、银行卡号)在日志输出前脱敏处理。IoT 控制服务的设备通信日志单独存储至 InfluxDB,支持按设备编号和时间范围查询。

8. 安全与合规设计

8.1 认证与授权

平台采用 OAuth2 + JWT 的统一认证授权方案。认证中心负责用户身份验证和 Token 签发,网关层负责 Token 校验和权限拦截,业务服务从 Token 中提取用户信息进行细粒度权限控制。

安全层次 技术方案 说明
身份认证 OAuth2 Password + JWT Access Token 有效期 2h,Refresh Token 有效期 7d,支持多端同时登录
权限控制 RBAC 角色权限模型 角色 → 权限 → 资源三级模型,数据权限按片区/小区隔离
接口安全 签名 + 时间戳 + 防重放 第三方接入接口强制签名验签,5 分钟内相同请求拒绝
传输安全 HTTPS + TLS 1.3 全站 HTTPS,证书自动续期,内部服务间通信可选 mTLS
密码安全 BCrypt + 盐值 用户密码 BCrypt 加密存储,密码强度校验(长度 ≥ 8,含字母数字)

8.2 数据安全

安全措施 适用范围 实现方式
敏感数据脱敏 证件号、手机号、银行卡号 展示层脱敏(138****5678),存储层加密(AES-256),日志层脱敏
数据库加密 用户证件号、银行账户 应用层 AES-256 加密存储,密钥由 KMS 管理,定期轮换
传输加密 所有网络通信 HTTPS/TLS 加密传输,内部服务间可选 mTLS 双向认证
数据隔离 多片区/多热源 按片区数据行级隔离,查询自动注入片区过滤条件
SQL 注入防护 所有数据库操作 MyBatis 参数化查询,网关层 SQL 注入检测过滤器

8.3 审计日志

所有涉及资金操作和敏感数据访问的行为均记录审计日志,审计日志独立存储(audit_db),不可篡改,保留期不少于 5 年。

审计类型 记录内容 触发时机
操作审计 操作人、操作时间、操作类型、操作对象、变更前后值 用户开户/变更、费用调整、退费审批、阀门控制
登录审计 登录人、登录时间、IP、设备、登录结果 所有登录尝试(成功/失败)
数据访问审计 访问人、访问时间、访问的数据对象、数据量 批量导出用户数据、查询用户敏感信息
系统审计 配置变更、权限变更、服务上下线 Nacos 配置修改、角色权限调整、服务部署/下线

8.4 合规要求

供热收费涉及公共事业资金管理,需满足以下合规要求:收费资金日清日结,每日对账确保账实相符;发票开具符合国家税务总局电子发票规范;用户个人信息保护遵循《个人信息保护法》,数据采集需用户授权,敏感数据加密存储;供热计量数据保存期不少于 3 个供热季,支撑费用争议追溯。

8.5 安全防护体系

blog-mermaid-11-flowchart.png

8.6 容灾与高可用

容灾层面 方案 RTO / RPO
应用层 K8s 多节点部署,Pod 故障自动重启,服务多副本 RTO < 30s / RPO = 0
IoT 控制层 StatefulSet 有状态部署,按设备分片,故障分片设备自动重连至健康节点 RTO < 60s / RPO = 0
中间件层 Nacos/RocketMQ/Redis 集群部署,自动故障转移 RTO < 60s / RPO = 0
数据库层 MySQL 主从同步 + 半同步复制,主库故障自动切换从库 RTO < 5min / RPO < 1s
机房级容灾 双机房主备部署,Binlog 异步复制至备机房,主机房整体故障时切换 RTO < 30min / RPO < 5min

9. Spring Cloud Alibaba 服务治理

9.1 服务治理体系总览

平台基于 Spring Cloud Alibaba 构建微服务治理体系,涵盖服务注册发现、配置管理、流量控制、服务容错、API 网关、分布式事务和服务安全七大能力域。各组件由 Nacos、Sentinel、Spring Cloud Gateway、Seata、RocketMQ 等 Alibaba 开源组件提供,形成统一的治理技术栈。

blog-mermaid-12-flowchart.png

9.2 Nacos 服务注册与发现

Nacos 同时承担服务注册中心与配置中心双重角色,是平台服务治理的基础设施入口。

9.2.1 集群部署

生产环境部署 3 节点 Nacos 集群,采用 raft 协议选举 Leader,确保注册数据强一致性。数据存储采用 MySQL 外部持久化(而非内嵌 Derby),支持配置历史版本回滚和审计追踪。

blog-mermaid-13-flowchart.png

9.2.2 服务注册与健康检查

配置项 说明
心跳间隔 5s 服务实例每 5s 向 Nacos 发送心跳
健康超时 15s 15s 未收到心跳标记为不健康
摘除超时 30s 30s 未收到心跳从实例列表摘除
保护阈值 0.3 当健康实例比例低于 30% 时进入保护模式,不继续摘除实例,避免雪崩
命名空间 dev / test / pre-prod / prod 环境隔离,不同环境互不影响
分组 HEATING_GROUP 同一环境内按业务分组,支持灰度发布时按组路由
集群 DEFAULT / BATCH 同一服务区分在线集群(DEFAULT,处理实时请求)和批处理集群(BATCH,处理定时任务),实现流量隔离

保护阈值机制说明:当收费服务 10 个实例中 8 个宕机,健康实例仅剩 2 个(20% < 30%),Nacos 进入保护模式——不再摘除剩余实例,并将不健康实例也返回给消费方,让消费方自行决定是否调用。这避免所有实例被摘除后服务完全不可用,消费方可通过 Sentinel 熔断降级保护自身。

9.2.3 动态配置管理

Nacos Config 为各微服务提供集中化配置管理,支持配置的热更新(无需重启服务即生效)。

配置分类 Data ID 格式 示例 刷新方式
应用基础配置 {服务名}-{profile}.yaml charge-service-prod.yaml 启动加载
公共配置 common-{profile}.yaml common-prod.yaml(数据库连接池、Redis 地址等) 热更新
业务配置 {服务名}-biz.yaml charge-service-biz.yaml(收费渠道开关、限流阈值) 热更新(@RefreshScope
灰度配置 {服务名}-{profile}-gray.yaml payment-service-prod-gray.yaml 热更新

配置变更审计:Nacos 配置变更需经过审批流程——修改人在控制台提交变更,审批人确认后生效。每次变更记录历史版本,支持一键回滚至任意历史版本。变更通知通过钉钉群推送,确保团队感知。

灰度发布配置示例:通过 Nacos 配置灰度规则,控制部分流量路由到新版本实例。例如收费服务发布 V2 版本时,先在 Nacos 配置 charge-service-prod-gray.yaml 指定 10% 流量路由至 V2 实例,观察无异常后逐步调整至 30%、100%,最终全量切换。

9.3 Sentinel 流量控制与熔断降级

Sentinel 是平台的流量治理核心,提供流控、熔断降级、系统自适应保护和热点参数限流四类能力。所有规则存储在 Nacos 中,支持动态推送和热更新。

9.3.1 流控规则

流控对象 阈值 流控行为 说明
API 网关 全局限流 2000 QPS 快速失败 超出阈值的请求返回 429,保护后端服务
支付服务 createPayment 500 QPS 排队等待(超时 3s) 第三方支付接口有 QPS 限制,排队避免被拒绝
收费服务 createChargeOrder 800 QPS 快速失败 缴费高峰期保护收费服务
计费账单服务 batchGenerateBill 5 并发线程 排队等待(超时 60s) 批量账单生成是 CPU 密集型任务,限制并发防 OOM
数据分析服务 queryReport 100 QPS 快速失败 报表查询保护数据仓库

流控模式说明

  • 直接限流:对资源本身限流(如支付接口 500 QPS)。
  • 关联限流:当关联资源达到阈值时限流本资源。例如支付接口达到 500 QPS 时,限制收费服务继续创建新缴费订单,避免支付服务积压。
  • 链路限流:只限制从指定入口来的流量。例如同一接口从 Web 端调用限流 1000 QPS,从小程序调用限流 500 QPS。

9.3.2 熔断降级规则

资源 熔断策略 阈值 恢复策略 降级动作
支付服务调用 异常比例 50%(最小 5 次请求) 半开探测,10s 后放 1 个请求试探 返回"支付通道繁忙,请稍后重试"
发票服务调用 异常数 10 次/分钟 半开探测,30s 后放 1 个请求 跳过开票,缴费仍成功,开票由对账任务补偿
通知服务调用 慢调用比例 RT > 2s 占比 > 60% 半开探测,15s 后放 1 个请求 跳过实时通知,消息存入队列延迟发送
IoT 控制服务调用 异常比例 40%(最小 3 次请求) 半开探测,20s 后放 1 个请求 阀门控制转异步重试,不影响停复供审批流程
数据分析服务查询 慢调用比例 RT > 5s 占比 > 50% 半开探测,60s 后放 1 个请求 返回缓存的上一次报表数据

熔断状态机

blog-mermaid-14-stateDiagram-v2.png

9.3.3 系统自适应保护

Sentinel 系统自适应保护从整体维度对服务实例进行保护,避免因负载过高导致服务完全不可用:

维度 阈值 说明
Load1(系统负载) 4.0 Linux 1 分钟平均负载超过 4.0 触发保护(4 核服务器基准)
CPU 使用率 80% CPU 超过 80% 触发保护,拒绝新请求
平均 RT 1s 所有入口请求平均响应时间超过 1s 触发保护
入口 QPS 1500 入口总 QPS 超过 1500 触发保护
线程数 200 入口总线程数超过 200 触发保护

与 K8s HPA 的配合:Sentinel 系统自适应保护是"软限流"(拒绝请求但不杀进程),K8s HPA 是"硬扩容"(增加 Pod 副本)。两者配合:CPU 超过 70% 时 HPA 扩容新 Pod,同时 Sentinel 在 80% 时开始限流保护现有 Pod,避免扩容延迟期间服务雪崩。

9.3.4 热点参数限流

针对特定业务参数进行精细化限流,避免热点数据打垮服务:

限流资源 参数 阈值 说明
queryBill userId 10 QPS/用户 防止单用户高频查询账单(可能是脚本攻击)
createPayment userId 2 QPS/用户 防止单用户重复发起支付(防重复下单)
queryReport reportType 5 QPS/报表类型 防止报表查询并发过高拖挂数据仓库
controlValve deviceId 1 QPS/设备 防止对同一设备重复下发控制指令

9.4 Spring Cloud Gateway API 网关

Spring Cloud Gateway 作为平台唯一入口,承载路由转发、认证鉴权、限流熔断、灰度发布等网关层治理职责。

9.4.1 网关架构

blog-mermaid-15-flowchart.png

9.4.2 路由配置

路由规则存储在 Nacos 中,支持动态新增和修改路由而无需重启网关:

路由 ID 谓词(Predicate) 目标服务 过滤器 说明
user-service-route Path=/api/user/** user-service RewritePath 用户服务路由
charge-service-route Path=/api/charge/** charge-service RewritePath + RequestRateLimiter 收费服务,含限流
payment-service-route Path=/api/payment/** payment-service RewritePath + CircuitBreaker 支付服务,含熔断
report-service-route Path=/api/report/** analytics-service RewritePath + 重试(3次) 报表查询,含重试
gray-route Header=X-Gray-Version,v2 charge-service (v2) RewritePath + GrayFilter 灰度路由,按 Header 路由至 v2

9.4.3 全局过滤器链

请求经过网关时按顺序通过以下过滤器,每个过滤器承担独立职责:

顺序 过滤器 职责 异常处理
-100 BlackListFilter IP 黑名单检查,恶意 IP 直接拒绝 返回 403
-90 CorsFilter 跨域处理,配置允许的 Origin 和 Header
-80 AuthFilter JWT Token 校验,解析用户身份注入 Header 返回 401
-70 PermissionFilter RBAC 权限校验,检查请求路径与用户角色的匹配 返回 403
-60 TraceFilter 注入 TraceID(SkyWalking),传递链路上下文
-50 LogFilter 记录请求日志(方法、路径、耗时、状态码)
0 路由过滤器 限流、熔断、重试等路由级处理 按过滤器策略
+100 ResponseFilter 统一响应体封装,添加 Request-ID

9.4.4 灰度发布

网关层支持基于 Header 的灰度发布,配合 Nacos 的版本元数据实现流量按比例路由:

灰度流程

  1. 新版本服务实例注册到 Nacos 时携带 version=v2 元数据。
  2. 网关灰度过滤器检查请求 Header X-Gray-Version
    • Header 为 v2 → 路由至 version=v2 的实例。
    • Header 为空 → 按权重路由,默认 90% 流量至 v1、10% 至 v2。
  3. 灰度比例通过 Nacos 配置动态调整(10% → 30% → 100%),无需修改代码或重启。
  4. 灰度期间通过 SkyWalking 监控 v2 实例的调用成功率和响应时间,异常时一键回滚(将 Nacos 灰度比例调回 0%)。

9.5 Seata 分布式事务

Seata 是平台的分布式事务解决方案,覆盖强一致性场景(AT 模式)和柔性事务场景(TCC/Saga 模式)。

9.5.1 Seata 架构

blog-mermaid-16-flowchart.png

部署说明:Seata Server 部署 3 节点集群,事务会话存储在独立 MySQL 中。各业务服务的数据库中创建 undo_log 表,用于 AT 模式记录数据变更前后的快照,支撑自动回滚。

9.5.2 AT 模式(自动补偿)

AT 模式适用于短事务场景,业务代码无感知(只需添加 @GlobalTransactional 注解),Seata 自动生成反向 SQL 实现补偿。

缴费场景 AT 模式流程

步骤 参与者 动作 说明
1 收费服务(TM) 开启全局事务,获取 XID @GlobalTransactional 注解触发
2 收费服务(RM) 执行本地事务:创建缴费订单 Seata 拦截 SQL,记录 undo_log(变更前快照 + 变更后快照)
3 计费账单服务(RM) 执行本地事务:回写账单状态为"已缴" XID 通过 RPC Header 传递,分支自动加入全局事务
4 收费服务(TM) 全局提交 TC 通知所有分支提交,异步清理 undo_log
异常分支 全局回滚 TC 通知所有分支回滚,根据 undo_log 生成反向 SQL 执行

AT 模式全局锁机制:Seata 通过全局锁防止写冲突——分支事务提交本地事务前需获取全局锁,如果该行数据已被其他全局事务锁定则等待。这保证在全局事务未完成期间,其他全局事务不能修改同一行数据。但本地事务可以读取(读已提交隔离级别)。

9.5.3 TCC 模式(手动补偿)

TCC 模式适用于需要精细化控制补偿逻辑的场景,业务需实现 Try、Confirm、Cancel 三个方法。

适用场景:退费流程中"冻结收费记录"这一步骤。AT 模式只能回滚数据变更,但"冻结"是一个状态流转(已缴费→退费中),需要确保补偿时恢复为"已缴费"而非简单的数据快照回滚。

阶段 收费服务动作 说明
Try 将收费记录状态改为"退费中",记录冻结时间 预留资源,锁定记录
Confirm 确认退费完成,状态改为"已退费" 确认业务操作
Cancel 回滚状态为"已缴费",清除冻结标记 补偿操作,恢复原始状态

TCC 注意事项

  • 空回滚:Try 未执行但 Cancel 被调用(如 Try 超时后 TC 触发回滚)。Cancel 需检查 Try 是否执行过,未执行则直接返回成功。
  • 幂等:Confirm 和 Cancel 可能被重复调用(网络重试),必须保证幂等。通过事务状态字段判断——已经是目标状态则直接返回成功。
  • 悬挂:Cancel 先于 Try 执行(Try 超时后 Cancel 触发,但延迟的 Try 随后到达)。Try 需检查全局事务是否已回滚,已回滚则不执行。

9.5.4 Saga 模式(长流程编排)

Saga 模式适用于多步骤、长耗时、涉及外部系统调用的业务流程(如开户流程)。平台采用 Seata Saga 状态机引擎(State Machine Engine),通过 JSON 定义流程编排。

开户流程 Saga 定义

{
  "Name": "userOpenAccountSaga",
  "States": [
    {
      "Name": "createUser",
      "Type": "ServiceTask",
      "ServiceName": "userService",
      "ServiceMethod": "createUser",
      "CompensateMethod": "cancelUser",
      "Next": "bindProperty"
    },
    {
      "Name": "bindProperty",
      "Type": "ServiceTask",
      "ServiceName": "propertyService",
      "ServiceMethod": "bindUserProperty",
      "CompensateMethod": "unbindUserProperty",
      "Next": "generateContract"
    },
    {
      "Name": "generateContract",
      "Type": "ServiceTask",
      "ServiceName": "contractService",
      "ServiceMethod": "generateContract",
      "CompensateMethod": "voidContract",
      "Next": "succeed"
    },
    {
      "Name": "succeed",
      "Type": "Succeed"
    }
  ]
}

Saga 状态持久化:Saga 引擎将每一步的执行状态(STARTED/COMPLETED/FAILED/COMPENSATED)持久化到 seata_saga_log 表。如果流程执行中服务宕机,重启后引擎读取日志恢复到中断点,继续执行或触发补偿。

9.6 服务安全与认证

9.6.1 OAuth2 + JWT 认证体系

平台采用 Spring Security OAuth2 + JWT 实现统一身份认证,认证中心独立部署,各业务服务通过网关层完成 Token 校验。

blog-mermaid-17-sequenceDiagram.png

Token 设计

属性 说明
签名算法 HS256
密钥 存储在 Nacos 配置中心,定期轮换(90天)
Payload userId、username、roles、permissions、iat(签发时间)、exp(过期时间)
AccessToken 有效期 2 小时
RefreshToken 有效期 7 天
刷新机制 AccessToken 过期后,客户端用 RefreshToken 换取新的 AccessToken,RefreshToken 旋转(旧 RefreshToken 失效)

多端登录控制:Redis 中以 session:{userId}:{deviceId} 为 Key 存储会话信息,支持同一用户多设备同时登录(如 Web 端 + 手机端),但单设备类型只允许一个会话(新登录踢掉旧会话)。

9.6.2 RBAC 权限模型

层级 实体 说明
用户 User 系统操作人员(营业员、管理员、财务、维修工等)
角色 Role 角色定义(如"片区营业员"、“财务审批人”、“系统管理员”)
权限 Permission 接口级权限(如 charge:refund:apply 退费申请、charge:refund:approve 退费审批)
资源 Resource 数据资源(如片区A的用户数据、小区B的房产数据)

数据权限隔离:通过网关层在请求中注入用户所属片区信息,业务服务在查询时自动追加片区过滤条件。例如片区营业员只能查询和操作本片区的用户和账单,财务审批人可查看全部片区数据。

角色 功能权限 数据权限
片区营业员 开户、收费、催收、工单受理 仅本片区
财务审批人 退费审批、对账查询、报表查看 全部片区
计量管理员 停复供审批、计费规则配置 分配的片区
维修工 工单接单、设备维修记录 分配的工单
系统管理员 全部功能 全部数据

9.6.3 服务间调用安全

内部服务间通过 Feign 调用时,需传递认证上下文和防止外部直接访问:

安全措施 实现方式 说明
Token 传递 Feign 拦截器自动将网关注入的 X-User-Id/X-Roles Header 透传 用户身份在调用链中保持
内部调用鉴权 服务间调用携带 X-Internal-Token(由认证中心签发的服务间专用 Token) 防止外部直接访问内部服务端口
网络隔离 K8s NetworkPolicy 限制业务服务端口仅允许网关 Pod 和同命名空间访问 网络层兜底隔离
敏感接口 退费、费用调整等接口需二次确认(短信验证码或操作密码) 防止误操作或恶意调用

9.7 服务可观测性

9.7.1 链路追踪(SkyWalking)

SkyWalking 通过 Java Agent 字节码增强实现无侵入式链路追踪,自动采集 HTTP、Feign、MQ、数据库等组件的调用链。

追踪维度 指标 用途
Trace 完整调用链(含跨服务、跨MQ) 请求全链路耗时分析
Span 单次操作(HTTP请求/SQL/MQ消费) 定位慢操作
Service 服务级聚合(QPS、平均RT、错误率) 服务健康监控
Instance 实例级聚合(CPU、内存、JVM) 实例级问题定位
Endpoint 接口级聚合(单个API的QPS/RT/错误率) 接口性能分析

TraceID 贯穿:网关层生成 TraceID,通过 HTTP Header 和 MQ 消息属性传递,确保一次业务操作(可能涉及同步调用+异步消息)的全部日志可通过 TraceID 串联查询。

9.7.2 指标监控(Prometheus + Grafana)

各服务通过 Micrometer 暴露 Prometheus 指标端点,Prometheus 每 15s 采集一次:

指标分类 采集内容 告警规则
JVM 指标 堆内存使用率、GC 次数/耗时、线程数 堆内存 > 85% 持续 5min 告警;Full GC > 3次/小时告警
HTTP 指标 QPS、响应时间(P50/P95/P99)、错误率 P99 > 3s 告警;5xx 错误率 > 1% 告警
业务指标 缴费成功率、账单生成量、退费金额、设备在线率 缴费成功率 < 95% 告警;设备离线率 > 10% 告警
中间件指标 Redis 连接数/命中率、MQ 积压量/消费延迟、MySQL 慢查询 Redis 命中率 < 90% 告警;MQ 积压 > 1000 告警
K8s 指标 Pod CPU/内存使用率、Pod 重启次数 CPU > 80% 持续 5min 告警;Pod 重启 > 3次/小时告警

Grafana 看板体系

看板 使用者 核心内容
服务健康总览 运维 11 个服务的状态、QPS、RT、错误率一览
缴费实时看板 业务/运维 当日缴费笔数、金额、成功率、支付渠道分布
IoT 设备看板 运维 设备在线率、告警数、阀门控制成功率
数据库性能看板 DBA MySQL 连接数、慢查询、主从延迟、大表扫描
JVM 诊断看板 开发 堆内存趋势、GC 日志、线程 Dump
Sentinel 流控看板 运维 限流/熔断触发次数、降级次数、规则变更历史

9.7.3 日志管理(ELK)

日志类型 采集方式 存储 保留周期
业务日志 Logback JSON + Filebeat Elasticsearch 30 天
访问日志 网关层记录 + Filebeat Elasticsearch 30 天
慢查询日志 MySQL slow_query_log + Filebeat Elasticsearch 90 天
审计日志 业务服务写入 audit_db MySQL(独立库) 5 年
IoT 通信日志 IoT 控制服务写入 InfluxDB InfluxDB 90 天

日志规范:所有日志输出为结构化 JSON,必含字段 timestamplevelservicetraceIdmessage。敏感字段(证件号、银行卡号)在日志输出前自动脱敏,通过自定义 Logback Layout 实现。

10. RocketMQ 消息交互设计

10.1 消息架构总览

RocketMQ 是平台异步通信和事件驱动的核心中间件,承担三类职责:跨服务事件通知、异步业务解耦、数据同步管道。平台所有业务服务既是消息生产者也是消息消费者,通过 Topic 分类和 Tag 过滤实现消息的精准路由。

blog-mermaid-18-flowchart.png

10.2 Topic 与消息分类设计

平台按业务领域规划 Topic,每个 Topic 内通过 Tag 区分消息子类型。消费组按服务+场景命名,同一服务的不同业务逻辑通过 Tag 过滤分别消费。

Topic 类型 生产者 消费者(消费组) Tag 子类型 说明
payment-success 事务消息 收费服务 invoice-servicenotification-service online(在线支付)、offline(线下缴费) 缴费成功后触发开票和通知
refund-completed 事务消息 收费服务 notification-service full(全额退)、partial(部分退) 退费完成后触发通知
bill-generated 事务消息 计费账单服务 notification-service season(供热季账单)、adjust(调整账单) 账单生成后触发催缴通知
approval-result 普通消息 审批服务 notification-serviceiot-control-service pass(通过)、reject(驳回) 审批结果通知相关方
user-opened 普通消息 用户服务 notification-servicebilling-bill-service individual(个人)、enterprise(单位) 用户开户后触发通知和计费纳入
workorder-event 普通消息 客服服务 notification-service created(创建)、dispatched(派单)、completed(完成) 工单生命周期事件通知
device-alarm 普通消息 IoT 控制服务 notification-servicecustomer-service offline(离线)、fault(故障)、threshold(阈值告警) 设备告警实时推送
data-sync-cdc 普通消息 Canal/Debezium analytics-service 按源表名打 Tag 数据库变更事件同步至数据仓库

10.3 消息流转全景

以缴费场景为例,展示一次完整缴费从同步调用到异步消息的完整流转:

blog-mermaid-19-sequenceDiagram.png

10.4 消息不丢失保障

消息从生产到消费经历五个环节,每个环节都存在丢失风险。平台通过分层保障机制确保消息端到端可靠投递。

blog-mermaid-20-flowchart.png

10.4.1 生产端保障

事务消息机制:对于"业务操作+消息发送"必须原子性完成的场景(如缴费成功后触发开票),采用 RocketMQ 事务消息。流程分三步:

  1. 发送半消息:生产者先向 Broker 发送一条半消息(此时消费者不可见),Broker 返回半消息发送成功。
  2. 执行本地事务:生产者执行本地数据库事务(如更新收费记录状态为"已缴费")。
  3. 提交/回滚:本地事务成功则向 Broker 发送 Commit(消息对消费者可见),失败则发送 Rollback(删除半消息)。

blog-mermaid-21-sequenceDiagram.png

事务回查实现要点:生产者需实现 TransactionListener 接口的 checkLocalTransaction 方法,通过查询本地数据库判断事务是否成功提交。回查超时设置为 60s,最多回查 15 次,超过后 Broker 自动回滚半消息。

普通消息发送重试:对于非事务消息(如审批结果通知),生产端配置同步发送(SendStatus.SEND_OK)+ 重试 3 次(指数退避:1s、2s、4s)。三次均失败则记录本地错误日志,由定时任务补偿重发。

10.4.2 Broker 端保障

保障措施 配置 说明
同步刷盘 flushDiskType=SYNC_FLUSH 消息写入磁盘后才返回成功确认,避免宕机时内存中消息丢失
主从同步复制 brokerRole=SYNC_MASTER 主节点将消息同步至从节点后才返回成功,主节点故障时从节点数据完整
消息轨迹 traceTopic=RMQ_SYS_TRACE_TOPIC 记录消息从生产到消费的全链路轨迹,支持按 MessageID 查询消息流转路径
消息存储 CommitLog + ConsumeQueue 所有消息顺序写入 CommitLog 文件,ConsumeQueue 作为索引加速消费。文件保留 72h,过期自动清理

10.4.3 消费端保障

手动 ACK 机制:消费者处理完业务逻辑后才向 Broker 发送消费确认(ACK)。如果消费过程中抛出异常,不发送 ACK,Broker 会将消息重新投递给同一消费组的其他实例或重试。

消费重试机制:消费失败的消息进入重试队列(%RETRY%消费组名),RocketMQ 按 1s、5s、10s、30s、1m、2m、3m、4m、5m、6m、7m、8m、9m、10m、20m、30m 的梯度重试 16 次。16 次仍失败的消息进入死信队列(%DLQ%消费组名)。

死信队列处理:死信队列中的消息不会被自动消费,由后台监控任务扫描并告警,运维人员通过 RocketMQ 控制台查看消息内容,判断失败原因后手动重发或标记为已处理。平台额外开发了死信管理看板,展示各消费组的死信数量、积压趋势和失败原因分类。

幂等消费:由于网络重试和消息重投递,消费者可能收到重复消息。所有消费者基于消息 ID(msgId)或业务唯一键实现幂等:

消费场景 幂等键 实现方式
缴费成功 → 开票 缴费订单号 开票前查询 invoice 表是否已存在该订单号的开票记录
缴费成功 → 通知 通知流水号(消息体中生成) 通知服务 Redis SETNX 去重,TTL 24h
账单生成 → 通知 账单 ID + 供热季 通知记录表唯一索引(账单ID + 通知类型)
审批结果 → 通知 审批单号 + 审批结果 通知记录表唯一索引
设备告警 → 通知 告警事件 ID Redis SETNX 去重,TTL 1h
CDC 数据同步 Binlog 位点(offset) 消费记录表记录已处理的最大 offset

10.5 事务一致性处理

10.5.1 三种一致性场景与方案选型

平台根据业务场景的事务特征选择不同的一致性方案:

场景 一致性要求 方案 选型理由
缴费下单 + 账单回写 强一致性(必须同时成功/失败) Seata AT 模式 短事务(<3s),两个服务同一机房,网络可靠
缴费成功 + 触发开票/通知 最终一致性(允许短暂延迟) RocketMQ 事务消息 跨服务异步操作,允许延迟,不允许丢失
退费 + 退款 + 账单恢复 最终一致性 RocketMQ 事务消息 + 本地消息表 退款为外部调用,需保证状态一致
开户流程(用户+房产+合同) 最终一致性 Saga 编排式 多步骤长流程,每步可补偿
数据同步至数据仓库 最终一致性 本地消息表 + 普通消息 允许分钟级延迟,对账兜底

10.5.2 缴费场景的事务一致性处理

缴费是平台最核心的交易链路,涉及收费服务、支付服务、计费账单服务三个数据源。采用"Seata 强一致 + RocketMQ 事务消息"组合方案:

blog-mermaid-22-flowchart.png

设计要点

  • Seata 全局事务只覆盖"缴费订单创建+账单状态回写"这一步强一致性操作。支付回调到达后,收费服务在 Seata 事务内创建/更新缴费订单并调用计费账单服务回写账单状态。任一分支失败,全局事务回滚,缴费订单和账单状态均恢复原值。
  • Seata 事务提交后,收费服务通过 RocketMQ 事务消息发送 payment-success 事件。事务消息保证"本地事务提交"与"消息发送"的原子性——本地事务成功则消息一定投递,本地事务失败则消息一定不投递。
  • 发票开具、用户通知、数据同步三个下游操作通过普通消费异步执行,消费失败自动重试,不影响缴费主流程。

异常场景处理

异常场景 处理方式
Seata 事务回滚(账单回写失败) 缴费订单也回滚,支付服务发起退款,通知用户支付异常
事务消息半消息发送成功但本地事务执行中宕机 Broker 事务回查,收费服务查询缴费订单状态——不存在则 Rollback,存在且已提交则 Commit
事务消息 Commit 成功但消费者(发票服务)消费失败 RocketMQ 自动重试 16 次,仍失败进入死信队列,运维人工干预或对账任务补偿
重复消费(网络重试导致) 消费者幂等设计,重复消息被去重,不产生副作用

10.5.3 退费场景的事务一致性处理

退费涉及收费服务本地事务(更新收费记录为"已退费")+ 支付服务外部调用(执行退款)+ 计费账单服务回写(恢复账单为"待缴")。由于退款是外部支付渠道调用,无法纳入 Seata 事务,采用"本地消息表 + 事务消息"组合方案:

blog-mermaid-23-sequenceDiagram.png

关键设计

  • 退费采用"先标记后执行"策略——审批通过后先将收费记录标记为"退费中"并冻结,防止在此期间重复退费。
  • 退款执行(支付服务调用第三方接口)失败时,消息进入重试队列。重试期间收费记录保持"退费中"状态。
  • 16 次重试仍失败进入死信队列,运维介入处理(如第三方接口临时故障,恢复后手动重发消息)。
  • 如果死信处理最终确认退款无法成功(如原支付渠道已关闭),运维将收费记录状态回滚为"已缴费",并通过通知服务告知用户退费失败需线下处理。

10.5.4 开户场景的 Saga 编排

开户流程涉及用户服务(创建用户档案)、房产服务(绑定房产关系)、合同服务(生成合同)三个服务,是典型的长流程多步骤场景。采用 Saga 编排式(Orchestration),由用户服务作为编排者协调流程:

blog-mermaid-24-flowchart.png

Saga 补偿规则

失败步骤 补偿动作 补偿顺序
Step 3(合同生成失败) 作废合同(如有)→ 解绑房产关系 → 注销用户档案 逆序:3’→2’→1’
Step 4(通知失败) 不触发补偿,通知延迟补偿发送(非核心流程) 无补偿
Step 5(计费纳入失败) 不触发补偿,下次定时任务补纳入 无补偿

实现方式:用户服务维护一个 Saga 执行日志表(saga_log),记录每一步的执行状态(STARTED/COMPLETED/FAILED/COMPENSATED)。Saga 编排器读取日志判断当前进度,决定继续执行还是触发补偿。补偿动作通过同步 Feign 调用执行,补偿失败则记录告警,由运维介入。

10.5.5 对账兜底机制

无论采用哪种一致性方案,平台均部署对账任务作为最终兜底,确保数据一致性:

对账任务 执行频率 对账内容 异常处理
缴费对账 每日 02:00 收费记录(收费服务)与账单状态(计费账单服务)比对 状态不一致的记录生成对账差异报告,自动修复或人工确认
退费对账 每日 02:30 退费记录(收费服务)与退款流水(支付服务)比对 退款成功但收费记录未更新的,自动修复;退款失败但收费记录已标记的,触发告警
开票对账 每日 03:00 缴费记录(收费服务)与发票记录(发票服务)比对 已缴费未开票的记录重新触发开票消息
消息补偿对账 每小时 本地消息表中"已发送未确认"的消息 重新投递至 MQ
死信队列扫描 每 10 分钟 各消费组死信队列积压数量 超过 10 条触发 P1 告警,超 50 条触发 P0 告警

10.6 消息消费顺序性保障

部分业务场景要求消息按业务顺序消费(如同一账单的状态变更:待缴→部分缴→已缴),平台通过以下机制保障:

机制 适用场景 实现方式
分区顺序消息 同一用户/账单的消息顺序 生产者以用户编号或账单 ID 作为 shardingKey,同一 key 的消息路由到同一队列,消费者单线程消费该队列
全局顺序消息 极少使用(性能影响大) 整个 Topic 只用一个队列,全局严格有序。仅用于对顺序要求极高的审计日志场景
业务版本号 状态变更消息 消息体携带版本号,消费者校验版本号递增,乱序消息暂存等待前序消息到达

缴费场景顺序消费示例:同一用户的缴费消息以用户编号为 shardingKey,确保该用户的缴费成功、退费发起、退费完成等消息按业务发生顺序到达消费者。不同用户的消息可并行消费,不影响吞吐量。

10.7 消息积压与流量控制

供热缴费高峰期(11-3 月)消息量激增,需防止消息积压导致消费延迟:

策略 说明
消费者并发扩容 消费组配置 consumeThreadMax=64,高峰期自动增加消费线程。同时通过 K8s HPA 扩容消费者 Pod 数量
消费批量化 非实时性要求的消息(如数据同步 CDC)采用批量消费模式,单次拉取 32 条批量处理
消息优先级 通知类消息与交易类消息分 Topic 部署,交易类消息(缴费/退费)优先级高于通知类,避免通知积压影响交易
削峰填谷 缴费高峰期消息进入队列暂存,消费者按自身处理能力匀速消费。队列深度超过阈值时告警,触发消费者扩容
死信预警 死信队列积压超过 10 条触发 P1 告警,超 50 条触发 P0 告警,确保异常及时发现

10.8 RocketMQ 集群部署架构

生产环境部署 3 节点 RocketMQ 集群(1 主 2 从或 3 主 3 从),确保高可用:

blog-mermaid-25-flowchart.png

部署要点

  • NameServer 部署 3 节点,互不通信,各自维护路由信息。生产者和消费者从任一 NameServer 获取 Broker 路由信息,单节点故障不影响服务。
  • Broker 部署 2 组(A/B),每组 1 主 1 从。Topic 的队列分布在两个 Broker 组上实现负载均衡。主节点故障时从节点接管读请求,写请求自动切换至另一组主节点。
  • 配置同步刷盘(SYNC_FLUSH)+ 同步复制(SYNC_MASTER),确保消息写入主节点并同步至从节点后才返回成功,避免单点故障导致消息丢失。
  • RocketMQ Dashboard 提供消息看板、积压监控、死信管理、消息轨迹查询等功能。

10.9 消息设计规范

规范项 要求
消息体格式 JSON,字段命名使用下划线风格(order_iduser_id),禁止使用驼峰
消息体大小 单条消息体不超过 4KB;超过时只传递业务 ID,消费者通过 API 查询详情
消息头 必须包含 traceId(SkyWalking)、source(来源服务)、eventType(事件类型)、timestamp(发生时间)
消息版本 消息体包含 version 字段,消费端按版本兼容处理,支持消息格式平滑升级
Topic 命名 小写中划线风格(payment-success),以业务事件命名,不以服务命名
Tag 使用 同一 Topic 内的子类型用 Tag 区分,消费者按 Tag 过滤,避免无效消费
消费组命名 {服务名}-{场景} 格式(如 invoice-servicenotification-service),同一消费组内负载均衡
幂等设计 所有消费者必须实现幂等逻辑,幂等键在消息设计文档中明确定义
消费超时 单条消息消费超时 30s,超时后 Broker 自动重试
消息轨迹 生产环境开启消息轨迹,保留 7 天,用于问题排查和链路分析

11. 热费缴纳高可靠与高并发实战

热费缴纳是平台最核心的交易链路,也是业务风险最高的环节。北方供热季集中在 11 月至次年 3 月,缴费高峰出现在供热季首月(11 月)和截止日前一周,日均缴费量可达平时的 10-20 倍。本章先分析缴费全链路可能发生的问题,再给出逐项解决方案和落地实现。

11.1 缴费全链路问题分析

一次完整缴费涉及客户端、API 网关、收费服务、计费账单服务、支付服务、第三方支付渠道、发票服务、通知服务共 8 个参与方,经同步调用和异步消息两阶段完成。下表按链路节点梳理可能出现的故障场景:

blog-mermaid-26-flowchart.png

编号 问题场景 触发条件 业务影响
P1 网关限流导致用户请求被拒 缴费高峰 QPS 超过网关阈值 用户无法缴费,体验差
P2 用户重复提交缴费订单 网络延迟用户多次点击、客户端重试 产生重复订单,可能导致重复扣款
P3 账单状态不一致 并发缴费同一账单、跨服务事务未提交 两个用户同时缴同一账单,或多缴
P4 支付调起后超时未完成 用户打开支付页但未操作、网络中断 订单挂在"支付中"状态,账单无法被他人缴纳
P5 第三方支付渠道不可用 微信/支付宝系统故障或维护 用户无法完成在线支付
P6 支付回调丢失或重复 第三方回调网络抖动、重复推送 缴费成功但系统未更新,或重复入账
P7 事务消息回查失败 收费服务在本地事务提交后宕机、回查接口超时 消息状态不确定,下游服务可能收不到消息
P8 消息消费失败 发票服务/通知服务消费异常 缴费成功但用户未收到通知或发票
P9 开票失败 税务系统接口故障、发票额度不足 用户缴费成功但无法获取发票
P10 缴费高峰期服务雪崩 单服务过载→依赖服务连锁过载→全局不可用 平台完全瘫痪,所有用户无法缴费

11.2 重复下单防护(P2)

问题根因:用户在网络延迟场景下多次点击"缴费"按钮,或客户端因超时自动重试,导致同一账单产生多个缴费订单。如果支付服务为每个订单都调起支付,可能造成用户被多次扣款。

解决方案:前端防抖 + 后端幂等 + 分布式锁三重防护。

blog-mermaid-27-sequenceDiagram.png

落地实现

防护层 实现方式 说明
前端防抖 缴费按钮点击后 3s 内禁用,显示 loading 第一道防线,防止用户无意多次点击
客户端幂等 客户端生成 requestId,同一 requestId 的请求视为重复 网关层基于 requestId 去重,3 分钟内相同 requestId 直接返回上次结果
Redis 分布式锁 SETNX lock:charge:bill:{billId} NX EX 10 以账单 ID 为锁键,获取锁后才创建订单,10s 自动过期防死锁
订单复用 查询是否存在"支付中"状态的订单,存在则返回原订单 用户关闭支付页重新进入时复用已有订单,而非新建
数据库唯一约束 订单表对 (bill_id, status='PAYING') 建唯一索引 兜底防线,即使并发绕过分布式锁,数据库也会拒绝重复插入

11.3 账单并发冲突(P3)

问题根因:同一房产可能有多个缴费人(如房东和租户),两人同时查询到同一账单为"待缴"状态并同时发起缴费,如果不做并发控制,可能导致同一账单被缴纳两次。

解决方案:乐观锁 + 状态机校验。

blog-mermaid-28-flowchart.png

落地实现

措施 实现 说明
乐观锁 账单表增加 version 字段,更新时 WHERE version = ? 无锁并发,性能优于悲观锁,适合"读多写少"场景
状态机校验 更新时 WHERE status = 'UNPAID' 只允许从"待缴"流转到"支付中",已锁定账单拒绝再次缴费
Seata 全局锁 AT 模式自动获取行级全局锁 在全局事务未提交期间,其他全局事务不能修改同一账单行
前端提示 缴费失败返回明确的业务提示 “该账单正在缴费中,请刷新后重试”,引导用户重新查询

账单状态流转规则

当前状态 允许流转 触发动作
UNPAID(待缴) → PAYING(支付中) 用户发起缴费
PAYING(支付中) → PAID(已缴) 支付成功回调
PAYING(支付中) → UNPAID(待缴) 支付超时/取消,订单关闭
PAID(已缴) → REFUNDING(退费中) 退费申请审批通过
REFUNDING(退费中) → UNPAID(待缴) 退费完成
REFUNDING(退费中) → PAID(已缴) 退费失败回滚

11.4 支付超时与订单关闭(P4)

问题根因:用户调起支付后长时间未完成操作(如打开微信支付页面后离开),订单一直停留在"支付中"状态,导致账单被锁定,其他缴费人无法缴纳。

解决方案:订单超时自动关闭 + 定时任务兜底清理。

blog-mermaid-29-flowchart.png

落地实现

方案 实现 说明
Redis 延迟队列 创建订单时写入 delay:order:close:{orderId},TTL 15 分钟 过期后触发监听器执行订单关闭逻辑,实时性好
定时任务兜底 每 5 分钟扫描 status=PAYING AND create_time < NOW()-15min 的订单 防止 Redis 延迟消息丢失,作为兜底
订单关闭逻辑 更新订单状态为 CLOSED,恢复账单状态为 UNPAID,释放 Redis 锁 关闭前需查询第三方支付平台确认未支付,防止"关单后用户完成支付"
查询第三方确认 调用微信/支付宝订单查询接口确认支付状态 如果第三方返回"已支付"则不关单,走支付成功流程
用户主动取消 客户端提供"取消支付"按钮 主动取消同样走关单逻辑

"关单后支付"异常处理:极端情况下,订单关闭与第三方支付回调存在时间窗口冲突——订单刚关闭,用户在第三方完成了支付。处理策略:

  1. 关单前查询第三方支付状态,确认未支付才执行关单。
  2. 如果关单后仍收到支付成功回调,执行"补单"流程——恢复订单状态为 PAID,回写账单状态为已缴,发送缴费成功通知。补单通过幂等设计保证安全。

11.5 支付回调可靠性(P6)

问题根因:第三方支付平台(微信/支付宝)通过 HTTP 回调通知支付结果,但回调可能因网络抖动而丢失、延迟或重复推送。如果回调处理不当,会导致"用户已付款但系统未入账"或"重复入账"。

解决方案:回调幂等 + 主动查询补偿 + 回调重试确认。

blog-mermaid-30-sequenceDiagram.png

落地实现

措施 实现 说明
回调幂等 Redis SETNX callback:{out_trade_no} NX EX 24h 首次回调处理,重复回调直接返回 SUCCESS
回调验签 验证第三方签名,防止伪造回调 微信使用 V3 API 的 RSA 签名验证,支付宝使用 RSA2
快速响应 收到回调后立即回复 SUCCESS,支付结果异步处理 避免回调处理超时导致第三方重复推送
主动查询补偿 定时任务每 2 分钟扫描"支付中超 5 分钟未回调"的订单 调用第三方查询接口确认支付状态,防止回调丢失
回调重试确认 第三方回调失败后会按 15s/15s/30s/1m/…/6h 重试 只要回复 SUCCESS 就停止重试,因此即使首次处理失败,后续重试仍有机会

11.6 事务消息回查容错(P7)

问题根因:收费服务通过 RocketMQ 事务消息发送 payment-success 事件,但发送半消息后如果服务宕机(本地事务执行中断、Commit/Rollback 未送达),Broker 需要回查本地事务状态。如果回查接口本身也不可用,消息状态将不确定。

解决方案:回查接口高可用部署 + 本地事务状态可追溯 + 超时兜底。

blog-mermaid-31-flowchart.png

落地实现

措施 实现 说明
回查接口实现 实现 TransactionListener.checkLocalTransaction 方法 查询收费订单表状态判断事务是否提交
回查接口高可用 收费服务多副本部署,任意实例可响应回查 Nacos 自动路由至健康实例
回查超时设置 Broker 回查超时 60s,最多 15 次 超过后 Broker 自动 Rollback 半消息
对账兜底 每小时扫描"订单状态=PAID 但 payment-success 消息未确认"的记录 重新通过普通消息发送,补偿事务消息 Rollback 的情况
本地事务状态表 新增 transaction_log 表记录事务执行状态 STARTED/COMMITTED/ROLLED_BACK,回查时查询此表而非业务表,避免业务表状态歧义

11.7 消息消费失败处理(P8/P9)

问题根因payment-success 消息的三个消费者(发票服务、通知服务、数据分析服务)可能因各自原因消费失败:发票服务对接的税务系统故障、通知服务的短信网关不可用、数据分析服务的 ETL 任务异常。

解决方案:消费失败自动重试 + 死信队列兜底 + 对账任务补偿。

blog-mermaid-32-flowchart.png

落地实现

场景 消费失败原因 重试策略 死信兜底 对账补偿
发票开具 税务系统接口故障 16 级梯度重试,约 4.5 小时 死信队列存储消息,运维通过控制台查看后手动重发 每日 03:00 开票对账:已缴费未开票的订单重新触发开票消息
缴费通知 短信网关不可用 同上 同上 通知补偿:每日扫描已缴费未通知的订单,补发通知
数据同步 ETL 任务异常 同上 同上 CDC 位点对账:检查数据仓库与业务库的差异,补偿同步

死信队列管理看板:平台开发了独立的死信管理看板,展示:

  • 各消费组死信积压数量和趋势
  • 死信消息内容和失败原因分类
  • 一键重发功能(修复底层问题后批量重发)
  • 死信超 10 条 P1 告警、超 50 条 P0 告警

11.8 缴费高峰期高并发架构(P1/P10)

问题根因:供热季首月日均缴费量可达平时的 10-20 倍,瞬时 QPS 从平时的 50 飙升至 1000+。如果不做容量规划和流量治理,单服务过载会通过调用链传导至依赖服务,引发全局雪崩。

整体方案:全链路压测定容量 → 分层限流控入口 → 弹性伸缩扩容量 → 降级保核心 → 削峰填谷平滑流量。

blog-mermaid-33-flowchart.png

11.8.1 全链路压测与容量规划

上线前通过全链路压测确定各服务的容量上限,制定扩容基线:

服务 平时 QPS 高峰 QPS(预估) 单实例承载 平时副本 高峰副本 扩容触发
API 网关 50 2000 400 QPS 2 8 CPU > 70%
收费服务 30 800 150 QPS 2 10 CPU > 60%
支付服务 30 500 100 QPS 2 8 CPU > 60%
计费账单服务 20 300 100 QPS 2 6 CPU > 65%
通知服务 20 600 200 QPS 2 6 消息积压 > 500
发票服务 10 200 80 QPS 2 4 CPU > 70%

压测方式:使用 JMeter 模拟用户缴费全流程(查询账单→创建订单→模拟支付回调→验证通知),逐步加压至预估峰值的 1.5 倍,观察各服务的 CPU、内存、RT、错误率,找到瓶颈点。

预热扩容:供热季开始前一周,通过 Nacos 配置将收费服务和支付服务的最小副本数从 2 调整为 5,提前扩容避免高峰期冷启动延迟。

11.8.2 分层限流策略

限流层 限流对象 阈值 流控行为 说明
CDN/负载均衡 总入口 3000 QPS 拒绝超出请求 第一道防线,保护全局
API 网关 全局 2000 QPS 快速失败返回 429 第二道防线
API 网关 单 IP 100 QPS 快速失败 防恶意刷接口
Sentinel 单用户 10 QPS 排队等待 3s 防脚本攻击
Sentinel 支付接口 500 QPS 排队等待 3s 保护第三方支付接口
数据库 连接池 每实例 50 连接 排队等待 HikariCP 连接池控制

限流后的用户体验:被限流的请求返回 HTTP 429 + 友好提示"当前缴费人数较多,请稍后重试"。前端收到 429 后自动延迟 3s 重试,最多重试 3 次。如果持续被限流,引导用户切换至线下缴费渠道(银行代扣、营业厅)。

11.8.3 弹性伸缩

K8s HPA 配置基于 CPU 和自定义指标的自动伸缩:

# 收费服务 HPA 配置示例
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: charge-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: charge-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "150"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Percent
        value: 100
        periodSeconds: 30
    scaleDown:
      stabilizationWindowSeconds: 300

伸缩策略要点

配置 说明
扩容触发 CPU > 60% 或 QPS > 150/实例 双指标触发,任一达标即扩容
扩容速度 每 30s 最多扩容 100% 快速扩容应对突发流量
缩容窗口 5 分钟稳定期 避免流量波动导致频繁伸缩
最小副本 2(高峰季调整为 5) 保证基础可用性
最大副本 10 防止过度扩容打垮下游依赖
预热扩容 供热季前手动调整 minReplicas 避免冷启动延迟

11.8.4 降级策略

高峰期对非核心服务执行降级,保障缴费主链路:

降级服务 降级动作 触发条件 恢复条件
发票服务 跳过实时开票,消息存入队列延迟处理 缴费 QPS > 500 QPS 回落后批量补开
通知服务 跳过实时短信,改为批量合并通知(每 5 分钟一批) 通知消息积压 > 1000 积压降至 200 以下
报表查询 拒绝报表查询请求,返回"维护中"提示 缴费 QPS > 800 QPS 降至 300 以下
设备状态查询 返回 Redis 缓存的设备状态(不查 InfluxDB) IoT 查询 RT > 2s RT 降至 500ms 以下
历史账单查询 只返回近 2 季账单(不查归档库) 数据库连接数 > 80% 连接数降至 60% 以下

降级原则

  • 降级只影响非核心功能,缴费主流程(创建订单→支付→入账)不降级。
  • 降级动作通过 Sentinel 规则自动触发,规则存储在 Nacos 中动态推送。
  • 降级期间向用户明确提示(如"发票将在 24 小时内开具"),管理预期。

11.8.5 数据库高并发优化

优化措施 实现方式 效果
读写分离 主库写、从库读(账单查询走从库) 读压力分散至从库,主库专注写入
连接池优化 HikariCP maximumPoolSize=50connectionTimeout=3s 控制连接数,防止数据库被打满
SQL 优化 账单查询走索引(idx_user_season),避免全表扫描 查询 RT 从 200ms 降至 10ms
批量操作 账单生成采用批量 INSERT(每批 500 条) 减少 SQL 执行次数,提升吞吐量
热点数据缓存 用户信息、房产信息、计费规则缓存至 Redis 减少 80% 的数据库读请求
慢查询监控 MySQL long_query_time=1s,慢查询告警 及时发现和优化慢 SQL

11.8.6 削峰填谷

利用 RocketMQ 消息队列平滑峰值流量:

blog-mermaid-34-flowchart.png

削峰效果:缴费高峰期收费服务以 1000 QPS 处理核心入账逻辑(同步),下游的发票开具、通知发送、数据同步通过 MQ 异步处理,消费者按自身能力匀速消费(200/500/100 QPS),避免瞬时 2000 QPS 直接打垮下游服务。MQ 积压在可接受范围内(通知延迟 < 1 分钟),高峰过后积压逐步消化。

11.9 第三方支付渠道不可用容灾(P5)

问题根因:微信、支付宝等第三方支付渠道可能因系统故障、维护或网络问题不可用,导致用户无法完成在线支付。

解决方案:多渠道兜底 + 降级引导 + 渠道健康检查。

blog-mermaid-35-flowchart.png

落地实现

措施 实现 说明
渠道健康检查 支付服务每 30s 调用第三方健康检查接口 检测渠道可用性,不可用时标记并从推荐列表移除
多渠道配置 支付服务维护支付渠道列表和优先级 微信(默认)→ 支付宝 → 银联 → 银行代扣
渠道自动切换 主渠道调用超时 5s 后自动切换至备用渠道 用户无感知,切换过程对用户透明
线下渠道引导 在线渠道全部不可用时引导用户线下缴费 展示营业厅地址、银行代扣入口
渠道故障告警 渠道不可用时 P1 告警通知运维 运维确认是否需要联系第三方

11.10 缴费高峰期保障预案

预案级别 触发条件 执行动作 响应时间
P0 - 紧急 缴费服务完全不可用 全量切换至备用机房;启动应急缴费通道(仅记录不实时入账,恢复后批量入账) 15 分钟
P1 - 严重 缴费成功率 < 90% 排查瓶颈服务,紧急扩容;降级非核心服务;通知用户延迟缴费 5 分钟
P2 - 警告 QPS 超过容量 80% 自动扩容至最大副本;开启降级策略;监控关键指标 自动触发
P3 - 预警 QPS 超过容量 60% HPA 自动扩容;发送预警通知 自动触发

应急缴费通道:极端情况下(如机房级故障),启动应急通道——用户在小程序提交缴费申请,系统仅记录缴费意向(不调用支付和入账),恢复后后台批量处理。此方案牺牲实时性,但保证用户"已经提交缴费"的业务凭证,避免因系统故障导致用户产生滞纳金。

11.11 缴费链路全链路监控

监控节点 监控指标 告警阈值 告警级别
API 网关 请求 QPS、429 比例 429 比例 > 5% P2
收费服务 创建订单 QPS、RT P99、错误率 RT P99 > 2s 或错误率 > 1% P1
支付服务 支付调起成功率、回调到达率 成功率 < 95% 或回调到达率 < 98% P1
计费账单服务 账单回写 RT、Seata 事务超时率 超时率 > 5% P1
RocketMQ 消息积压量、消费延迟 积压 > 1000 或延迟 > 60s P2
MySQL 连接数使用率、慢查询数 连接数 > 80% 或慢查询 > 10/min P1
Redis 命中率、连接数 命中率 < 90% 或连接数 > 80% P2

全链路看板:Grafana 缴费实时看板展示从请求入口到消息消费的完整链路状态,任一节点标红即表示该节点异常,运维可快速定位故障点。SkyWalking 链路追踪支持按 TraceID 查询单笔缴费的完整调用链,用于问题排查。

12. 分布式事务管理

微服务架构下,每个服务拥有独立数据库,一次业务操作往往跨越多个服务和数据源。传统的本地事务(ACID)无法直接覆盖跨服务场景,分布式事务成为平台必须正面解决的核心工程问题。本章先梳理平台各业务场景的事务流转特征与潜在问题,再给出方案选型、落地实现和兜底保障。

12.1 跨服务事务流转全景

平台六大业务场景中,有四个场景涉及跨服务数据写入,需要分布式事务保障一致性。下图展示各场景的事务流转路径与参与方:

blog-mermaid-36-flowchart.png

12.2 分布式事务面临的核心问题

跨服务事务流转中,以下七类问题在供热收费平台的实际运行中真实存在,必须逐一覆盖:

12.2.1 跨服务数据不一致

问题描述:缴费场景中,收费服务创建缴费订单(charge_db)和计费账单服务回写账单状态(billing_bill_db)分属两个数据库。如果收费订单创建成功但账单回写失败,用户已付款但账单仍显示"待缴",可能导致重复缴费。反之,账单已标记"已缴"但收费订单未创建,则缺少缴费凭证。

影响范围:直接导致资金账务与业务账单脱节,引发用户投诉和财务对账困难。

12.2.2 网络分区导致的事务悬挂

问题描述:Seata AT 模式下,TC(事务协调者)通知分支提交/回滚时,如果网络分区导致通知无法到达某个分支服务,该分支的 undo_log 记录将长期悬挂——本地事务已执行但全局事务状态不确定。后续如果 TC 恢复并触发回滚,分支根据 undo_log 执行反向 SQL,但如果期间数据已被其他事务修改,回滚可能失败或产生脏数据。

影响范围undo_log 积压影响数据库性能,数据状态不确定影响业务正确性。

12.2.3 事务超时与长事务

问题描述:Seata AT 模式默认全局事务超时 60s。在缴费高峰期,如果计费账单服务因数据库压力增大导致账单回写响应变慢,全局事务可能超时。超时后 TC 自动回滚全局事务,但此时收费订单的本地事务可能已经提交——用户已付款,但订单被回滚,资金丢失。

影响范围:高峰期高频出现,直接导致资金损失。

12.2.4 消息与本地事务的原子性

问题描述:缴费成功后,收费服务需要同时完成两个操作——更新收费记录状态为"已缴费"(本地事务)和发送 payment-success 消息(通知下游开票和通知)。如果先提交本地事务再发消息,消息发送失败则下游收不到通知;如果先发消息再提交本地事务,本地事务失败则下游收到错误通知。两者无法保证同时成功。

影响范围:下游服务状态与主业务不一致,发票漏开或通知漏发。

12.2.5 并发事务的全局锁冲突

问题描述:Seata AT 模式通过全局锁保证分支事务间的写隔离。当多个全局事务同时修改同一账单行时,后到达的事务需等待前一个事务释放全局锁。缴费高峰期,大量用户同时缴费可能导致全局锁等待超时,事务频繁回滚重试,吞吐量急剧下降。

影响范围:高峰期缴费成功率下降,用户体验恶化。

12.2.6 TCC 空回滚与悬挂

问题描述:TCC 模式下,Try 阶段因网络超时未执行,TC 误判 Try 失败触发 Cancel,但延迟的 Try 请求随后到达并执行——此时 Cancel 已执行但 Try 又预留了资源,资源永久悬挂无人释放。反过来,如果 Cancel 先于 Try 到达,Cancel 执行了补偿但 Try 随后执行了资源预留,预留的资源同样无人释放。

影响范围:退费场景中收费记录状态错乱,"退费中"状态永久卡住。

12.2.7 Saga 流程中断后的恢复

问题描述:开户 Saga 流程涉及用户服务、房产服务、合同服务三步。如果第二步(房产绑定)执行成功后第三步(合同生成)失败,Saga 需要逆序执行补偿(解绑房产→注销用户)。但如果补偿过程中服务再次宕机,Saga 流程中断在中间状态——用户档案已创建但房产绑定未解绑,数据处于半成品状态。

影响范围:开户失败但残留脏数据,影响后续重新开户。

12.3 分布式事务方案选型矩阵

平台根据各业务场景的事务特征(一致性要求、事务时长、参与方数量、是否涉及外部系统),选择差异化的分布式事务方案:

业务场景 一致性要求 事务时长 参与方 外部系统 选型方案 选型理由
缴费入账 强一致性 < 3s 2 个服务 Seata AT 短事务、同机房、需严格一致
退费处理 最终一致性 10-60s 3 个服务 是(退款接口) TCC + 事务消息 需精细化补偿、涉及外部退款
开户流程 最终一致性 5-30s 3 个服务 Saga 编排式 长流程多步骤、每步可补偿
停复供流程 最终一致性 5-15s 3 个服务 否(IoT 设备) Saga 编排式 含物理设备操作、允许延迟
缴费后异步操作 最终一致性 异步 3 个消费者 是(税务/短信) 事务消息 允许延迟、不可丢失
数据同步 最终一致性 异步 1 个消费者 本地消息表 允许分钟级延迟、对账兜底

blog-mermaid-37-flowchart.png

12.4 Seata AT 模式落地实现

12.4.1 适用场景与架构

Seata AT 模式用于缴费入账场景——收费服务创建缴费订单并调用计费账单服务回写账单状态,两步操作必须原子性完成。AT 模式对业务代码侵入性最小,只需添加 @GlobalTransactional 注解。

flowchart TB
    subgraph TM 事务管理器
        TM1[收费服务<br/>@GlobalTransactional]
    end
    subgraph TC 事务协调者
        TC1[Seata Server<br/>3节点集群]
    end
    subgraph RM 资源管理器
        RM1[收费服务<br/>charge_db<br/>undo_log表]
        RM2[计费账单服务<br/>billing_bill_db<br/>undo_log表]
    end
    subgraph 存储
        DB[(Seata MySQL<br/>global_table<br/>branch_table<br/>lock_table)]
    end

    TM1 -->|1.开启全局事务获取XID| TC1
    TM1 -->|2.分支注册| TC1
    TC1 --> DB
    TM1 -->|3.执行本地事务| RM1
    RM1 -->|记录undo_log| RM1
    TM1 -->|4.RPC传递XID| RM2
    RM2 -->|5.执行本地事务| RM2
    RM2 -->|记录undo_log| RM2
    RM2 -->|6.分支注册| TC1
    TM1 -->|7.全局提交/回滚| TC1
    TC1 -->|8.通知分支| RM1 & RM2

12.4.2 缴费入账 AT 模式完整流程

blog-mermaid-39-sequenceDiagram.png

支付回调后的入账流程:支付成功回调到达后,收费服务在 Seata 全局事务内执行:分支1 更新缴费订单状态为"已缴费"(charge_db),分支2 更新账单状态为"已缴"(billing_bill_db)。全局事务提交后,再通过 RocketMQ 事务消息发送 payment-success 事件触发下游异步操作。

12.4.3 undo_log 表设计与回滚机制

每个参与 AT 模式的数据库中需创建 undo_log 表:

CREATE TABLE `undo_log` (
    `branch_id`     BIGINT       NOT NULL COMMENT '分支事务ID',
    `xid`           VARCHAR(128) NOT NULL COMMENT '全局事务ID',
    `context`       VARCHAR(128) NOT NULL COMMENT '上下文',
    `rollback_info` LONGBLOB     NOT NULL COMMENT '回滚信息(before/after image)',
    `log_status`    INT          NOT NULL COMMENT '状态:0-正常 1-全局已完成',
    `log_created`   DATETIME     NOT NULL COMMENT '创建时间',
    `log_modified`  DATETIME     NOT NULL COMMENT '修改时间',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB COMMENT='AT模式undo_log表';

回滚机制:当全局事务回滚时,TC 通知各分支执行回滚。分支从 undo_log 中读取 before_image(变更前数据快照),生成反向 SQL(INSERT→DELETE、UPDATE→反向 UPDATE、DELETE→INSERT)执行回滚。回滚完成后删除 undo_log 记录。

before_image / after_image 示例(账单状态回写场景):

{
  "before_image": {
    "table": "bill",
    "id": 123,
    "status": "UNPAID",
    "version": 1
  },
  "after_image": {
    "table": "bill",
    "id": 123,
    "status": "PAYING",
    "version": 2
  },
  "rollback_sql": "UPDATE bill SET status='UNPAID', version=1 WHERE id=123 AND version=2"
}

12.4.4 全局锁机制与写隔离

AT 模式通过全局锁实现分支事务间的写隔离,保证在全局事务未完成期间,其他全局事务不能修改同一行数据:

阶段 全局锁操作 说明
分支提交前 lock_table 插入 table_name + pk 获取全局锁,如果已被其他全局事务锁定则等待
分支提交后 本地事务已提交,全局锁保留 其他全局事务可读(读已提交),但不可写
全局事务提交 异步删除 lock_table 记录 释放全局锁
全局事务回滚 回滚前重新获取全局锁 防止回滚期间数据被其他事务修改

写隔离效果:用户 A 缴费时,账单行被全局锁锁定。用户 B 尝试缴费同一账单时,分支2(账单回写)需获取全局锁,发现已被用户 A 的事务锁定,等待超时后全局事务回滚,用户 B 收到"账单正在缴费中"提示。

12.4.5 超时与异常处理

异常场景 处理方式 配置
全局事务超时 TC 自动回滚全局事务,通知所有分支回滚 timeout=30000(30s,高峰期可调至 60s)
分支本地事务失败 分支上报失败状态,TM 发起全局回滚 自动触发
TC 通知分支提交/回滚失败 TC 重试通知,重试间隔 5s,最多 10 次 retry=10, interval=5000
undo_log 回滚失败 分支上报回滚失败,TC 标记全局事务为"回滚失败",人工介入 告警通知运维
undo_log 悬挂清理 定时任务每小时清理超过 24h 的 log_status=1 的 undo_log 防止 undo_log 积压

超时调优:默认全局事务超时 60s,但缴费场景的正常耗时不超过 3s。平台将超时调整为 30s,留出充足的异常重试余量,同时避免长事务占用全局锁。高峰期如果出现超时频繁,临时调整至 60s 并排查慢 SQL。

12.5 TCC 模式落地实现

12.5.1 适用场景与设计

TCC 模式用于退费场景——收费记录需要从"已缴费"流转到"退费中"再到"已退费",涉及外部退款接口调用,需要精细化控制补偿逻辑。AT 模式只能按 undo_log 快照回滚,无法表达"退费中→已缴费"这种业务语义的补偿。

退费 TCC 三阶段设计

阶段 方法 收费服务动作 说明
Try prepareRefund 更新收费记录状态为"退费中",记录冻结时间和冻结金额 预留资源,锁定记录
Confirm confirmRefund 更新收费记录状态为"已退费",记录退费完成时间 确认退费完成
Cancel cancelRefund 回滚收费记录状态为"已缴费",清除冻结标记 补偿操作,恢复原始状态

12.5.2 TCC 实现

// 退费TCC接口定义
@LocalTCC
public interface RefundTccAction {

    @TwoPhaseBusinessAction(name = "refundTccAction",
        commitMethod = "confirmRefund",
        rollbackMethod = "cancelRefund")
    boolean prepareRefund(BusinessActionContext ctx,
                          @BusinessActionContextParameter(paramName = "chargeId") Long chargeId,
                          @BusinessActionContextParameter(paramName = "refundAmount") BigDecimal refundAmount);

    boolean confirmRefund(BusinessActionContext ctx);

    boolean cancelRefund(BusinessActionContext ctx);
}

// Try 阶段实现
@Transactional
public boolean prepareRefund(BusinessActionContext ctx, Long chargeId, BigDecimal refundAmount) {
    // 幂等检查:防止悬挂(Cancel先于Try到达)
    RefundRecord existing = refundRecordMapper.findByXid(ctx.getXid());
    if (existing != null) {
        return true; // 已执行过Try
    }

    ChargeRecord record = chargeMapper.selectById(chargeId);
    if (record.getStatus() != ChargeStatus.PAID) {
        throw new RuntimeException("收费记录状态不允许退费");
    }

    // 状态流转:PAID → REFUNDING
    int affected = chargeMapper.updateStatusWithVersion(
        chargeId, ChargeStatus.REFUNDING, record.getVersion());
    if (affected == 0) {
        throw new RuntimeException("状态更新失败,可能被并发修改");
    }

    // 记录TCC事务状态(防悬挂+幂等)
    refundRecordMapper.insert(new RefundRecord(
        ctx.getXid(), chargeId, refundAmount, TccPhase.TRY));
    return true;
}

// Confirm 阶段实现
@Transactional
public boolean confirmRefund(BusinessActionContext ctx) {
    Long chargeId = (Long) ctx.getActionContext("chargeId");
    RefundRecord record = refundRecordMapper.findByXid(ctx.getXid());

    // 幂等检查
    if (record.getPhase() == TccPhase.CONFIRM) {
        return true; // 已确认
    }

    // 状态流转:REFUNDING → REFUNDED
    chargeMapper.updateStatus(chargeId, ChargeStatus.REFUNDED);
    refundRecordMapper.updatePhase(ctx.getXid(), TccPhase.CONFIRM);
    return true;
}

// Cancel 阶段实现
@Transactional
public boolean cancelRefund(BusinessActionContext ctx) {
    Long chargeId = (Long) ctx.getActionContext("chargeId");
    RefundRecord record = refundRecordMapper.findByXid(ctx.getXid());

    // 空回滚检查:Try未执行但Cancel被调用
    if (record == null) {
        // 记录空回滚标记,防止后续Try到达时悬挂
        refundRecordMapper.insert(new RefundRecord(
            ctx.getXid(), chargeId, BigDecimal.ZERO, TccPhase.CANCEL));
        return true;
    }

    // 幂等检查
    if (record.getPhase() == TccPhase.CANCEL) {
        return true; // 已取消
    }

    // 状态回滚:REFUNDING → PAID
    chargeMapper.updateStatus(chargeId, ChargeStatus.PAID);
    refundRecordMapper.updatePhase(ctx.getXid(), TccPhase.CANCEL);
    return true;
}

12.5.3 空回滚与悬挂防护

异常场景 触发条件 防护机制 实现方式
空回滚 Try 超时后 TC 触发 Cancel,但 Try 从未执行 Cancel 检查 refund_record 是否存在 Try 记录 不存在则插入 Cancel 标记直接返回,不执行业务补偿
悬挂 Cancel 先执行,延迟的 Try 随后到达 Try 检查 refund_record 是否存在 Cancel 标记 存在 Cancel 标记则拒绝执行 Try
幂等 Confirm/Cancel 被重复调用 检查 refund_record.phase 字段 已是目标阶段则直接返回 true

blog-mermaid-40-flowchart.png

12.6 Saga 模式落地实现

12.6.1 适用场景与流程定义

Saga 模式用于开户流程和停复供流程——多步骤长流程,每步可定义补偿动作。平台采用 Seata Saga 状态机引擎,通过 JSON 定义流程编排。

开户 Saga 流程

blog-mermaid-41-flowchart.png

Saga JSON 定义

{
  "Name": "userOpenAccountSaga",
  "Comment": "用户开户Saga流程",
  "StartState": "createUser",
  "States": {
    "createUser": {
      "Type": "ServiceTask",
      "ServiceName": "userServiceImpl",
      "ServiceMethod": "createUser",
      "CompensateState": "cancelUser",
      "Next": "bindProperty"
    },
    "bindProperty": {
      "Type": "ServiceTask",
      "ServiceName": "propertyServiceImpl",
      "ServiceMethod": "bindUserProperty",
      "CompensateState": "unbindProperty",
      "Next": "generateContract"
    },
    "generateContract": {
      "Type": "ServiceTask",
      "ServiceName": "contractServiceImpl",
      "ServiceMethod": "generateContract",
      "CompensateState": "voidContract",
      "Next": "succeed"
    },
    "cancelUser": {
      "Type": "ServiceTask",
      "ServiceName": "userServiceImpl",
      "ServiceMethod": "cancelUser"
    },
    "unbindProperty": {
      "Type": "ServiceTask",
      "ServiceName": "propertyServiceImpl",
      "ServiceMethod": "unbindUserProperty"
    },
    "voidContract": {
      "Type": "ServiceTask",
      "ServiceName": "contractServiceImpl",
      "ServiceMethod": "voidContract"
    },
    "succeed": {
      "Type": "Succeed"
    }
  }
}

12.6.2 Saga 状态持久化与恢复

Saga 引擎将流程执行状态持久化到 seata_saga_log 表,确保服务宕机后可恢复:

CREATE TABLE `seata_saga_log` (
    `id`              BIGINT       NOT NULL AUTO_INCREMENT,
    `saga_id`         VARCHAR(128) NOT NULL COMMENT 'Saga实例ID',
    `state_name`      VARCHAR(128) NOT NULL COMMENT '当前状态名',
    `state_type`      VARCHAR(32)  NOT NULL COMMENT '状态类型:ServiceTask/Succeed',
    `business_key`    VARCHAR(128) COMMENT '业务键(如用户编号)',
    `status`          VARCHAR(32)  NOT NULL COMMENT '执行状态:RU-运行 SU-成功 FA-失败 CO-补偿',
    `input_params`    TEXT COMMENT '输入参数',
    `output_params`   TEXT COMMENT '输出参数',
    `exception`       TEXT COMMENT '异常信息',
    `created_at`      DATETIME     NOT NULL,
    `updated_at`      DATETIME     NOT NULL,
    PRIMARY KEY (`id`),
    INDEX `idx_saga_id` (`saga_id`),
    INDEX `idx_status` (`status`)
) ENGINE=InnoDB COMMENT='Saga状态日志表';

中断恢复机制

中断场景 恢复策略 实现
正向步骤执行中宕机 读取 seata_saga_log 最后状态,如为 RU 则重试当前步骤 Saga 引擎自动恢复
正向步骤执行失败 读取状态为 FA,触发逆序补偿 Saga 引擎自动触发
补偿步骤执行中宕机 读取补偿状态为 CO,重试当前补偿步骤 Saga 引擎自动恢复
补偿步骤执行失败 标记 Saga 为"补偿失败",人工介入 P1 告警,运维通过管理台手动处理

blog-mermaid-42-flowchart.png

12.6.3 停复供 Saga 流程

停复供场景的 Saga 流程涉及物理设备操作(阀门控制),补偿逻辑有特殊性——阀门控制失败不能回滚费用调整(用户已获批停供),而是转为人工处理:

步骤 正向动作 补偿动作 失败处理
1. 费用调整 计费账单服务按停供日期重新计算热费 恢复原计费方式 自动补偿
2. 阀门控制 IoT 控制服务远程关阀 远程开阀(恢复供热) 转人工工单,不影响费用调整
3. 通知用户 通知服务发送停供成功通知 无补偿(非核心) 延迟补偿发送

关键设计:费用调整在审批通过后立即生效并持久化,阀门控制作为独立步骤——即使阀门控制失败,费用已按停供计算,不会因设备故障导致用户多缴费用。阀门控制失败转人工处理后,Saga 标记为"部分完成",不触发费用调整的补偿。

12.7 可靠消息事务落地实现

12.7.1 RocketMQ 事务消息

事务消息用于缴费成功后的异步操作——确保"收费记录更新"(本地事务)与"payment-success 消息发送"的原子性。

blog-mermaid-43-sequenceDiagram.png

transaction_log 表设计

CREATE TABLE `transaction_log` (
    `xid`           VARCHAR(128) NOT NULL COMMENT '事务ID(与MQ msgId关联)',
    `business_key`  VARCHAR(128) NOT NULL COMMENT '业务键(如缴费订单号)',
    `status`        VARCHAR(32)  NOT NULL COMMENT 'STARTED-已开始 COMMITTED-已提交 ROLLED_BACK-已回滚',
    `created_at`    DATETIME     NOT NULL,
    `updated_at`    DATETIME     NOT NULL,
    PRIMARY KEY (`xid`),
    INDEX `idx_business_key` (`business_key`),
    INDEX `idx_status` (`status`)
) ENGINE=InnoDB COMMENT='事务消息本地状态表';

回查接口实现

public class PaymentTransactionListener implements TransactionListener {

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        String xid = msg.getTransactionId();
        String orderId = msg.getKeys();
        try {
            // 写入事务状态(STARTED)
            transactionLogMapper.insert(new TransactionLog(xid, orderId, "STARTED"));
            // 执行本地事务
            chargeService.confirmPayment(orderId);
            // 更新事务状态(COMMITTED)
            transactionLogMapper.updateStatus(xid, "COMMITTED");
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (Exception e) {
            transactionLogMapper.updateStatus(xid, "ROLLED_BACK");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        String xid = msg.getTransactionId();
        TransactionLog log = transactionLogMapper.findByXid(xid);
        if (log == null) {
            // 事务未开始,回滚
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        switch (log.getStatus()) {
            case "COMMITTED":
                return LocalTransactionState.COMMIT_MESSAGE;
            case "ROLLED_BACK":
                return LocalTransactionState.ROLLBACK_MESSAGE;
            default:
                // STARTED 状态:本地事务可能还在执行中,返回未知让Broker稍后再查
                return LocalTransactionState.UNKNOW;
        }
    }
}

12.7.2 本地消息表方案

本地消息表用于数据同步场景——将各业务服务的数据变更可靠地同步至数据仓库。与事务消息不同,本地消息表不需要 RocketMQ 事务消息支持,通过本地事务+定时任务实现:

blog-mermaid-44-flowchart.png

本地消息表设计

CREATE TABLE `local_message` (
    `id`             BIGINT       NOT NULL AUTO_INCREMENT,
    `message_id`     VARCHAR(64)  NOT NULL COMMENT '消息唯一ID',
    `topic`          VARCHAR(128) NOT NULL COMMENT 'MQ Topic',
    `tag`            VARCHAR(64)  COMMENT 'MQ Tag',
    `body`           TEXT         NOT NULL COMMENT '消息体(JSON)',
    `status`         VARCHAR(32)  NOT NULL COMMENT 'PENDING-待发送 SENT-已发送 FAILED-发送失败',
    `retry_count`    INT          DEFAULT 0 COMMENT '重试次数',
    `next_retry_at`  DATETIME     COMMENT '下次重试时间',
    `created_at`     DATETIME     NOT NULL,
    `updated_at`     DATETIME     NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_message_id` (`message_id`),
    INDEX `idx_status_retry` (`status`, `next_retry_at`)
) ENGINE=InnoDB COMMENT='本地消息表';

工作流程

步骤 动作 说明
1 业务操作 + 插入消息记录 在同一本地事务中执行,保证原子性
2 定时任务扫描 status=PENDING 的消息 每 5 秒扫描一次
3 投递消息至 RocketMQ 发送成功后更新 status=SENT
4 发送失败更新 retry_countnext_retry_at 指数退避:1s/5s/30s/2m/10m/30m
5 retry_count > 10 标记为 FAILED 触发告警,人工介入

12.8 对账兜底体系

无论采用哪种分布式事务方案,平台均部署全链路对账任务作为最终兜底,确保数据一致性:

blog-mermaid-45-flowchart.png

对账任务 执行频率 对账逻辑 自动修复规则 告警阈值
缴费对账 每日 02:00 收费记录 status=PAID 与账单 status=PAID 比对 收费已缴但账单未更新→回写账单;账单已缴但收费记录不存在→生成异常报告 差异数 > 10
退费对账 每日 02:30 退费记录 status=REFUNDED 与支付退款流水比对 退款成功但收费记录未更新→更新收费记录;退款失败但收费记录已标记→触发告警 差异数 > 5
开票对账 每日 03:00 缴费记录 status=PAID 与发票记录比对 已缴费未开票→重新发送开票消息;已开票但缴费记录已退费→触发发票冲红 差异数 > 20
消息补偿 每小时 transaction_logstatus=COMMITTED 但消息未确认的记录 重新通过普通消息发送 差异数 > 5
Saga 状态 每小时 seata_saga_logstatus=RU 超过 24h 的记录 触发补偿流程;补偿失败标记人工介入 差异数 > 0
undo_log 清理 每小时 undo_loglog_status=1 超过 24h 的记录 直接删除(全局事务已完成) 清理失败数 > 100

12.9 分布式事务监控

监控维度 指标 告警阈值 告警级别
Seata 全局事务 事务成功率、平均耗时、超时率 成功率 < 99% 或超时率 > 5% P1
Seata 分支事务 分支回滚率、undo_log 积压量 回滚率 > 2% 或积压 > 500 P1
TCC 事务 Try/Confirm/Cancel 成功率、空回滚次数 Cancel 率 > 10% 或空回滚 > 5/h P2
Saga 流程 流程成功率、平均耗时、补偿触发率 成功率 < 95% 或补偿率 > 5% P1
事务消息 半消息超时回查率、消息投递延迟 回查率 > 5% 或延迟 > 60s P2
本地消息表 PENDING 消息积压量、FAILED 消息数 积压 > 100 或 FAILED > 5 P2
对账任务 差异记录数、对账任务执行耗时 差异 > 10 或耗时 > 30min P1

Seata 事务大盘:Grafana 看板展示全局事务 QPS、成功率、平均耗时、超时率、分支回滚率趋势。按服务维度下钻,可查看各服务的分支事务状态分布。异常事务列表展示 XID、参与服务、失败原因,支持一键跳转 SkyWalking 查看 TraceID 对应的完整调用链。

12.10 分布式事务设计规范

规范项 要求
方案选型 强一致性场景用 AT,需精细化补偿用 TCC,长流程多步骤用 Saga,异步通知用事务消息/本地消息表
事务范围 AT/TCC 事务必须控制在 30s 以内,超过 30s 改用 Saga 或消息方案
幂等设计 所有 Confirm/Cancel/消费端必须实现幂等,幂等键在接口文档中明确定义
补偿设计 Saga 每步必须定义补偿动作,补偿动作必须幂等且可重复执行
外部系统 涉及外部系统(支付/税务)的操作不纳入 AT 事务,使用 TCC 或消息方案
全局锁 AT 模式避免在热点行上长时间持有全局锁,热点数据用 TCC 替代
超时设置 全局事务超时 30s(可调),分支事务超时 10s,事务消息回查超时 60s
日志记录 所有事务操作记录事务日志(XID、参与方、状态、时间),支撑问题排查
对账兜底 所有分布式事务必须有对账任务兜底,对账频率不低于每日一次
降级策略 事务中间件(Seata/RocketMQ)不可用时,降级为本地事务+消息补偿模式
0
博主关闭了所有页面的评论