Skip to content

建行市场结算平台(亮点与难点)

附件: 8年经验.pdf

上面这位学员简历的项目没什么亮点和难点,投出去面试机会可能不多,经过和老师沟通后,着重优化了第二个项目建行惠市宝项目,这个项目本质上就是一个银行结算平台,优化如下:

项目名称:中国建设银行惠市宝(专业市场结算平台)

项目描述:
该项目是建设银行面向全国专业市场的统一支付结算平台,日交易金额过亿,服务数百家专业市场、上万商户的核心系统。项目采用微服务架构,主要包含合约、支付、清算、对账调账四大核心模块。

技术栈:

SpringBoot + SpringCloud + Redis + Kafka + Quartz + Oracle

核心职责与技术难点:

  1. 架构设计与性能优化
  • 实现了基于本地消息表+Kafka的事务最终一致性方案,确保支付、分账等核心场景的数据一致性
  • 采用多级缓存架构(本地缓存+Redis集群),将商户信息查询性能提升5倍,系统整体响应时间降低到200ms以内
  • 设计并实现了基于分库分表的高性能支付模块,通过分库策略和索引优化,使得单表数据量控制在千万级别,查询性能提升300%
  1. 支付流程优化与安全性提升
  • 设计了支付防重复提交机制,通过分布式锁+幂等性检查,有效防止重复支付
  • 实现了基于风控规则引擎的交易监控系统,支持灵活配置风控规则,有效拦截异常交易
  • 开发了支付结果实时通知重试机制,通过指数退避算法,确保通知成功率达到99.99%
  1. 清算系统设计与优化
  • 设计了支持多维度的分账规则引擎,可灵活配置商户分账比例、阶梯分账等复杂场景
  • 实现了分布式任务调度系统,通过Quartz集群确保大批量清算任务的可靠执行
  • 开发了智能对账系统,支持T+1差异化对账,对账准确率达99.999%,大幅减少人工对账工作量
  1. 系统监控与运维
  • 构建了基于ELK的统一日志收集分析平台,实现系统运行状态实时监控
  • 设计了服务降级和熔断机制,通过Sentinel实现系统限流保护
  • 实现了分布式链路追踪,通过SkyWalking快速定位系统瓶颈

技术亮点:

  • 采用SpringCloud微服务架构,实现系统高可用性,服务可用性达到99.99%
  • 使用分布式事务确保支付环节的数据一致性
  • 实现了支付、清算等核心模块的高并发处理能力,系统TPS达到3000+
  • 通过分库分表、缓存优化等手段,有效支持海量数据处理

项目成果:

  • 系统成功上线后支持日均交易量10万+,峰值TPS 3000+
  • 平台运行稳定,核心接口响应时间<200ms,系统可用性99.99%
  • 获得建行总行年度优秀项目奖,并在多个分行成功推广使用

这样的项目描述既保留了原有业务特点,又突出了技术难点和解决方案,同时加入了具体的性能指标和业务成果,使项目更具说服力和亮点。在面试时,你可以重点展开讨论系统架构、业务设计亮点、分布式事务处理、性能优化等技术难点的具体实现方案。

面试如何回答

一、实现了基于本地消息表+Kafka的最终一致性方案,确保支付、分账等核心场景的数据一致性

针对这个点,可以从以下几个方面来回答:

  1. 业务场景说明:
    在惠市宝项目中,支付和分账是两个核心场景。比如当一个商户收款1000元时,可能需要按照合同约定将这笔钱分给多个相关方,如市场方抽成10%,平台方抽成5%等。这个过程涉及多个账户的资金变动,必须保证数据一致性。
  2. 架构设计:
    我们采用了本地消息表+Kafka的最终一致性方案,具体架构如下:
    • 本地消息表用于记录事务消息,包含消息ID、业务类型、消息内容、状态、重试次数等字段
    • Kafka用于可靠的消息投递
    • 定时任务用于消息重试
    • 各个微服务通过消费Kafka消息来完成业务处理
  3. 具体实现流程:
    以支付成功后进行分账为例:

(1) 支付服务收到支付成功通知:

- 开启本地事务
- 更新支付订单状态
- 写入本地消息表,状态为待发送
- 提交事务

(2) 消息发送机制:

- 专门的消息发送线程扫描本地消息表
- 将待发送的消息投递到Kafka
- 投递成功后更新消息状态为已发送

(3) 分账服务消费消息:

- 消费Kafka消息
- 执行分账业务逻辑
- 更新分账结果
- 确认消息消费完成,更新消息状态为已处理
  1. 可靠性保证:
    我们通过以下机制确保消息可靠性:
    • 本地消息表和业务操作在同一个事务内
    • 定时任务定期扫描未成功发送的消息进行重试
    • 消息发送采用至少一次投递策略
    • 消费端保证幂等性处理
    • 引入消息状态机制,记录消息完整生命周期
  2. 异常处理:
    系统考虑了各种异常情况:
    • 消息发送失败:通过定时任务重试机制处理
    • 消息消费失败:通过消费重试机制处理
    • 重复消息:通过业务唯一标识保证幂等性
    • 消息积压:这个点要做好比较复杂,可以参考直播课《双十一高并发抢购(秒杀)系统三高架构实战》
  3. 监控和运维:
    我们还实现了完善的监控机制:
    • 监控消息发送、消费状态
    • 统计消息处理时延
    • 异常消息告警
    • 提供消息补偿接口

