支付系统对账算法优化方案 转 http://www.yeeach.com/post/1318#comment-152796
一、目前对账的算法:
1、从上游渠道(银行、银联等金融机构)获取对账文件,程序逐行解析入库
2、在存储过程中,以上游对账文件的表为基准,程序逐行读取并与我们系统的交易记录/账务记录(有账务系统情况下,合理方案应该是于账务记录)对比,查找出差异记录。
3、以我们系统的交易记录/账务记录为基准,程序逐行读取与上游对账文件对比,查找出差异记录
二、目前对账算法问题:
1、使用存储过程,对账过程都在数据库端完成,对数据库性能影响较大,而且对账逻辑扩展极为麻烦
2、逐行比对算法效率较低,但算法上并无好的优化余地。如果采用数据库INTERSECT、MINUS对数据库压力也高。
3、在业务量大的情况下(例如有上百家上游渠道需要对,每一家都有几十万条交易记录),对账服务器及数据库服务器负荷较高。即便采用读写分离,对账时候使用读库,压力一样很大。
4、导入批量文件,逐行入库效率较为低下(每一次都需要建立网络连接、关闭连接)
三、对账算法优化思路:
1、涉及网络传输的,尽量采用批量方式操作,减少网络消耗及时间消耗
2、使用Redis等NOSQL数据库,降低数据库服务器的压力。同时在扩展上也容易,一台Redis服务器不够,可以无限制增加用于对账用的服务器。
3、使用Redis的set集合的sdiff功能,利用Redis sdiff算法的高性能,比对上游记录和我方记录的差异
四、对账算法说明:
1、利用Oracle/Mysql的load data infile将对账文件批量导入到数据库
2、程序读取上游账务记录表,对上游账户记录执行select concat(channel_id, ":" , order_id , ":" , HASH_MD5(channel_id , order_id , amount , status , timestamp1 , timestamp2 ,…) ) as hashid from table ,得到对应的SET集合。
这里思路是将需要对账的记录拼成格式为:channel_id:order_id:hashid形式的串,以便作为Redis SET集合的item。
其中channel_id和order_id用于标识对应的渠道及订单,只是例子,根据实际需要补充。之所以要在hashid前面带上channel_id和order_id,主要目的是在sdiff后,能够作为主键,根据set结合的item查找出对应的数据库记录。
3、对我方的交易记录表/账户历史表采用上一步的类似算法,得到对应的SET结合
4、采用Redis的pipeline功能(Redis的各种客户端,包括java客户端jedis 都提供此功能),将上游账务记录SET集合和我方的交易记录/账务历史记录SET集分别批量执行sadd插入Redis,得到两个SET集合。
由于单条记录sadd 插入Redis效率较差(每一次都涉及网络open、close、传输消耗),因此使用Redis的pipeline功能,已实现批量入库功能。
可以参考:http://redis.io/topics/mass-insert
5、利用redis的sdiff功能查找出上游账务集合与我方记录集合的差值
6、从sdiff 集合中channel_id:order_id:hash,定位对应的数据库记录,更新对账状态。
具体性能及实现可以在实现后测试对比一下,应该会有大幅的性能提升。