当前位置:首页 > 问答 > 正文

聊聊Uber支付系统背后的那些分布式架构设计和坑

主要参考自Uber Engineering官方技术博客的多篇历史文章,特别是关于从单体架构到微服务拆分,以及构建可靠支付系统的部分)

Uber最早的支付系统非常简单,就是直接嵌在司机和乘客客户端里的几行代码,然后连接到一个巨大的单体后端应用上,这个单体应用什么都管,从下单、匹配司机、路线计算,一直到最后的支付扣款,这在公司规模很小、业务只在几个城市运营的时候是没问题的,反正交易量不大,系统也简单。

但Uber扩张得太快了,业务迅速蔓延到全球几百个城市,这时候,那个庞大的单体应用就变成了一个巨大的麻烦,想象一下,这个应用就像一间巨大的集体宿舍,所有部门的人都挤在里面工作,支付团队想改一下扣款的逻辑,可能不小心就把地图团队的代码搞坏了,导致整个系统崩溃,最要命的是,支付流程只是这个巨大应用里的一小段代码,每次支付功能需要更新或者扩容,都必须把整个庞大的应用重新部署一遍,风险高,速度慢。

Uber下决心把系统拆开,也就是所谓的“微服务化”,支付系统被从一个整体里剥离出来,成了一个独立的服务,这就好比把支付部门从那个大集体宿舍里请出来,给了它一间独立的办公室,它有自己的门,自己的电话线(网络接口),自己管理自己的事情,支付团队现在可以专注于开发支付功能,想升级就升级,想扩容就扩容,再也不用担心会影响到叫车或者地图这些其他服务了,这是他们架构设计上最关键的一步。

拆分成微服务只是第一步,后面跟着来了更多的“坑”,第一个大坑就是数据一致性,一个简单的支付流程,其实涉及很多步骤,乘客付款后,要扣乘客的钱,还要把钱加到司机的账户里,可能还要计算平台的手续费,在单体应用里,这些操作可以在一个数据库事务里完成,要么全部成功,要么全部失败,能保证数据是准确的。

但现在,扣款服务、司机账户服务、记账服务可能都是独立的微服务,各有各的数据库,怎么保证这些操作要么一起成功,要么一起失败呢?这就是个难题,Uber采用了一种叫“Saga模式”的方法来应对,简单说,就是把一个完整的大操作拆成一个个小步骤,然后按顺序执行,如果其中某一步失败了,就自动触发一系列补偿操作,把前面已经成功的步骤“撤销”掉,先扣款成功,但给司机加钱时失败了,系统就得自动执行一个“退款”操作,把钱退给乘客,虽然最终可能不是严格的实时一致,但能保证最终大家的账目都是对的。

第二个大坑是重复请求和消息处理,在分布式系统里,网络是不可靠的,支付服务可能因为网络超时,向银行的支付网关重复发送了同一个支付请求,如果银行没有很好的幂等性处理(就是无论请求多少次,结果都一样的机制),乘客就可能被重复扣款,这绝对是灾难性的,Uber的解决方案是生成一个唯一的ID来标识每一笔支付请求,即使同一个请求因为网络问题来了好几次,支付网关看到这个相同的ID,也只会处理一次,后面的直接忽略掉,这个看似简单的机制,对于保证金融系统的正确性至关重要。

第三个挑战是全球支付的复杂性,Uber在全球运营,每个国家的支付方式都千差万别,有的地方流行信用卡,有的地方用支付宝、微信支付,在印度可能流行UPI,在巴西可能流行某种本地信用卡分期,支付系统必须能够灵活地接入各种各样的本地支付方式,同时还要应对不同国家的金融法规和汇率波动,这要求他们的支付架构必须有非常好的可扩展性,能够像搭积木一样,方便地接入新的支付渠道。

还有一个持续的挑战是对账,即使系统设计得再完美,也难免会有极少数异常情况导致支付服务、银行、司机账户几边的数据对不上,Uber建立了一套非常强大的离线对账系统,这个系统就像是一个财务审计部门,每天会把所有支付相关的流水记录捞出来,跟银行提供的对账单、内部的交易记录进行比对,自动找出差异,然后报警或者尝试自动修复,这套系统是保证整个支付流程最终准确无误的最后一道防线。

Uber支付系统的演进,就是一个不断遇到问题、解决问题的过程,从最初简单的单体架构,到拆分成微服务以应对增长,再到解决分布式带来的数据一致、消息重复等新问题,最后还要处理全球业务的复杂性,每一个设计决策背后,几乎都是为了填上一个曾经遇到过的“坑”。

聊聊Uber支付系统背后的那些分布式架构设计和坑