二、采用多级缓存架构(本地缓存+Redis集群),将商户信息查询性能提升5倍,系统整体响应时间降低到200ms以内

  1. 业务背景:
    在支付系统中,商户信息查询是一个非常高频的操作。每次支付、分账、退款等操作都需要查询商户信息,日访问量在百万级别。原来直接查询数据库的方式已经无法满足性能要求,所以我们设计了多级缓存方案。
  2. 架构设计:
    我们采用了两级缓存架构:
    • 一级缓存:本地缓存(Caffeine)
    • 二级缓存:Redis集群
    • 最后是MySQL/Oracle数据库持久层
  3. 具体实现:

(1) 缓存架构:

- 本地缓存:使用Caffeine,设置最大容量10000条,过期时间15分钟
- Redis集群:采用哨兵模式,确保高可用,存储全量商户信息,过期时间30分钟
- 采用二级缓存的读写策略:
    * 读取:本地缓存 -> Redis集群 -> 数据库
    * 写入:数据库 -> 失效Redis -> 失效本地缓存

(2) 缓存更新策略:

- 采用Cache-Aside模式
- 更新数据库后,删除Redis缓存
- 通过消息队列通知其他节点清除本地缓存

PS:如果有对多级缓存架构代码实现不清楚的可以参考直播课《京东生产环境Redis高并发缓存架构实战》

  1. 性能保障:
    我们采取了多项措施确保性能:
    • 本地缓存采用LRU淘汰算法,保留最常访问的商户信息
    • Redis集群使用哨兵模式,保证高可用
    • 使用布隆过滤器防止缓存穿透
    • 采用分布式锁防止缓存击穿
    • 不同级别缓存采用不同过期时间,防止雪崩
  2. 监控和运维:
    实现了完善的监控机制:
    • 缓存命中率监控
    • 缓存延迟监控
    • 缓存容量监控
    • 异常告警机制
  3. 实际效果:
    优化后达到以下效果:
    • 本地缓存命中率达到85%
    • Redis缓存命中率达到95%
    • 平均响应时间从1s降到200ms以内
    • 系统QPS提升5倍以上
  4. 问题处理:
    在实施过程中解决了以下问题:
    • 缓存一致性:通过消息队列通知机制解决
    • 缓存穿透:使用布隆过滤器
    • 缓存击穿:使用分布式锁
    • 缓存雪崩:错开缓存过期时间

三、设计并实现了基于分库分表的高性能支付模块,通过分库策略和索引优化,使得单表数据量控制在千万级别,查询性能提升300%

  1. 业务背景:
    在支付系统中,订单数据增长非常快,日订单量百万级别。随着业务发展,单表数据量已经超过5000万,查询性能下降严重,特别是在商户对账、订单查询等场景下,响应时间达到秒级,严重影响用户体验。因此我们进行了分库分表改造。
  2. 整体方案:
    我们采用了分库分表+索引优化的综合方案:
    • 分库:按照商户ID范围进行水平分库,分为8个库
    • 分表:按照订单时间范围进行分表,每个库按月分表
    • 索引优化:基于业务查询特点优化索引结构
  3. 具体实现:

(1) 分片策略:

  • 分库键选择:选择商户ID作为分库键
    • 原因:商户维度查询频繁
    • 实现:merchant_id % 8 确定库位置
  • 分表策略:按月份分表
    • 格式:t_order_yyyyMM
    • 优势:便于历史数据归档和清理

PS:关于分库分表分片策略选择的更多场景参考直播课《面试必问海量数据分库分表架构实战》

(2) 数据路由实现:

java
public class OrderRouter {  
    public String getTargetDatabase(Long merchantId) {  
        int dbIndex = merchantId % 8;  
        return "db_" + dbIndex;  
    }  

    public String getTargetTable(Date orderTime) {  
        return "t_order_" + DateUtil.format(orderTime, "yyyyMM");  
    }  
}

(3) 索引优化:

  • 商户维度查询:merchant_id + create_time复合索引
  • 订单查询:order_no唯一索引
  • 支付状态查询:pay_status + create_time复合索引
  1. 性能优化措施:
    除了分库分表,我们还采取了以下优化措施:
    • 引入分布式主键生成器,避免跨库ID冲突
    • 优化SQL语句,避免跨库关联查询
    • 添加适当的冗余字段,减少关联查询
    • 实现异步订单归档机制,定期归档历史订单
  2. 数据迁移方案:
    采用了平滑迁移策略:
    • 准备阶段:预先创建新库表结构
    • 双写阶段:新订单双写新旧库
    • 存量迁移:按照商户维度分批迁移历史数据
    • 切换阶段:验证数据一致性后切换读写

PS:关于分库分表数据迁移的完整方案实现参考直播课《面试必问海量数据分库分表架构实战》

  1. 异常处理:
    设计了完善的异常处理机制:
    1. 统一异常错误处理
    2. 路由失败:降级到默认库集群(备份)
