支付宝是如何处理热点账户高并发转账问题的

前言:方案设计前提

一般账务系统对账户的冲扣需要满足以下两点

1:更新账户表中的账户余额。

2:记录账户明细表中的账户更新前余额,账户更新后余额,操作金额。

其中对账户表中的余额更新一般是直接update,对账户明细表中的操作前金额,操作后金额和操作金额就是对账户表update的记录

1:为什么做热点账户设计

热点账户交易是性能瓶颈,在银行或者第三方支付系统的账务数据库的处理中,数据从一个账户转出,或者有数据转入一个账户,账户都会收到记账请求,并都有一个记账处理的过程。记账处理过程主要包括两部分,一是记录记账凭证,二是更新账户的余额。为了保证账户不被其他请求影响数据的准确性,在进行记账处理时,会先对账户的资源加锁,记账处理完毕后会自动释放锁。随着账务处理业务量的增大,账务数据库中的账户常常会在瞬间产生多个并发操作,但所有对应的并发线程中只有一个线程能够持有当前账户的资源锁,其他线程必须等待该锁被释放后再逐一进行记账处理,这样该账户将会被频繁加锁释锁,使该账户成为账务数据库热点,产生性能瓶颈点,严重影响账务数据库的性能。

image

对于同一账户ID来说,由于实际业务需要更新账户可用余额和账户流水日志,所以单笔冲扣功能是在一个事物中进行操作,任何更新操作都会对数据上行锁,图例如下

image

2:业界关于热点账户冲扣设计方案

1.并发度控制

同一时刻,对同一账户修改的请求数越多,这个账户的锁等待问题就越严重,所谓并发度控制就是要控制同一时刻对热点账户请求的数量,可以通过控制上游支付系统并发请求数据或者账务系统处理的并发请求数来实现。这一方案的缺点是对业务是有损的,当热点账户出现的时候,支付或者账务处理失败率会增加,用户的体验会变差,较大的银行或者第三方支付公司用地比较少。

image

2.汇总明细记账

实时的交易全部是insert账务明细(insert的开销很小,能够支持高并发。如果基于分布式部署,insert的并发容量理论上可以无限大),然后定时(比如每半个小时)将之前半个小时内的账务明细sum出一个结算总金额,一笔入账结算到指定账户。这个方案的缺点就是:交易不能实时入账,其实如果控制好定时汇总入账的频度,比如分钟级,用户也是可以接受的。这种方式对收单类业务(账户加钱)非常实用,但是对支出类业务(账户减钱)类来说,有账户透支地风险。

image

3.缓冲入账

将实时同步的记账请求进行异步化,以达到记账实时性和系统稳定性之间平衡的记账手段,这就是”削峰填谷“。详细地讲,假如账务系统对同一个账户的处理阈值为100笔/s,24小时不间断服务(一天能处理86400000笔)。当业务高峰期来临的时候,热点账务的请求数会达到200笔/s。当账户的交易低于100笔/秒的时候,账务系统几乎还是实时地处理了记账请求,而当交易大于100笔/秒的时候,账务系统先返回结果,把账务处理丢到可靠的处理队列中,等并发量不大的时候慢慢消化,对用户来说感受到的体验还是很快就记账成功了。   这个方案是有个前提是:热点账户在某几个高峰时间点需要缓冲记账来削峰填谷,并且能在日间填完。一旦账户的日间交易量暴增,导致日间队列根本来不及消化,整个队列越来越长,那就不存在谷可以填,这时候肯定会带来用户大量的投诉。另外这种方案对支出类业务(账户减钱)来讲,也会有账户透支地风险

image

4.子账户拆分

具体来讲就是创建与热点账户对应的多个影子账户,所述影子账户与所述账户的数据结构相同,将所述影子账户设置为隐藏,并将所述账户的余额分散至各个影子账户。当账务系统接收到账务请求的时候,通过前置进行hash分配(具体的hash函数会有更多方案)选择影子账户进行记账,这样就将原来对一个账户的请求分散到多个影子账户中,分散了账务热点。   这个方案也有缺点:通过算法选择的影子账户扣款,影子账户的余额可能是不足的,但账户的总余额是够的,这样可能影响账务处理的成功率。

image

5.内存数据库+缓存入账

提高单台数据库服务器处理能力(I/O,CPU,memory)或者选取内存数据库实时地处理记账请求,然后异步地存储到可靠数据库上。

image

6.升级服务硬件,对CPU内存等进行升级

image

3:几种方案的对比

1:并发度控制

对单个账户并发操作进行限流降级控制,使得系统健康的完成入账出账操作,但是在并发很高的情况下还是会杀死很多正常的冲扣功能,会极大的提高冲扣的失败率,所以对我们账务系统来说不是允许的。

2:汇总明细入账

对账户的冲扣操作已流水的形式记录下来,通过定时job来将出入账流水更新到业务表中。这种做法对于频繁的入账来说性能提高明显,但是因为没有对总金额进行校验,对支出类业务(账户减钱)类来说,有账户透支地风险。并且对于金额的校验需要通过流水数据和当前可用余额来判定,有并发问题,计算很难准确。并且我们实际线上业务是【频繁出账,低频入账】,所以此办法不可取。

