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

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 个基础支撑服务。业务服务按供热经营领域划分为用户域、计费收费域、运营管控域、分析域四个限界上下文。

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,用户线上签字或线下签署,合同文件存储至文件服务 |

编排要点:用户服务作为开户流程的编排者,同步调用房产服务验证房产有效性并建立绑定关系,调用合同服务生成合同。合同文件生成通过文件服务异步处理,不阻塞开户主流程。用户-房产绑定关系由房产服务管理,绑定操作与用户档案创建通过 Saga 模式协调——任一步骤失败则整体回滚。
4.2 热费缴纳
涉及服务:计费账单服务、收费服务、支付服务、发票服务、通知服务
用户通过小程序或 APP 查询应缴账单,选择缴费方式和支付渠道完成在线支付。支付成功后,收费服务回写账单状态(调用计费账单服务),触发发票服务自动开具电子发票,通知服务向用户推送缴费成功消息和发票链接。
| 功能 | 说明 |
|---|---|
| 账单查询 | 按用户编号和供热季查询待缴账单,展示基本热费、计量热费、滞纳金等明细 |
| 在线支付 | 支持微信、支付宝、银联等渠道,支付服务对接第三方支付平台完成扣款 |
| 发票开具 | 支付成功后自动开具增值税电子普通发票,用户可在线查看和下载 |

一致性保障:缴费链路涉及收费服务、支付服务、计费账单服务三个数据源,采用 Seata AT 模式保证缴费订单创建与账单状态回写的分布式事务一致性。支付回调采用幂等设计(基于订单号去重),防止重复回调导致重复入账。发票开具为异步操作,通过消息队列解耦,开票失败不影响缴费主流程,由对账任务补偿。
4.3 停复供申请
涉及服务:审批服务、计费账单服务、IoT 控制服务、通知服务
用户因房屋空置、装修等原因申请停止或恢复供热。系统根据申请类型发起审批流程,审批通过后,计费账单服务调整费用计算方式(停供期间按基本热费收取),IoT 控制服务远程控制入户阀门关闭或开启,全程通过通知服务向用户和运维人员推送节点状态。
| 功能 | 说明 |
|---|---|
| 申请审批 | 用户提交停供/复供申请,系统根据业务规则自动流转至对应审批人(片区管理员→计量管理员) |
| 费用调整 | 审批通过后,计费账单服务按停供日期重新计算热费,停供期间仅收取基本热费(通常为全额的 30%-50%) |
| 阀门控制 | IoT 控制服务通过 IoT 平台远程控制入户阀门开关,记录操作日志,支持控制失败自动重试和人工介入 |

异常处理:阀门控制是物理设备操作,可能因网络中断、设备离线等原因失败。设计三级容错策略:一级自动重试(3 次,间隔递增),二级转人工处理(生成运维工单),三级告警通知(IoT 控制服务向运维人员推送告警)。费用调整在审批通过即刻生效,不依赖阀门控制结果——即使阀门控制失败,费用已按停供计算,避免用户因设备故障多缴费用。
4.4 退费处理
涉及服务:收费服务、审批服务、支付服务、通知服务
用户因多缴、停供退费、计费错误等原因申请退费。收费服务受理退费申请并冻结原收费记录,审批服务按金额分级审批(500 元以下片区审批,500 元以上需财务审批),审批通过后支付服务执行原路退款,收费服务更新账单和发票状态。
| 功能 | 说明 |
|---|---|
| 退费申请 | 用户或营业员发起退费申请,关联原缴费记录,填写退费原因和退费金额 |
| 审核审批 | 按退费金额分级审批,系统校验退费金额不超过原缴金额,审批通过后冻结原收费记录 |
| 退款处理 | 支付服务按原支付渠道执行退款(微信/支付宝原路退回,现金退费生成退费单据),退款成功后更新账单和发票 |

资金安全:退费流程涉及资金退还,采用先冻结后退款策略——退费申请提交即冻结原收费记录,防止在此期间再次发起退费。退款操作通过支付服务的退款接口执行,原路退回至用户原支付账户。退款金额超过原缴金额时系统拒绝,退款与发票冲红通过消息队列异步联动,保证财务数据一致性。
4.5 报修投诉
涉及服务:客服服务、通知服务、设备档案服务
用户通过热线电话、小程序或 APP 提交报修或投诉工单。客服服务创建工单并自动分类派单,通知服务向维修人员推送派单信息。维修人员现场处理完毕后回填处理结果,系统触发回访评价通知。如涉及设备故障,工单关联设备档案服务记录维修日志。
| 功能 | 说明 |
|---|---|
| 工单受理 | 多渠道受理(电话/小程序/APP/现场),自动分类(报修/投诉/咨询),关联用户和房产信息 |
| 派单通知 | 按片区和工单类型自动匹配维修人员,通过短信和 APP 推送派单通知,支持抢单和指派两种模式 |
| 维修处理 | 维修人员现场处理,填写处理结果和维修材料,上传现场照片,如涉及设备故障同步至设备档案服务 |

