大厂的支付系统设计高可用设计

关注我,紧跟本系列专栏文章,咱们下篇再续!

作者简介:魔都技术专家兼架构,多家大厂后端一线研发经验,各大技术社区头部专家博主,编程严选网创始人。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。

负责交易系统(提单、支付)及基础系统(API网关、定位、地址)等核心架构设计及开发,通过深入业务,构建合理业务架构。目前主攻降低软件复杂性设计、构建高可用系统方向。

0 前言

线下现金交易,可能抹个零头、少几毛几块都问题不大,但平台上的准确性、一致性,是支付系统的首要指标。

互联网公司,“快”是核心必要指标,特别是以实时性需求的o2o(Online To Offline)电商,整个订单生命周期不到1h,若支付延迟几min,履约质量就会下降,甚至劝退用户!

所以架构整体层面上,在保证系统的安全稳定性同时,要尽可能保证系统高性能运转,解决互联网的“快”和金融行业的“稳”的矛盾。

大纲

  • 一、支付平台整体能力介绍
  • 二、如何安全保障金额正确性
  • 三、高可用架构的一些实践及思路

1 支付平台整体能力介绍

1.1 支付平台的整体架构

支付系统功能架构图:

平台系统,应具备:产品通用能力、个性化可配置。目前接入支付平台有几十条业务线,根据不同业务形态,提供支付产品:

  • 收银台(具备多种支付能力)
  • 直连支付(直接唤醒支付场景)
  • 代扣服务(周期性扣款)
  • 代付(好友代付等)
  • 协议支付(便快捷支付、免密支付等)

支付系统核心功能:支付和退款。

如预售场景,为业务线提供定金支付这种2阶段支付能力(如定金支付);退款主要是全款、部分退款、以及提供人工退款服务。

提供支付营销能力,进一步提升支付转化,如根据用户所属区域进行支付引流,支付券产品、免息产品、满减等。

在开发定金、和营销产品之前,支付系统被设计为一个订单,在产品形态上很单一,仅认为订单为固定金额,因此开发这两个需求的时候改动大。

目前主流支付能力大概有多阶段支付、大额支付场景、组合支付、支付参与营销等能力,所以有开发支付系统需求的同学,在最开始最好提前预留一些设计。

接入所有主流支付方式(微信支付、支付宝、京东支付、京东白条等),尤其是现在各类小程序当道,各业务线都需要支持在不同小程序中发展业务,但受平台支付方式限制,就要接入更多支付方式,例如百度支付、头条支付等,都要接入。

当业务被嵌入到各种各样的流量渠道入口,我们也要根据不同的渠道支持不同的支付方式,如App支付、H5支付、刷脸支付等。

2 支付能力的快速接入

支付快速接入:

设计流程主要目标:屏蔽接入第三方支付平台的复杂度,为业务提供便捷接入的支付的能力。

整体交互逻辑:用户下单后,业务线生成生订单的同时请求支付系统,返回携带加密后的收银台链接,业务前端渲染收银台H5链接,之后用户操作都直接与支付系统直接交互,不再经过业务线。

支付渠道-接入微信支付

左上是收银台(支付页),包括订单基本信息、随机减活动和微信引流活动等。

右上是支付平台和微信交互逻辑。

3 配置化支付方式

最小粒度是支持按不同渠道配置,如最核心的商超业务渠道有几十个,根据不同终端适当屏蔽风控能力减弱的支付方式,或在某些特别终端按业务要求配置指定支付方式,每种相同的支付方式,根据具体的业务线或具体的业务配置不同的商户号,不同的商户号在第三方平台的费率,收款账户都不同。

4 支付单的生命周期

一笔支付单的生命周期:

多端支付场景

第一: 由于我们无法感知用户唤醒sdk后的操作。

所以不能限制用户的支付行为,一个订单可从多个手机多个渠道使用相同或不同支付方式、不同的人同时对一笔进行支付。

第二:支付、退款跨越多个端。

支付跨越多机构,就会经过支付平台、第三支付平台、银行等。支付和退款是天然异步场景,这是不可抗因素。