3:缓冲入账

需要动态判断流量低峰高峰,维护请求队列,有账户透支地风险,并且异步请求中结果不可控。

4:子账户拆分

子账户拆分方案中对于子账户的扣款进行负载,可以满足对同一账户的高频访问负载到其子账户上,极大满足了并发的需求,子账户的余额可能是不足的,但账户的总余额是够的,这样可能影响账务处理的成功率,并且处理对子账户的扣款和入账来说需要做到金额相对平均比较复杂,对记录账户期初余额期末余额处理涉及到并发,相对复杂。

5:增加硬件处理能力CPU.内存等

备选方案,无法从根本上解决单点账户的并发压力。

6:内存数据库实时地处理记账,异步入库

使用redis做数据前置处理,将数据库中的热点账户金额初始同步到redis中,然后将操作记录流水,通过job定时任务刷新流水到业务表。这样将db和缓存分开极大的加大了并发性能,但是却衍生出来一个问题如下

image

假设redis初始金额为100,

(1) 当线程1对redis账户金额进行原子减操作时,剩余金额40,并记录流水表等待异步入账

(2) 当线程2对redis账户金额进行原子减操作时,剩余金额-20,此时金额已经为负,按照业务要求金额不能为负所以必须要做反向操作

(3) 当线程2还没有对redis余额进行反向操作维护的时候又出现线程3进行充值操作,此时金额又变成-20+100=80,已经出现金额混乱,对业务要求的期初余额期末余额无法准确的满足,所以对redis的金额进行同时冲扣会带来余额的并发问题。

但是对缓存进行操作和延迟批量流水入账可以极大的满足我们对性能的需求,所以在【2.汇总明细记账】和【5.内存数据库+缓存入账】的基础上进行改良来满足对我们的业务需求

4:详细方案设计

方案设计前提:

(1):【对账户的余额的更新】:准确的更新账户余额,不允许出现多扣,少扣等情况。

(2):【对账户操作记录的更新】:准确的记录账户流水表中期初余额,期末余额,操作金额等情况,不允许出现任何的金额错误发生。

前期准备:

(1) :新增延迟入账【流水表】,新增入账,出账数据先入【流水表】,通过定时任务将【流水表】入账和出账数据同步到业务数据表中,并且负责新增入账数据的缓存同步工作。

下面的方案会对此表统一称为【流水表】

image

(2) :新增【redis】数据结构【SortedSet(有序集合)】 key为【hotspot_account】

下面会对这个数据集合称为【缓存操作记录】

image

其中score为当前账户操作时间【新覆盖旧】,member为出入账的账户ID。key【hotspot_account】,所有账户的入账出账操作需要记录到hotspot_account中,主要是提供给【图1中定时任务】获取所有账户流水ID。

(3)新增【redis】数据结构【SortedSet(有序集合)】 key为【hotspot_account_currentbalance】

下面会对这个数据集合称为【缓存余额】

image

其中:

score为当前账户可用余额,【热点账户新操作流程之前需要将数据库中热点账户的数据同步到hotspot_account_currentbalance中】

member为账户ID

到此,前期准备工作已经全部结束。

当账户金额充值新增时:

1:记录redis操作记录【hotspot_account】

image

如图所示红色数据部分,当账户110000056666660010入账时,插入或更新数据,member=110000056666660010,score为当前时间戳(秒)。

ps:操作指令【ZINCRBY key increment member】,当 key 不存在,或 member 不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。

2:新增【流水表】,设置入账状态为未入账

image

当账户金额扣减时:

1:同金额充值相同首先记录redis操作记录【hotspot_account】。

2:直接对缓存hotspot_account_currentbalance对应的金额进行扣减。

image

3:定时任务

定时任务的作用是将流水表的数据更新到【账户表】,和【流水明细表】,并且设置【流水表中】数据已入账,同时要将新入账数据流水到更新【hotspot_account_currentbalance】中的可用账户余额,让扣减操作得以继续进行。以下操作流程:

image

修改于2019年10月24日:

在第15步的时候目前版本在极端情况下会出现一个问题,当定时任务发现了当前缓存余额<0时,会再从流水表中把数据重新查询一遍入账,这里有一个小小的问题,在查询过程中可能会有部分线程没有入账到数据库,这样会出现超扣的问题,也就是说线程1在

扣减redis的时候成功了,但是没有insert到流水表,这个时候又有一个线程2扣减redis的线程扣负了,这个时候定时任务发现余额为负,不应该直接同步余额正确的做法是加锁等待扣减操作流程执行完毕,由于加锁的复杂性,所以这里采用一个简单的办法,由于扣减redis和mysql操作基本都是瞬时的,所以直接sleep 5 s即可,这个时间足够发生full gc等一些其他未知因素的耗时了,当然感兴趣的小伙伴也可以加锁去自己拓展实现,会有一定性能上的影响,最后感谢群里小伙伴@Token指出的问题。

image

加笔者微信mingyuan_2018,或者扫码加群,即可获取完整版pdf资料,以及更多技术方案设计+代码示例+ 面试资料+ 享受美团,阿里,头条内推福利 ↓↓↓


image.png

注意:想去其他互联网大厂的勿扰,目前只有美团,阿里,头条的内推通道

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349