这个点一般在大厂高可用系统里用得比较多,我举个详细场景说明:

某电商平台的订单支付系统,商户ID为88888888的订单支付请求到来时,恰好目标分库db_0发生故障。

  1. 正常路由过程:
// 计算目标分库  
Long merchantId = 88888888L;  
int dbIndex = merchantId % 8;  // 结果为0  
String targetDb = "db_0";  

// 发现db_0不可用,触发降级
if (!isDatabaseAvailable("db_0")) {
return "db_default";
}

  1. 默认库的数据结构:
-- db_default库中的表结构(与原表完全一致)
CREATE TABLE t_order (
id BIGINT PRIMARY KEY,
merchant_id BIGINT,
order_amount DECIMAL(10,2),
pay_status VARCHAR(20),
create_time DATETIME,
-- 额外增加的字段,用于追踪
target_db VARCHAR(20), -- 记录原目标库
target_table VARCHAR(50), -- 记录原目标表
sync_status VARCHAR(20) -- 同步状态
);
  1. 数据处理流程:
@Transactional
public void handleOrderPayment(Order order) {
try {
// 1. 尝试写入目标库
String targetDb = orderRouter.getTargetDatabase(order.getMerchantId());
if (!isDatabaseAvailable(targetDb)) {
// 2. 目标库不可用,写入默认库
saveToDefaultDatabase(order);

    // 3. 记录同步信息  
    markForSync(order, targetDb);  

    // 4. 触发异步同步任务  
    asyncSyncTask.submit(order.getId());  
}  

} catch (Exception e) {
log.error("Order payment failed", e);
throw new PaymentException("Payment processing failed");
}
}

  1. 数据同步机制:
@Scheduled(fixedDelay = 5000) // 每5秒执行一次
public void syncFromDefaultDatabase() {
// 1. 查询待同步的订单
List<Order> pendingOrders = findPendingOrders();

for (Order order : pendingOrders) {  
    try {  
        // 2. 检查目标库是否恢复  
        if (isDatabaseAvailable(order.getTargetDb())) {  
            // 3. 同步到目标库  
            syncToTargetDatabase(order);  

            // 4. 更新同步状态  
            updateSyncStatus(order, &quot;SYNCED&quot;);  
        }  
    } catch (Exception e) {  
        // 5. 同步失败,记录失败次数,后续重试  
        updateSyncStatus(order, &quot;SYNC_FAILED&quot;);  
        log.error(&quot;Sync failed for order: &quot; + order.getId(), e);  
    }  
}  

}

默认库的关键特点:

  1. 数据临时性:
    • 仅在目标库不可用时使用
    • 数据最终会同步到目标库
    • 同步完成后可以清理
  1. 完整性:
    • 与原表结构完全一致
    • 增加同步相关的管理字段
    • 保存完整的业务数据
  1. 同步机制:
    • 异步同步到目标库
    • 定时检查目标库可用性
    • 失败重试机制
    • 同步状态追踪

这种机制的优势:

  1. 保证业务连续性:即使分库出现故障,订单支付仍能正常进行
  2. 数据不会丢失:所有数据都会被妥善保存
  3. 最终一致性:通过同步机制确保数据最终一致
  4. 可追踪性:完整的同步状态记录

PS:如果用了分库分表的中间件,比如shardingsphere等,这些中间件一般是会给目标库(主库)配置从库,从库会保持跟主库同步一致,当目标库出问题时,通过shardingsphere的配置可以让操作故障转移到从库去。


7. 监控运维:
实现了全方位监控:
- SQL执行时间监控
- 分片路由成功率监控
- 表容量监控
- 查询性能监控
8. 实际效果:
改造后达到以下效果:
- 单表数据量控制在1000万以内
- 查询响应时间从800ms降到200ms以内
- 系统吞吐量提升3倍
- 支持水平扩展,可以通过增加分库数量提升容量

四、实现了基于风控规则引擎的交易监控系统,支持灵活配置风控规则,有效拦截异常交易

业务背景
作为支付公司,我们每天要处理数百万笔交易,需要在毫秒级别完成风控决策。风控规则涉及交易金额、频次、商户画像、用户行为等多个维度,且规则需要能够快速调整以应对新的风险。

关于规则引擎的详细代码实现可以参考直播课《传统CRUD保险系统亮点与难点优化实战》

五、开发了支付结果实时通知重试机制,通过指数退避算法,确保通知成功率达到99.99%

这个点面试官可能会问到指数退避算法,解释如下:

指数退避是一种在重试操作时,能够自动调整等待间隔的算法。每次重试失败后,等待时间会以指数形式增加,同时添加一定随机性,避免多个客户端同时重试导致的'惊群效应'。(这个随机性可以避免大量客户端同时重试,导致服务器在固定时间点承压过重)

六、服务器部署情况

参考保险分销平台的例子

更新: 2025-01-21 21:30:42
原文: https://www.yuque.com/tulingzhouyu/db22bv/fhtiwxnmcgg0nztu