5 如何安全保证金额正确性

交易金额的强一致性保障设计

台账信息记录4个金额字段:应收、实收、应退、实退。收到支付通知时,会在收到支付通知的时候进行一次对账。

判断:应收+应退=实收+实退

保持该等式,即可正确计算每笔次金额变动。虽然使用一个简单公式来保障多端并发下金额修改的正确性,但由于金额频繁改动,是否可能出现逻辑bug?

若给用户少退了,第一时间即可得到用户的反馈,及时修正bug,并补偿给用户;但如果给用户多退一些钱,很可能用户不会产生反馈,我们自己也没发现。

所以系统底线保障是:

确保不会产生多退款

实收的钱应≥应收的钱。

通过不变量和不需要加工的数据来验证变量

要通过啥手段,保证实收≥应收?尽可能通过不变量和无需加工的数据来验证变量:

  • 不变量:订单金额

  • 无需加工的数据:业务申请的退款。业务一旦申请退款,校验通过就会插入DB。这里是业务产生的退款,退款可能细分人工退款、多支付退款等,但这些都不用关心。

我们要关心:**订单金额-业务产生的退款,**即至少收到多少钱,若和实收相等,则认为没问题。

如何保证实收就是正确的?

继续使用实收和支付平台对账,就可进一步确保实收没问题,需对账每一笔正向支付交易和逆向支付交易产生的金额记录,且对账至少需要2种机制来相互保障。

6 高可用架构实践及思路

引用:软件在本质上是复杂的,软件本身的复杂性在于除了要解决问题域,还要解决非功能性需求和软件域特有问题:安全性、可用性、可维护性、可扩展性、性能、一致性、容错性、稳定性、可重用性、幂等、兼容等等。

6.1 分类

三部分:

image-20240205151612307

6.2 实时性保障

支付是一个不可抗拒的跨端异步场景,还要抵抗网络带来的不确定因素。一笔银行转账,大家在心里有预期,即使实时转账,大家也会自觉等待一段时间。

但对在线支付,用户支付完后,用户很理所当然的想到应该看到订单是已支付状态,而非待支付状态,延迟增加到一定时长,客服就找到研发。

线上场景特征

  • **配送时效性。**一笔订单生命周期就不到1小时,所以在支付上我们不能延迟,不能像银行转账这么慢
  • **高并发。**银行等金融机构有钱,活动力度不亚于一些商品秒杀场景
  • **营销限量。**若用户享受支付优惠,但最终由于支付通知的延迟、服务器负载较高的情况下未能成功处理支付通知,那么用户就会要求索赔营销优惠

线下

比线上更复杂:

  • **即时性。**大家去的线下商店,都支持收银台在线支付,所以在排队的情况下,就需要商家及时完成顾客支付请求
  • **网络环境不可控。**不同位置,网络信号存在不确定性
  • **群体性。**线上主要是平台和支付平台的网络交互,但线下还涉及商家,整个支付环节也没这么流畅

尽可能快的让订单支付完成或在某种支付有问题时,第一时间下线这种支付方式。

过去做法是通过暴力从DB反查待支付订单,但对DB压力较大,还得单独写个任务表,后改写为

基于事件通知机制

使用MQ作为事件管道,但由于不同场景触发的反向查询时机不同,不能对所有对待支付订单进行无差别对待,因此就受限MQ特性。目前不支持个性化延迟消费消息,对此策略是申请多个队列,并按不同延迟level入队。

查询补偿设计

反向查询主要场景

① 支付唤醒

由于用户需要输入密码,我们考虑到需要用户参与,进行多次间隔3秒到重试之后,如果还没支付结果则放到更大时间间隔到重试leave中。

② 协议支付、代扣类

用户无需输入密码,所以我们选择更低延迟到消费队列、或无延迟队列。

③ 订单取消

由于反向查询在一定的次数之后会放弃,不然会很占用资源;但如果一笔订单取消了,那么也有可能会因为支付延迟导致订单取消,所以我们就会最后查询一次。

④ 支付通知

