- 如何保证业务的一致性?
- 自己的回答
分为两个部分:
第一部分是信贷系统与第三方支付系统如何保证业务的一致性,采用的是以支付系统的结果为最终的业务结果,如果通过联机调用时返回业务终态(成功或者失败)那么即用这个终态,使用TCC的方式更新信贷系统下的三个子域,本质上是2PC,如果通过联机系统未能获取到业务的终态时,通过批量任务来完成状态的同步,本质上是通过最终一致性的方式来保证事务的一致性;
第二部分是在得到支付系统返回的终态后,信贷系统内部的三个子域相互同步状态,第一个是采用信贷核心系统作为内部状态的提供者提供相应的查询状态接口,另外两个系统通过定时轮询的方式来同步状态,另外一方面通过设置状态异常的监控告警机制来对异常流水进行监控
- 结合Ai后的回答
我们采用的是“TCC + 最终一致性补偿机制”相结合的方式,分别应对“联机强交互场景”和“异常或处理中场景”。
在联机交易流程中分成三个阶段:
Try 阶段:
额度系统预占额度
信贷系统预生成交易流水(标记为 pending)
调用支付系统发起扣款
Confirm 阶段
支付成功 → 确认额度扣减 + 信贷入账
Cancel阶段
支付失败 → Cancel 回滚预占额度 + 入账失败
有几个需要注意的点是
1. TCC中的操作的使用全局唯一的业务流水号进行操作
2. Confirm和Cancel都是幂等的
3. 数据库底层使用唯一索引作为兜底保证不会重复记账,并且在业务层所有分支事务状态必须是单向流转(INIT → TRYING → CONFIRMED/CANCELED),避免状态回滚。
4. 通过“事务状态表 + 幂等控制 + 状态流转 + 超时补偿”来解决空悬挂、业务悬挂、重复调用和乱序问题等问题
5. 所有关键状态变更必须在本地事务内完成,避免状态漂移
对于处理中或未知状态的交易,采用最终一致性补偿的方式
当联机调用无法获得终态(例如:支付超时\网络抖动\第三方系统处理中)
我们采用:
定时任务主动查询支付状态
对异常订单做对账补偿
同步最终状态
同时配套:
异常订单监控告警
长时间未终态自动标红
支持人工干预
对于最重要的核算系统宕机的场景的处理:
如果是Try阶段宕机那么该笔交易如果写入系统,后续通过最终一致性的补偿方案会设置成失败,如果没写入系统,这不存在该笔交易;
如果是Comfirm阶段失败,Confirm失败并不会导致业务直接失败或者挂起,而是记录到事务日志表,由异步补偿线程扫描重试,超过重试阈值进入异常队列并触发告警;
最后补充一下,对于选择分布式事务的解决方案,是根据金融系统在高并发场景下,我们更关注的是交易的可控性,而不是强一致性,才选择的TCC+最终一致性补偿机制结合来实现;
- 如果核算系统宕机2小时这么办?
- 自己的回答
首先确认宕机发生的原因,是否可以紧急修复尽量避免影响业务,如果无法修复,需要考虑是否有备用方案可以让业务正常进来,后续通过补偿来完成核算系统的业务
- 结合Ai后的回答
遇到这种问题要分成两个阶段进行处理,第一个阶段是确认问题,第二个阶段是处理问题;核心的处理原则是尽量减少对业务的影响;
## 第一阶段:
1. 找到发生问题的原因,判断故障级别以及评估恢复的时间,我在团队内部分享过稳定性建设的相关内容,可以按照文档进行操作
首先进行故障分级:
判断是否单点故障(可快速切换)、是否数据库问题、是否整体不可用、评估恢复时间(<10分钟/>30分钟)
如果短时间可恢复就采用启动限流、暂停消费、等待恢复等操作;如果超过30分钟都无法恢复的话,就启动降级策略
## 第二阶段
主要做的事情是降级处理策略,核心的原则是尽量保证不中断主流程、不产生异常账务数据、可恢复记账
目前采取的降级策略是MQ异步承接流量+恢复后重试的方案
对于不涉及到金额的交易异常,采用异步MQ的方式进行处理,如果MQ也有异常的话采用日终差错补账的方式进行处理
另外一种是涉及到金额的交易异常,这种目前是采用的是上游案件+核算记录相结合的方式来保证数据的不重复和可重试,通过唯一的申请流水号的方式确认业务状态;
保证数据不丢失的方案是通过:
1. 本地事务保证交易落库
2. 消息的手动确认保证可靠的消费和投递
3. 日终全量对账的方式保证流水和账务的一致性
## 恢复后的处理步骤
1. 限流恢复策略,防止流量过大压垮核算系统
2. 通过唯一交易流水号保证不重复记账
3. 日间巡检+日终对账补账务结合的机制
对于客户的影响是交易状态为处理中,延迟展示
- 线上CPU 100%的排查实战
在 2025 Q1 末尾,我们核算系统某节点 CPU 飙升至 400%(机器为 4 核),触发告警。
我先判断问题类型:QPS无明显波动、内存水位正常、CPU 持续拉高;通过 Grafana 观察 JVM 指标发现:Full GC 次数异常增加、STW 时间明显上升
初步判断为:GC 频繁导致 CPU 被 GC 线程占满,通过查看GC日志确认:Eden 区频繁被打满,晋升失败,触发 Full GC;这说明:存在短时间大量对象创建,或单次大对象分配
紧接着定位对象来源,由于线上不能使用 Arthas,我们通过:jmap dump 堆,jstack 抓线程栈,分析堆对象分布,发现:有一个接口返回的 List<PO> 集合异常巨大,进一步定位到:前端未传入客户号参数,导致SQL未带where条件,查询全表数据
导致:
大量数据加载进 JVM,填满 Eden 区,晋升至老年代触发频繁 Full GC,CPU 被 GC 线程打满。
当时我们没有立即重启,是通过配置中心临时增加了这个接口的参数必填校验,确认问题后才滚动重启节点恢复,恢复时间约20分钟。
最后在复盘这个问题后确认本质上是没有对数据进行访问防御能力,后续做了四层治理:
1️⃣ 接口层防御
必填参数校验
默认增加分页 limit
2️⃣ DAO 层防御
禁止无 where 条件查询
增加最大返回行数限制
得到的经验就是:
- 高 QPS 系统必须假设调用方会传错参数
- 任何数据查询必须做“最大数据量保护”
ps:内存堆栈信息是通过自动oom自动dump命令导出的
- 分布式事务与高并发一致性
碎碎念
- TimerTask
Timer timer = new Timer(false);
timer.schedule(new RefreshTask(), 10*1000L, 30*1000L);
private class RefreshTask extends TimerTask {
@Override
public void run() {
reflesh();
}
}
reflesh() {
// 拉去权限数据
// 更新缓存数据
}
TimerTask在执行时,如果抛出异常不进行捕获就直接终止,建议使用ScheduledThreadPoolExecutor来替代
- mycat全局表插入的时抛出索引重复
现象是mycat在插入时,抛出了索引重复但是再次查询时,确有数据
原因是因为mycat在查询时候只会随机查询一个分库中的数据,由于不同分库的主键序列不一样,导致每次插入时,id不一样,随后又通过id来进行删除,这样只会删除一个表的数据,导致数据未被删除的库再次插入时抛出索引重复。
- mycat全局表随机选择库
public static String getAliveRandomDataNode(TableConfig tc) {
List<String> randomDns = (List<String>)tc.getDataNodes().clone();
MycatConfig mycatConfig = MycatServer.getInstance().getConfig();
if (mycatConfig != null) {
//将dataNodes随机排列
Collections.shuffle(randomDns);
//省略...满足条件选取第一个dataNode
}
// all fail return default
return tc.getRandomDataNode();
}
- 对重复数据的不同处理在1.6.x上对于重复数据是直接抛出异常,最新的版本则是返回修改值
- 查询端口占用情况
# 端口占用情况
netstat -nap |grep 2181
# 端口连接数
netstat -na | grep 2181 | wc -l