SLA 管理:工单按紧急程度设置不同的响应时效(紧急 2 小时、普通 24 小时、咨询 48 小时),超时未接单自动升级并通知主管。维修人员现场无法解决的工单可申请转单或技术支援,工单全程状态可追溯,形成完整的服务闭环。
4.6 报表统计
涉及服务:数据分析服务
管理层通过数据分析服务查看收费统计、欠费分析和经营分析等报表。数据分析服务内部的数据管道组通过 CDC 实时捕获各业务服务的 MySQL 数据变更,清洗转换后写入数据仓库;查询服务组基于数据仓库的预聚合数据生成多维分析报表,支持按时间、片区、用户类型等维度灵活筛选。
| 功能 | 说明 |
|---|---|
| 收费统计 | 按日/月/季/年统计收费金额、收缴率、缴费笔数,支持按片区和收费渠道下钻 |
| 欠费分析 | 统计欠费用户数、欠费金额、账龄分布,识别长期欠费用户,支撑催收策略 |
| 经营分析 | 收入趋势、成本对比、收缴率同比环比、用户增长分析,为经营决策提供数据支撑 |

数据时效性:收费统计和欠费分析报表基于数据仓库的预聚合数据,数据延迟控制在 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 模式(编排式) | 开户流程、停复供流程 | 每一步操作定义正向动作和补偿动作,任一步骤失败按逆序执行补偿 |

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 部署并按设备分片。

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 安全防护体系

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 开源组件提供,形成统一的治理技术栈。

9.2 Nacos 服务注册与发现
Nacos 同时承担服务注册中心与配置中心双重角色,是平台服务治理的基础设施入口。
9.2.1 集群部署
生产环境部署 3 节点 Nacos 集群,采用 raft 协议选举 Leader,确保注册数据强一致性。数据存储采用 MySQL 外部持久化(而非内嵌 Derby),支持配置历史版本回滚和审计追踪。


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 个请求 | 返回缓存的上一次报表数据 |
熔断状态机:

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 网关架构


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 的版本元数据实现流量按比例路由:
灰度流程:
- 新版本服务实例注册到 Nacos 时携带
version=v2元数据。 - 网关灰度过滤器检查请求 Header
X-Gray-Version:- Header 为
v2→ 路由至version=v2的实例。 - Header 为空 → 按权重路由,默认 90% 流量至 v1、10% 至 v2。
- Header 为
- 灰度比例通过 Nacos 配置动态调整(10% → 30% → 100%),无需修改代码或重启。
- 灰度期间通过 SkyWalking 监控 v2 实例的调用成功率和响应时间,异常时一键回滚(将 Nacos 灰度比例调回 0%)。
9.5 Seata 分布式事务
Seata 是平台的分布式事务解决方案,覆盖强一致性场景(AT 模式)和柔性事务场景(TCC/Saga 模式)。
9.5.1 Seata 架构

部署说明: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 校验。

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,必含字段 timestamp、level、service、traceId、message。敏感字段(证件号、银行卡号)在日志输出前自动脱敏,通过自定义 Logback Layout 实现。
10. RocketMQ 消息交互设计
10.1 消息架构总览
RocketMQ 是平台异步通信和事件驱动的核心中间件,承担三类职责:跨服务事件通知、异步业务解耦、数据同步管道。平台所有业务服务既是消息生产者也是消息消费者,通过 Topic 分类和 Tag 过滤实现消息的精准路由。