同步进行查询结果,主要为防止伪造通知,但增加了一次外网交互,超时可能性很大,伪造通知是极端场景。所以在超时之后会暂时信任本次通知,继续交给反查队列,继续对这笔通知进行验证。

6.3 应用部署隔离

高可用部署的一个架构,划分维度 2C,2B:

根据2C和2B业务请求,对服务器部署上做资源倾斜。确保业务互不影响。

2C一般正向交易场景,RT要求高;2B场景对时效基本没要求,某些业务场景下,会存在集中性大批量退款申请、退款流程的事务也比较大,ToB就针对一些任务worker更消耗CPU。

目标是尽量避免非业务耗时导致的RT升高,而导致RT升高因素有:

  • 池化资源不够(http请求线程、rpc处理线程、数据库线程、以及http连接等)
  • CPU资源抢占
  • GC 导致的业务线程等待
  • ...

6.4 多级本地缓存

商品支付的营销需求。参与商品不到百万级,但调用量大,峰值调用量超10万QPS、RT苛刻5ms。

但也由于这是刚新起的业务,产生的业务价值收益有不确定性,所以没打算通过机器去抗量,所以我们把业务请求直接请求到Redis。

但是redis需要较多副本才能扛超高并发,避免大量无效请求。并且增加内存基本的缓存,使用布隆过滤器(Bloom Filter),仍然会把cpu打高,通过门店的过滤把cpu降到最低,所以我们最后会通过caffeine来做热点sku缓存。

Redis的利用率已达97%,这完成是布隆过滤器来决定的,效果明显!

6.5 监控

某晚,手机一阵震动,打开报警一看,报警很明显。红色框是垂直类-支付渠道层的报警,而且都是apple Pay导致的报警,大概率影响支付方式apple Pay。

绿色框是水平维度监控,显示我们的影响功能:支付、查询。同理报警没有显示的业务线,就跟这些具体业务场景没关系。

所以,做平台,监控非常重要的,:“没有度量就没有管理”。

跑在线上生产环境中的每一个服务,也需要管理,我们需要管理它们的运行情况,所以就需要我们建立的完整指标反馈监控系统。

① 机器层面的监控

  • 机器维度:系统指标:cpu、负载、内存
  • 网络指标:tcp连接数、丢包数、tcp重传
  • 磁盘指标:磁盘使用率和磁盘繁忙度
  • 容器指标:关注线程数
  • 应用:软件异常

② 应用层面的监控

  • 系统异常:基础组建异常(数据库、Redis)、RPC异常
  • 业务异常:业务异常的捕获主要是为了捕获业务线的一些非法出入参。
  • 非预期逻辑:主要一些没想到的一些逻辑场景。通过自定义监控。
  • Bug(Jex接入):上线一阵时间后。大家可以去搜搜excpetion、error关键词。总有意想不到的收获。

③ 业务监控维度

作为平台类系统,最重要的是结合水平维度+垂直维度划分系统报警情况:

  • 水平维度:支付、退款、营销、通知
  • 垂直维度:业务方、渠道平台

FAQ

延迟队列使用什么框架实现的?

京东的JMQ按照队列进行消费延迟。RocketMQ支持消息级的延迟。

如何避免单重复支付呢?或者避免重复退款呢?

支付是一个不可抗拒的多支付场景。在接收到支付通知的时候,要做对账,如果是多支付,就进行退款。

商品价格类型是用float还是decimal?

支付系统最好使用bigDecimal,因为和支付平台交互单位都是元。其他交易系统尽可能使用long类型,分作为单位。

个阶段对账,如果有差错,怎么处理?

程序设计越复杂,bug的可能性就会越多,所以要尽可能通过一些不变对量和不可修改的数据进行底线保障,至少需要2种金额校验相互保障。

如果应收50,A付款30,B付款40 你这个情况咋退款呢?退款给谁呢?

不会出现。支付金额至少是50,即便多支付,也是100 或者150。

sku的缓存怎么做的,说到是基于Redis的,那数据库和缓存的同步呢?比如下单后扣减库存呢?或更新呢?

