高频交易支付架构并不复杂

写在前面支付系统是整个交易系统中相当核心的一部分功能,以我们的交易中台为例,通过领域方式的拆分,支付架构隶属于订单团队,在整个用户下单之后进行支付,支付之后成单进入交易履约流程 。
支付系统由于本身和金融相关,不像其他高频系统面对海量请求可以大量使用缓存,异步mq等方式解决三高问题 。支付系统对数据一致性要求更高,所以对于其架构设计原则还是有自己特点的 。
分库分表构建一个支撑每秒十万只读系统并不复杂,无非是通过一致性哈希扩展缓存节点,水平扩展web服务器等 。每秒钟数十万数据更新操作,在任何数据库上都是不可能的任务,首先需要对订单表进行分库分表 。
在进行数据库操作时,一般会用ID(UID)字段,所以选择以UID进行分库分表 。
分库策略我们选择了“二叉树分库”,所谓“二叉树分库”指:在进行数据库扩容时,以2倍数进行扩容 。比如:1台扩容2台,2台扩容4台,以此类推 。最后把Order库分了8个库中,每个库10个表 。
根据uid计算数据库编号:
分库信息 = (uid / 10) % 8 + 1
根据uid计算表编号:
表编号 = uid %10
订单ID订单系统的ID必须具有全局唯一的特征,简单的方式是利用数据库的序列,每操作一次就能获得一个全局唯一的自增ID,如果支持每秒10w订单,那每秒至少需要生成10w订单ID,通过数据库自增ID显然无法完成上述请求 。所以通过内存计算获取全局唯一的订单ID 。
JAVA领域著名的唯一ID应该是UUID了,不过UUID太长且包含字母,不适合做订单ID 。
通过反复比较筛选,借鉴Twitter的算法实现全局唯一ID 。
三部分组成:

  • 时间戳
  • 时间戳的粒度是毫秒级,生成订单ID时,使用System.currentTimerMillis()作为时间戳 。
  • 机器号
  • 每个订单服务器都被分配一个唯一的编号,生成订单ID时,直接使用该唯一编号作为机器即可 。
  • 自增序号
  • 当同一服务器的同一号码中有多个生成订单ID的请求时,会在当前毫秒下自增此序号,下一个毫秒此序号继续同0开始 。如同一服务器同一毫秒生成3个订单ID请求,这3个订单ID的自增序号分别是0,1,2 。
最终订单结构:
分库分表信息 + 时间戳 + 机器号 + 自增序号
还是按照第一部分根据uid计算数据库编号和表编号的算法,当uid=9527时,分库信息=1,分表信息=7,将他们进行组合,两位的分库分表信息即为”17” 。
最终一致性我们通过对order表uid维度的分库分表,实现了order表的超高并发写入与更新,通过uid和订单ID查询订单信息 。
上面方案虽然简单,但是保持两个order表机器的数据一致是很麻烦的事情 。
两个表集群显然是在不同的数据库集群中,如果写入与更新中引入强一致性的分布式事务,这无疑会大大降低系统效率,增长服务响应时间,这是我们所不能接受的,所以引入了消息队列进行异步数据同步,为了实现数据的最终一致性 。
当然消息队列的各种异常会造成数据不一致,所以我们又引入了实时服务监控,实时计算两个集群的数据差异,并进行一致性同步 。
高频交易支付架构并不复杂

文章插图
 
数据库高可用所谓数据库高可用指的是:
当数据库由于各种原因出现问题时,能实时或快速的恢复数据库并修补数据 。
从整体集群角度看,就像没有出任何问题一样,需要注意的是,这里的恢复数据库服务并不一定是指修复原有数据库,也包括将服务切换到另外备用的数据库 。
数据库高可用的主要工作是数据恢复月数据修补,一般我们完成这两项工作的时间长短,作为衡量高可用好坏的标准 。
我们认为,数据库运维应该和项目组分开,当数据库出现问题时,应由DBA实现统一恢复,不需要项目组操作服务,这样便于做到自动化,缩短服务恢复时间 。
高频交易支付架构并不复杂

文章插图
 
如上图所示,web服务器将不再直接连接从库DB2和DB3,而是连接LVS负载均衡,由LVS连接从库 。
这样做的好处是LVS能自动感知从库是否可用,从库DB2宕机后,LVS将不会把读数据请求再发向DB2 。
同时DBA需要增减从库节点时,只需独立操作LVS即可,不再需要项目组更新配置文件,重启服务器来配合 。
再来看主库高可用结构图:
高频交易支付架构并不复杂


推荐阅读