10.2 Topic 与消息分类设计
平台按业务领域规划 Topic,每个 Topic 内通过 Tag 区分消息子类型。消费组按服务+场景命名,同一服务的不同业务逻辑通过 Tag 过滤分别消费。
| Topic | 类型 | 生产者 | 消费者(消费组) | Tag 子类型 | 说明 |
|---|---|---|---|---|---|
payment-success |
事务消息 | 收费服务 | invoice-service、notification-service |
online(在线支付)、offline(线下缴费) |
缴费成功后触发开票和通知 |
refund-completed |
事务消息 | 收费服务 | notification-service |
full(全额退)、partial(部分退) |
退费完成后触发通知 |
bill-generated |
事务消息 | 计费账单服务 | notification-service |
season(供热季账单)、adjust(调整账单) |
账单生成后触发催缴通知 |
approval-result |
普通消息 | 审批服务 | notification-service、iot-control-service |
pass(通过)、reject(驳回) |
审批结果通知相关方 |
user-opened |
普通消息 | 用户服务 | notification-service、billing-bill-service |
individual(个人)、enterprise(单位) |
用户开户后触发通知和计费纳入 |
workorder-event |
普通消息 | 客服服务 | notification-service |
created(创建)、dispatched(派单)、completed(完成) |
工单生命周期事件通知 |
device-alarm |
普通消息 | IoT 控制服务 | notification-service、customer-service |
offline(离线)、fault(故障)、threshold(阈值告警) |
设备告警实时推送 |
data-sync-cdc |
普通消息 | Canal/Debezium | analytics-service |
按源表名打 Tag | 数据库变更事件同步至数据仓库 |
10.3 消息流转全景
以缴费场景为例,展示一次完整缴费从同步调用到异步消息的完整流转:

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

10.4.1 生产端保障
事务消息机制:对于"业务操作+消息发送"必须原子性完成的场景(如缴费成功后触发开票),采用 RocketMQ 事务消息。流程分三步:
- 发送半消息:生产者先向 Broker 发送一条半消息(此时消费者不可见),Broker 返回半消息发送成功。
- 执行本地事务:生产者执行本地数据库事务(如更新收费记录状态为"已缴费")。
- 提交/回滚:本地事务成功则向 Broker 发送 Commit(消息对消费者可见),失败则发送 Rollback(删除半消息)。

事务回查实现要点:生产者需实现 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 事务消息"组合方案:

设计要点:
- Seata 全局事务只覆盖"缴费订单创建+账单状态回写"这一步强一致性操作。支付回调到达后,收费服务在 Seata 事务内创建/更新缴费订单并调用计费账单服务回写账单状态。任一分支失败,全局事务回滚,缴费订单和账单状态均恢复原值。
- Seata 事务提交后,收费服务通过 RocketMQ 事务消息发送
payment-success事件。事务消息保证"本地事务提交"与"消息发送"的原子性——本地事务成功则消息一定投递,本地事务失败则消息一定不投递。 - 发票开具、用户通知、数据同步三个下游操作通过普通消费异步执行,消费失败自动重试,不影响缴费主流程。
异常场景处理:
| 异常场景 | 处理方式 |
|---|---|
| Seata 事务回滚(账单回写失败) | 缴费订单也回滚,支付服务发起退款,通知用户支付异常 |
| 事务消息半消息发送成功但本地事务执行中宕机 | Broker 事务回查,收费服务查询缴费订单状态——不存在则 Rollback,存在且已提交则 Commit |
| 事务消息 Commit 成功但消费者(发票服务)消费失败 | RocketMQ 自动重试 16 次,仍失败进入死信队列,运维人工干预或对账任务补偿 |
| 重复消费(网络重试导致) | 消费者幂等设计,重复消息被去重,不产生副作用 |
10.5.3 退费场景的事务一致性处理
退费涉及收费服务本地事务(更新收费记录为"已退费")+ 支付服务外部调用(执行退款)+ 计费账单服务回写(恢复账单为"待缴")。由于退款是外部支付渠道调用,无法纳入 Seata 事务,采用"本地消息表 + 事务消息"组合方案:

关键设计:
- 退费采用"先标记后执行"策略——审批通过后先将收费记录标记为"退费中"并冻结,防止在此期间重复退费。
- 退款执行(支付服务调用第三方接口)失败时,消息进入重试队列。重试期间收费记录保持"退费中"状态。
- 16 次重试仍失败进入死信队列,运维介入处理(如第三方接口临时故障,恢复后手动重发消息)。
- 如果死信处理最终确认退款无法成功(如原支付渠道已关闭),运维将收费记录状态回滚为"已缴费",并通过通知服务告知用户退费失败需线下处理。
10.5.4 开户场景的 Saga 编排
开户流程涉及用户服务(创建用户档案)、房产服务(绑定房产关系)、合同服务(生成合同)三个服务,是典型的长流程多步骤场景。采用 Saga 编排式(Orchestration),由用户服务作为编排者协调流程:

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 从),确保高可用:

部署要点:
- NameServer 部署 3 节点,互不通信,各自维护路由信息。生产者和消费者从任一 NameServer 获取 Broker 路由信息,单节点故障不影响服务。
- Broker 部署 2 组(A/B),每组 1 主 1 从。Topic 的队列分布在两个 Broker 组上实现负载均衡。主节点故障时从节点接管读请求,写请求自动切换至另一组主节点。
- 配置同步刷盘(
SYNC_FLUSH)+ 同步复制(SYNC_MASTER),确保消息写入主节点并同步至从节点后才返回成功,避免单点故障导致消息丢失。 - RocketMQ Dashboard 提供消息看板、积压监控、死信管理、消息轨迹查询等功能。
10.9 消息设计规范
| 规范项 | 要求 |
|---|---|
| 消息体格式 | JSON,字段命名使用下划线风格(order_id、user_id),禁止使用驼峰 |
| 消息体大小 | 单条消息体不超过 4KB;超过时只传递业务 ID,消费者通过 API 查询详情 |
| 消息头 | 必须包含 traceId(SkyWalking)、source(来源服务)、eventType(事件类型)、timestamp(发生时间) |
| 消息版本 | 消息体包含 version 字段,消费端按版本兼容处理,支持消息格式平滑升级 |
| Topic 命名 | 小写中划线风格(payment-success),以业务事件命名,不以服务命名 |
| Tag 使用 | 同一 Topic 内的子类型用 Tag 区分,消费者按 Tag 过滤,避免无效消费 |
| 消费组命名 | {服务名}-{场景} 格式(如 invoice-service、notification-service),同一消费组内负载均衡 |
| 幂等设计 | 所有消费者必须实现幂等逻辑,幂等键在消息设计文档中明确定义 |
| 消费超时 | 单条消息消费超时 30s,超时后 Broker 自动重试 |
| 消息轨迹 | 生产环境开启消息轨迹,保留 7 天,用于问题排查和链路分析 |
11. 热费缴纳高可靠与高并发实战
热费缴纳是平台最核心的交易链路,也是业务风险最高的环节。北方供热季集中在 11 月至次年 3 月,缴费高峰出现在供热季首月(11 月)和截止日前一周,日均缴费量可达平时的 10-20 倍。本章先分析缴费全链路可能发生的问题,再给出逐项解决方案和落地实现。
11.1 缴费全链路问题分析
一次完整缴费涉及客户端、API 网关、收费服务、计费账单服务、支付服务、第三方支付渠道、发票服务、通知服务共 8 个参与方,经同步调用和异步消息两阶段完成。下表按链路节点梳理可能出现的故障场景:

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

落地实现:
| 防护层 | 实现方式 | 说明 |
|---|---|---|
| 前端防抖 | 缴费按钮点击后 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)
问题根因:同一房产可能有多个缴费人(如房东和租户),两人同时查询到同一账单为"待缴"状态并同时发起缴费,如果不做并发控制,可能导致同一账单被缴纳两次。
解决方案:乐观锁 + 状态机校验。

落地实现:
| 措施 | 实现 | 说明 |
|---|---|---|
| 乐观锁 | 账单表增加 version 字段,更新时 WHERE version = ? |
无锁并发,性能优于悲观锁,适合"读多写少"场景 |
| 状态机校验 | 更新时 WHERE status = 'UNPAID' |
只允许从"待缴"流转到"支付中",已锁定账单拒绝再次缴费 |
| Seata 全局锁 | AT 模式自动获取行级全局锁 | 在全局事务未提交期间,其他全局事务不能修改同一账单行 |
| 前端提示 | 缴费失败返回明确的业务提示 | “该账单正在缴费中,请刷新后重试”,引导用户重新查询 |
账单状态流转规则:
| 当前状态 | 允许流转 | 触发动作 |
|---|---|---|
| UNPAID(待缴) | → PAYING(支付中) | 用户发起缴费 |
| PAYING(支付中) | → PAID(已缴) | 支付成功回调 |
| PAYING(支付中) | → UNPAID(待缴) | 支付超时/取消,订单关闭 |
| PAID(已缴) | → REFUNDING(退费中) | 退费申请审批通过 |
| REFUNDING(退费中) | → UNPAID(待缴) | 退费完成 |
| REFUNDING(退费中) | → PAID(已缴) | 退费失败回滚 |
11.4 支付超时与订单关闭(P4)
问题根因:用户调起支付后长时间未完成操作(如打开微信支付页面后离开),订单一直停留在"支付中"状态,导致账单被锁定,其他缴费人无法缴纳。
解决方案:订单超时自动关闭 + 定时任务兜底清理。

