在讲解对账系统设计前 先画个大致的草图 说明下背景
我们为什么这样做
公网对账:简单的可以说 就是一个大平台 部署了很多应用 比如支付接口 基础平台 对账服务等应用,主要用于对外 当然公网的系统架构整体布局比较复杂 这里暂且不去表述这个大平台架构,对于公网对账就是对外拉取所有的通道对账单文件和公网平台的本身的数据库订单做对账
私网对账:简单来说就是一个个项目应用(公网和私网都有各自的数据库 数据结构一样 只是公网的数据包含了所有的私网的各个项目的数据 具体数据如何同步目前暂且不表) 对于私有云对账就是把公网对账过程中拉取的第三方对账单文件和公网平台订单统一打包压缩的解密成文件通过内网传输 后 再解密解压 使用私网内的数据库的数据对账
问题一:为啥是这种模式
因为整个公网大平台部署在多个异地 提供多种服务,特别是支付服务,而私网针对一个个子项目应用 主要目的就是用来给银行机构登录用来对账清分使用,同时某些原因私网不提供外网的能力 所以需要搭建这种模式,当然公网私网数据库如何同步的问题 是个很大的话题 此处暂且不表
问题二:为啥公网对完账后 私网还要对账一次
用公网对账后会将通道对账文件和本平台订单文件打包内网给私网进行二次对账,这样做主要目的在于用使用者主要使用私网的环境对账清分,而私网的数据库有可能和公网数据库有点细微差别(比如对账费率有点不同),所以用私网对账才能保证对账的准确性。另外假如私网不想和第三方对账对账(也就是本地对账),我们可以直接将公网对账结果copy给私网,对账结果速度非常快。
对于这种模式 公网的服务器一般配置比较高 同时配置了多台服务器,而对于私网相当于将对账任务做了划细 所以私网我们基本部署是单台服务器
讲完了上述背景后,聊下系统变更历史
V1.0时代 技术选型spring +mybatis+memcached+jdbc
这种方式比较直接 就是多线程下载通道对账单 然后下载对账单的文件解析成自己适配的dto 放入内存 然后把订单从读库里读出 放到内存里 一一比对 这种使用方式针对数据量不大时可以用,当量大是比较吃内存
这里用到了几个技术点:
使用memached 对于对账系统比较特殊 一般都是使用的昨日的订单数据 拉取的基础服务数据也是昨天的数据 所以此处可以定时任务在凌晨的时候将需要的数据预热 缓存
将订单数据和对账按商户id进行hash到一个队里里 这样便于扩展, 当时考虑使用redis对账 但是考虑到私网不能安装redis,同时redis单台对账肯定不行,所以这里直接用jvm队列机制内存操作 这里订单基于hash 对列 起到缓冲作用 数据量不是很大时 也不会导致内存溢出
读取订单备库时(已按时间分区) 每个线程拉取6个小时的订单 也就是4*6=24,将数据拉取 此处直接使用jdbc拉取 后面批量入库也直接用jdbc 不再使用mybatis
每个hash队列对账完后 需要主动释放资源 当负载过高需要Sytem.GC(),当然这里的GC优化 是根据不同场景去做了调优 具体不再表述 私网的GC收集器 parnew+cms收集器 堆配置4g 永久代1g
由于我们的私网对账的项目对于公网来说 数据相当于被切割了细小的服务,同时基于某些原因 私网有些服务比如redis等很多中间件不能安装 针对私网的对账 目前采用的是 改方案
V 2.0时代
V2.0主要是对1.0结构调整 和使用了redis
技术选型 spring +mybatis+memcached+jdbc+redis
对账大致流程:jdbcJob -> distributor -> aggregator -> preposer -> exporter
jdbcJob 并发拉取订单
distributor 分发器,订单分发
aggregator 聚合器,聚合汇总
preposer 前置器,输出前置处理
exporter 输出器,输出-入库
Distributor:
1.修改任务状态-加锁 update BillStatus 通过数据库Status更新是否成功 使用数据库乐观锁 拉取对账单任务
2.拉取账单:下载第三方账单(延签)-解析对账单到BufferedReader读取到redis内存
3.JDBC并发对账 分发 聚合:
拉取订单 拆分:
拉取订单的时间跨度是6个小时*拉取订单的并发数是4个=24(一天的量)
初始化分发池:
初始化聚合池:
* 1)求sum count等操作
* 2)商户、渠道信息等查询回填
* 3)商户费率查询,手续费计算等
* 4)银联多费率、封顶值、品牌费计算等
* 5)其他计算
分发队列DataQueue: 队列容量=100000 阻塞队列=ArrayBlockingQueue
distribute:发数据 根据商户id运用hash算法找到分发路径
4.订单比对
a.先从redis订单拷贝到本地内存thirdMap
b.jdbc分批次 拉取平台数据库表 Job hash对和对账单同一个队列
c.第三方对账单和平台数据库比对 记录:
需要对账:
1).比金额====一致 获取对平的订单的对账结果 --》 分发队列DataQueue分发
2).比金额====不一致 进入异常订单
3).平台有第三方没有 进入异常订单 平台挂账
本地对账:获取对平的订单的对账结果dto--》 分发队列DataQueue分发对账结果dto
第三方有,本地没有:代表平台改天没有数据或平台库里都没这笔订单:
成功订单,调用第三方接口去同步订单的状态,时间 金额 并写入数据库继续比对
记录平台少订单的异常记录
d:分发队列DataQueue DataAggregator做聚合汇总:统计笔数 金额 商户手续费 第三方手续费
f:数据输出器:
* 1)数据前置预处理
ExportPreposer-->入库前的处理:对汇总后的数据进行金额处理,如抹零
* 2)商户日结入库
* 3)银联商户日结入库
* 4)其他明细入库
入库后 最后触发GC
改时代系统 也有很多问题,比如hash散列不一致导致 某个队列内存占用过高,还有比如redis挂掉了怎么办 ,另外订单一一比对过程中对内存的消耗也很高 总之问题较多 ,但是基本满足目前场景需求即可 无需过多设计
V3.0
V3.0目前主要是对数据库的改造上,订单改造,订单表和订单明细表合二为一 去掉不必要索引 ,将对账适配的使用字符串比对,内存比对换成fastDB等 此处这里不再 讲述
目前系统 公网使用v2.0 私网使用v1.0 效果还是可以