一、背景:
目前正在负责XX公司的退款系统;此系统经过N多人迭代,N多个兼容版本、N多种代码风格,目前已经无法维护下去了;借此机会,将整个系统推倒重来、彻底重构了一遍;
经压测,重构后性能(吞吐量)提升了12倍,其他指标提升如下:
为什么要重构?
二、 重构前场景调研:
三、重构前后架构对比:
之前系统架构图:
大体介绍下这个系统是干什么的,都做了些什么事:
- 这是一个退款系统,由上游系统发起退款申请(相当于底层服务)
- 多场景:退款的场景有N多种,现在每一种退款都维护一套单独逻辑(需要抽象)
- 退款单组装:接收到退款请求后,做了一些基本的校验,校验通过后去组装退款单实体,组装实体的时候有些属性需要调用外部接口获取(这一步网络开销很耗时)
- 持久化:待退款单组装完毕后,将退款单入库;入库前又做了一些规则校验
- 库存扣减:通过dubbo接口调用支付系统扣减库存
- 推送外部系统:推送mq给财务系统
- 发票红冲:调用发票系统进行发票红冲
- 退费发起:定时任务捞取退款单,调用网关系统,请求三方退费
- 数据同步:大数据系统会T+1日同步退款单表
嗯,这么多步骤尽然在一个事务当中;得回之前并发量不大,不然数据库早挂了!
我们来分享下这个系统有哪些问题,基于这些问题我们改如何改造:
1、这里最大的问题就是:这么多操作都在同步线程处理显然是不合理的,系统要改成异步处理;规则校验+库存扣减完毕后,快速返回,其余逻辑异步处理
2、数据一致性问题;第4、5、6、7步的数据一致性无法保证
3、每一种退费场景都需要维护一套,代码大量冗余,维护成本相当之高
4、目前退费单表近1亿数据量,需要支持APP和WEB端各种维度、各种条件查询,响应已经很慢
5、事务力度太大,需要减小或弃用事务
6、没有对入口限流,如发生大量请求系统会瘫痪
7、系统模块耦合严重
8、非实时退款
9、处理链路过长
10、无挡板、无限流
改造后系统架构图
优化点如下:
1、架构异步化改造(提高并发)
按之前所说,同步线程里之做
1、基本规则校验:
2、退款单组装:这里只组装基本属性,需要调外部接口填充的属性在异步里做
3、退款单持久化+消息表持久化+远程余额扣减:这里引用了seata组件AT模式,保证了这三部的强一致性(消息表的目的第4部与第3部的最终一致性)
4、发送统一消息:此时就可以返回给上游
2、校验规则前置、快速失败(提高并发)
3、非核心属性填充后置、异步处理(提高并发)
这里说的就是退费单表需要调用外部接口填充的属性,在异步的mq里去做
4、减少调用链路(提高并发)
5、本地二级缓存(提高并发)
城市字典表缓存在了redis的,value值过大,在高并发场景下,会将带宽打满;做了本地三级缓存解决
6、OLAP数据库切换(查询性能优化)
退款单表存在于mysql中,近1亿数据量访问会很耗时;所以将mysql中数据通过DTS实时同步到ADB(阿里的分析性数据库,查询性能非常高)中,那么以后查询全部切到ADB
7、使用seate+消息表设计保证了数据强一致性及最终一致性(一致性)
seata组件保证了退款单持久化+消息表持久化+远程余额扣减的强一致性,消息表的作用是 如果seata这一步成功了,消息发送失败了,可以基于消息表补偿;(seata要比本地事务性能低3倍左右,后续还要优化)
8、使用消息驱动、事件分发完成周边业务逻辑的结耦(结耦、削峰填谷)
消息中间件会增加系统的灵活性,当退款事件完成后,会发送一个统一消息,需要感知这个事件的服务 只需要订阅消息即可,从而达到系统结耦的目的
9、精炼代码、使用工厂、模板、责任链、建造者、过滤器、策略、外观、适配器、装饰器设计模式抽象设计退款框架(可维护性、扩展性)
10、映射码系统搭建(配置化、扩展性)
映射码系统是基于三方各种返回码、对我们系统订单的状态及事件映射,更直观的观察各个状态码及其统计,在不改变代码的前提下,通过配置即可映射不同状码触发的事件
11、非核心业务剥离(稳定性)
我们将退款系统中涉及的查询接口、导出接口等相关业务进行剥离;因为在高并发场景下,查询接口会占据大量的系统线程,从而导致cpu频繁调动,增加系统开销,剥离后的另一个好处就是 如果需求上有查询接口的改动,那么只发查询服务器即可
12、入口限流(稳定性)
在入口处我们使用了sentinel进行了接口tps限流,保证系统最后一道防线
13、灰度切换流量(安全性)
我们设计了一个开关,在接口入口处(filter),可按百分比、地区、userId进行流量灰度;如果新接口有问题话,也可快速回滚
14、使用redission+gauva+sentinel进行调用三方限流(减少无效请求)
我们调用微信、支付宝等三放的退款接口时,同样也会遭到三放的限流,所以在调用三方前 我们会对系统订单限流,从而减少无效调用
15、mock挡板开发(场景模拟)
在开发环境,有些异常返回码是无法模拟的,从而导致测试场景覆盖不全;所以需要一个mock挡板系统来模拟与三方交互
16、链路追踪(快速定位)
这个非常重要,能帮助我们快速定位问题,我们的链路追踪不进能在http请求中串通、在mq、rpc交互下及多线程模式下也全部打通,具体参考我的另一篇博客,专门来讲全链路追踪,点击这里
17、减少事务力度、参数调优
将事务粒度最小化,尽量把事务中的查询、接口调用、复杂计算放在事物外提前做好,用参数的形式传到事物中,保证事务中仅有简单的更新、删除操作
18、Log4j升级为Log4j2
这点也非常重要,Log4j采用的是日志同步刷盘,会阻塞住线程,而Log4j采用了异步刷盘策略,必要时可以丢弃日志,在我的另一篇系统优化博客中会介绍,点击这里
19、数据库连接调优
数据库连接池设置为系统cpu*2+1为最佳,过多的连接会导致cpu平凡切换,具体看这里
20、tomcat线程数调优
根据系统的业务类型来进行tomcat线程数调优,距离拿4核8G物理机来说,如果是io型业务,maxThread=1000即可,如果是cpu密级型,可减少maxThread的数量;jvm的内存分配调优同样也是这个道理