落地实现:
| 方案 | 实现 | 说明 |
|---|---|---|
| Redis 延迟队列 | 创建订单时写入 delay:order:close:{orderId},TTL 15 分钟 |
过期后触发监听器执行订单关闭逻辑,实时性好 |
| 定时任务兜底 | 每 5 分钟扫描 status=PAYING AND create_time < NOW()-15min 的订单 |
防止 Redis 延迟消息丢失,作为兜底 |
| 订单关闭逻辑 | 更新订单状态为 CLOSED,恢复账单状态为 UNPAID,释放 Redis 锁 |
关闭前需查询第三方支付平台确认未支付,防止"关单后用户完成支付" |
| 查询第三方确认 | 调用微信/支付宝订单查询接口确认支付状态 | 如果第三方返回"已支付"则不关单,走支付成功流程 |
| 用户主动取消 | 客户端提供"取消支付"按钮 | 主动取消同样走关单逻辑 |
"关单后支付"异常处理:极端情况下,订单关闭与第三方支付回调存在时间窗口冲突——订单刚关闭,用户在第三方完成了支付。处理策略:
- 关单前查询第三方支付状态,确认未支付才执行关单。
- 如果关单后仍收到支付成功回调,执行"补单"流程——恢复订单状态为 PAID,回写账单状态为已缴,发送缴费成功通知。补单通过幂等设计保证安全。
11.5 支付回调可靠性(P6)
问题根因:第三方支付平台(微信/支付宝)通过 HTTP 回调通知支付结果,但回调可能因网络抖动而丢失、延迟或重复推送。如果回调处理不当,会导致"用户已付款但系统未入账"或"重复入账"。
解决方案:回调幂等 + 主动查询补偿 + 回调重试确认。

落地实现:
| 措施 | 实现 | 说明 |
|---|---|---|
| 回调幂等 | 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 需要回查本地事务状态。如果回查接口本身也不可用,消息状态将不确定。
解决方案:回查接口高可用部署 + 本地事务状态可追溯 + 超时兜底。

落地实现:
| 措施 | 实现 | 说明 |
|---|---|---|
| 回查接口实现 | 实现 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 任务异常。
解决方案:消费失败自动重试 + 死信队列兜底 + 对账任务补偿。

落地实现:
| 场景 | 消费失败原因 | 重试策略 | 死信兜底 | 对账补偿 |
|---|---|---|---|---|
| 发票开具 | 税务系统接口故障 | 16 级梯度重试,约 4.5 小时 | 死信队列存储消息,运维通过控制台查看后手动重发 | 每日 03:00 开票对账:已缴费未开票的订单重新触发开票消息 |
| 缴费通知 | 短信网关不可用 | 同上 | 同上 | 通知补偿:每日扫描已缴费未通知的订单,补发通知 |
| 数据同步 | ETL 任务异常 | 同上 | 同上 | CDC 位点对账:检查数据仓库与业务库的差异,补偿同步 |
死信队列管理看板:平台开发了独立的死信管理看板,展示:
- 各消费组死信积压数量和趋势
- 死信消息内容和失败原因分类
- 一键重发功能(修复底层问题后批量重发)
- 死信超 10 条 P1 告警、超 50 条 P0 告警
11.8 缴费高峰期高并发架构(P1/P10)
问题根因:供热季首月日均缴费量可达平时的 10-20 倍,瞬时 QPS 从平时的 50 飙升至 1000+。如果不做容量规划和流量治理,单服务过载会通过调用链传导至依赖服务,引发全局雪崩。
整体方案:全链路压测定容量 → 分层限流控入口 → 弹性伸缩扩容量 → 降级保核心 → 削峰填谷平滑流量。

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=50、connectionTimeout=3s |
控制连接数,防止数据库被打满 |
| SQL 优化 | 账单查询走索引(idx_user_season),避免全表扫描 |
查询 RT 从 200ms 降至 10ms |
| 批量操作 | 账单生成采用批量 INSERT(每批 500 条) | 减少 SQL 执行次数,提升吞吐量 |
| 热点数据缓存 | 用户信息、房产信息、计费规则缓存至 Redis | 减少 80% 的数据库读请求 |
| 慢查询监控 | MySQL long_query_time=1s,慢查询告警 |
及时发现和优化慢 SQL |
11.8.6 削峰填谷
利用 RocketMQ 消息队列平滑峰值流量:

削峰效果:缴费高峰期收费服务以 1000 QPS 处理核心入账逻辑(同步),下游的发票开具、通知发送、数据同步通过 MQ 异步处理,消费者按自身能力匀速消费(200/500/100 QPS),避免瞬时 2000 QPS 直接打垮下游服务。MQ 积压在可接受范围内(通知延迟 < 1 分钟),高峰过后积压逐步消化。
11.9 第三方支付渠道不可用容灾(P5)
问题根因:微信、支付宝等第三方支付渠道可能因系统故障、维护或网络问题不可用,导致用户无法完成在线支付。
解决方案:多渠道兜底 + 降级引导 + 渠道健康检查。

落地实现:
| 措施 | 实现 | 说明 |
|---|---|---|
| 渠道健康检查 | 支付服务每 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 跨服务事务流转全景
平台六大业务场景中,有四个场景涉及跨服务数据写入,需要分布式事务保障一致性。下图展示各场景的事务流转路径与参与方:

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 个消费者 | 否 | 本地消息表 | 允许分钟级延迟、对账兜底 |

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 模式完整流程

支付回调后的入账流程:支付成功回调到达后,收费服务在 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 |

12.6 Saga 模式落地实现
12.6.1 适用场景与流程定义
Saga 模式用于开户流程和停复供流程——多步骤长流程,每步可定义补偿动作。平台采用 Seata Saga 状态机引擎,通过 JSON 定义流程编排。
开户 Saga 流程:

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 告警,运维通过管理台手动处理 |

12.6.3 停复供 Saga 流程
停复供场景的 Saga 流程涉及物理设备操作(阀门控制),补偿逻辑有特殊性——阀门控制失败不能回滚费用调整(用户已获批停供),而是转为人工处理:
| 步骤 | 正向动作 | 补偿动作 | 失败处理 |
|---|---|---|---|
| 1. 费用调整 | 计费账单服务按停供日期重新计算热费 | 恢复原计费方式 | 自动补偿 |
| 2. 阀门控制 | IoT 控制服务远程关阀 | 远程开阀(恢复供热) | 转人工工单,不影响费用调整 |
| 3. 通知用户 | 通知服务发送停供成功通知 | 无补偿(非核心) | 延迟补偿发送 |
关键设计:费用调整在审批通过后立即生效并持久化,阀门控制作为独立步骤——即使阀门控制失败,费用已按停供计算,不会因设备故障导致用户多缴费用。阀门控制失败转人工处理后,Saga 标记为"部分完成",不触发费用调整的补偿。
12.7 可靠消息事务落地实现
12.7.1 RocketMQ 事务消息
事务消息用于缴费成功后的异步操作——确保"收费记录更新"(本地事务)与"payment-success 消息发送"的原子性。

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 事务消息支持,通过本地事务+定时任务实现:

本地消息表设计:
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_count 和 next_retry_at |
指数退避:1s/5s/30s/2m/10m/30m |
| 5 | retry_count > 10 标记为 FAILED |
触发告警,人工介入 |
12.8 对账兜底体系
无论采用哪种分布式事务方案,平台均部署全链路对账任务作为最终兜底,确保数据一致性:

| 对账任务 | 执行频率 | 对账逻辑 | 自动修复规则 | 告警阈值 |
|---|---|---|---|---|
| 缴费对账 | 每日 02:00 | 收费记录 status=PAID 与账单 status=PAID 比对 |
收费已缴但账单未更新→回写账单;账单已缴但收费记录不存在→生成异常报告 | 差异数 > 10 |
| 退费对账 | 每日 02:30 | 退费记录 status=REFUNDED 与支付退款流水比对 |
退款成功但收费记录未更新→更新收费记录;退款失败但收费记录已标记→触发告警 | 差异数 > 5 |
| 开票对账 | 每日 03:00 | 缴费记录 status=PAID 与发票记录比对 |
已缴费未开票→重新发送开票消息;已开票但缴费记录已退费→触发发票冲红 | 差异数 > 20 |
| 消息补偿 | 每小时 | transaction_log 中 status=COMMITTED 但消息未确认的记录 |
重新通过普通消息发送 | 差异数 > 5 |
| Saga 状态 | 每小时 | seata_saga_log 中 status=RU 超过 24h 的记录 |
触发补偿流程;补偿失败标记人工介入 | 差异数 > 0 |
| undo_log 清理 | 每小时 | undo_log 中 log_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)不可用时,降级为本地事务+消息补偿模式 |