整体是基于redis的。但是redis需要较多的副本才能扛超高并发,避免了大量无效请求。增加了内存基本的缓存,使用了布隆过滤器,但是仍然会把cpu打高,通过门店的过滤把cpu降到最低,最后通过caffeine来做热点sku缓存。

2024系统设计面试指南 文章被收录于专栏

面向 2024 校招/社招全网最新最全的系统设计面试。八年开发经验,毕业四年成为技术专家兼架构师,乐于知识分享,擅长图文讲解各种软件技术! 现如今,牛客网人均某马点评,但本质都是系统设计考量点,如: 1.多级缓存设计,如何保证缓存跟数据库的一致性? 2.设计模式,各种业务流程,到底何时何地使用何种模式? 3.玩转分布式框架 ... 更多技术重难点设计,尽在本专栏!

全部评论
老哥图片挂了,能补充一下吗
点赞 回复 分享
发布于 2024-06-21 14:17 四川

相关推荐

核心本地商业-业务研发平台4月9日1:面试官自我介绍+自我介绍2:Spring源码讲一下3:Spring源码学完什么收获4:spring的事务了解吗?,mysql的事务呢?5:spring的事务和mysql的事务有什么联系?6:项目jdk什么版本?7:默认垃圾回收器是什么?8:介绍一下ZGC和G1?9:项目性能调优怎么做的?垃圾回收器参数,jvm参数,10:Seata分布式事务?11:其他分布式事务了解吗?(模模糊糊说了MQ)12:AT和TCC说一下?13:看过12306官方的实现吗?14:既然你看过一些实现,那么你想怎么优化呢?(答了锁粒度方案,和redis方案)15:redis方案的一致性怎么解决?(答了cannal,MQ)16:redis网络波动,用于以为下单失败,但是其实redis收到消息怎么办?(当时说的MQ,但是面试官想说的不是这个,后面复盘应该是本地消息表+定时任务)17: 项目数据库方面有什么优化?(联合索引,explan,慢查询日志,skywalking)18:加完索引,前端需要注意什么?19:数据库的什么字段适合加索引 ,什么字段不适合? 20:Hashmap源码看过吗21:Hashmap多个线程put有什么问题?(值覆盖,极端情况下数组越界)22:值覆盖举个例子?23:极端情况下数组越界什么意思?24:还有其他的情况吗?(最后答了链表和红黑树可能会空指针异常)25:多线程用什么替代?(concurentHashmap)26:concurentHashmap1.7和1.8区别?27:concurentHashmap1.7为什么用了CAS?28:concurentHashmap1.8怎么实现?(乐观和悲观两种情况)29:1.8里的volatile是什么?30:volatile能解决线程安全问题吗?(不能)31:那能解决什么问题?32:SQL题,单表topk,审错题意了,写了快20分钟用子查询才写出来,然后面试官说有没有其他方法,又写了10分钟33:大模型相关问题,agent,Transfomer34:反问全称1小时40分钟,汗流浃背,后面写SQL的时候脑子已经不转了。二面4月15日1:深挖项目(20分钟)2:Redission怎么实现的?3:为什么不用redis的setnx?4:数据库分库分表,分库分表会有什么问题?(说了一下怎么分库分表,有什么问题没打上来)5:某个服务器cpu飙高怎么排查(top,htop,ps, jstake, dump文件,visualVM)6:优化sql?(skywalking,慢查询日志,索引,elplain的字段)7:深度分页优化?(业务上不允许深度分页,子查询,索引查询,游标分页)反问二面完,感觉g了,面试官说项目没选好,mysql没有分库,不太像分布式项目。
这名字响亮不响亮:我去,spring是你简历写的熟悉源码嘛,还是面试官直接问的嗷。好可怕
点赞 评论 收藏
分享
近期我看到很多同学私信我要TEG面经,一个个发比较麻烦,我比较懒,就统一发出来吧,哈哈。问的很多问题是和我简历上写的东西,所以具体问的内容还是得看简历,每个人可能都不太一样,简历上的东西大家一定要过熟。一面(1h):1. 自我介绍2. ThreadLocal底层原理3. ThreadLocal什么时候会发生内存泄漏4. ThreadLocal怎么清理5. 用过ThreadLocal吗,什么场景6. 上下文可以传递的ThreadLocal怎么设计的7. 设计有参考一些类似的开源的框架吗8. Java内存泄漏怎么排查9. 看到了数据区大小之后进一步要怎么做10. 哪些情况容易造成内存泄漏11. 缓存没有及时的清理,比如使用一个Map进行本地缓存,然后对于过期对象没有及时清理,我在项目中使用的Caffeine其中一个主要目的就是它能够自动清理过期的数据12. 资源或者连接没有及时关闭13. 使用集合装对象的时候,对象没有重写hashCode和equals导致无法移除对象14. 数据库事务隔离级别15. 各种隔离级别的特点16. MySQL如何进行数据同步17. binlog存了什么东西18. JVM判断对象是垃圾的方法19. JVM内存模型20. 为什么要用Dubbo21. Dubbo和Http的调用底层区别    1. Dubbo支持长连接复用    2. Dubbo的数据格式更加紧凑    3. Dubbo的数据序列化方式更加高效22. Dubbo怎么基于Netty实现同步和异步连接的转化?    1. 把Netty的NIO事件驱动模型和业务线程池解耦    2. 通过CompleteableFuture进行接口回调管理异步结果23. 有没有测试Dubbo和Feign进行性能测试    1. 本地测试差了大概十倍24. 分库分表怎么做的25. 分表数量和原因26. 怎么用用户id查订单不发生全路由27. 如果想通过商品查订单要怎么查28. 多表情况怎么保证一致性29. Redis的持久化机制30. 预热缓存是什么31. 余票信息是保存在哪里的32. 如何保证票不超卖33. 限流是用的什么方案34. 能实习什么时候到35. 算法:LRU36. 反问业务37. 反问实习生品质二面(两个面试官,混合双打1h):1. 自我介绍2. 基因法是做什么的3. 如何根据时间去查询订单4. 布隆过滤器相关    - 与位图的区别    - 长度和hash函数的设置方法5. Elasticsearch相关    - 存储的信息内容    - 深分页问题    - 延时可能的原因6. 缓存使用    - 缓存使用方式    - 本地缓存和Redis的一致性    - 误判如何处理    - 本地缓存内存占用值7. Redisson相关    - 选择原因    - 看门狗自动续期失败情况    - 看门狗时间设置不合理的影响8. 消息队列    - 异步生成订单的使用    - 消息消费失败的解决方案9. 限流实现    - 限流算法实现方式    - 区分正常请求和恶意请求10. 并发问题验证情况11. 大模型相关    - 了解大模型的影响    - 大模型的技术12. 学生会经历    - 参加收获    - 分工不明确的处理方式13. 代码评审14. 后端其他框架了解15. 对大模型发展的看法和焦虑16. 场景题:设计微博信息流三面(30min,又问微博,不知道是巧合还是故意的):1. 自我介绍2. 负载均衡的策略3. 哈希路由(优化)4. 一致性协议5. 文件拷贝技术6. 项目中的技术难点7. TCP和UDP的区别8. 应用层要怎么对UDP进行优化9. 场景题:基于UDP的语音通话,丢包怎么兜底10. 场景题:设计微博Feed流11. RPC框架的高性能实现12. 最近比较感兴趣的点是什么13. 写代码的时候少出bug或者是性能保证14. 学习新技术的方式HR面(20min):1. 升学规划2. 介绍有比较挑战性的任务3. 自己给项目打分4. 还有值得哪些改进的地方5. 你对于本部门要做的东西了解吗6. 过往的项目对于实习有什么帮助7. 过往有没有挑战性的目标8. 介绍:围绕code进行开发、八月底/九月初进行答辩9. 其他公司的面试情况10. 反问:技术栈c++、java
点赞 评论 收藏
分享
评论
1
13
分享

创作者周榜

更多
牛客网
牛客企业服务