写在开头,好多人都问我在支付宝是如何处理高并发的。哎~~~~,那我今天就说一下,我是怎么解决天天上百万的还款的,顺便介绍一下高并发的思路。spring
首先,先看一个图(看不懂不要紧,稍后我会解释)。数据库
首先,介绍下,我在支付宝主要负责开发信贷核心系统,它是网商银行的核心系统(不是皮系统),通俗的说就是贷款和还款,固然实际上很是复杂,举个例子,如今国内银行的信贷系统都是从IBM或者oracle买的(开发太难了),而网商这边开发了本身的一套系统。编程
有一天,有这么一个大需求:资金驱动。我来解释一下,首先,若是用户被打上了逾期或者强制扣款标志,那么,若是你的支付宝帐号有money,支付宝受托系统就会给我发消息,而后我处理,最后调用支付宝受托扣款。这是为何,由于怕用户还不上钱或者不还钱,否则你想一想贷出去的钱都收不回来,马爸爸不哭晕在厕所~~~~网络
言归正传,这个需求看起来特别的清楚明了,可是通常恶心都看起来简单(以往的编程经验)。并发
看到这个,我因而想到了这个处理办法:oracle
一、接收消息分布式
二、消息落地高并发
三、处理消息,掉支付宝受托接口。性能
ok,思路清晰,而且完成了。测试
~~~~~~~~~~~~~~~~~~~~~~~沾沾自喜,当自测的时候,哭晕在厕所,妈的,处理消息的时候简直太慢了,由于对于一个用户,要找出他全部未结清的支用(借据),每笔支用还包含支用帐单(分期帐单),须要先结息,计算每笔分期帐单的表内,表外的正常利息,逾期利息,逾本罚,逾利罚,滞纳金,还有宽限期处理。。。。。(业务极其复杂,学过会计学的就懂了)。形成的问题就是时间太长了,一个事务时间很是长,再加上调支付宝受托接口的时间,太长了。并且网络访问放在了事务中,简直是噩梦(缘由很简单:时间太长,事务可能会自动终止,链接不能及时归还致使数据库挂了)。
考虑到以上这个问题,我又想了一个方案:
一、处理消息后将消息落地。
二、起一个调度任务处理消息生成扣款指令。
三、再起一个调度任务负责捞取扣款指令掉支付宝接口。
~~~~~~~~~~~~~~~~~~~~~~~~~so easy,可是当调试的时候发现,我太天真,由于对于每一个消息处理很慢(上面介绍过),等我生成扣款指令,消息列表都他妈的都满了,致使消息多的处理不过来,怎么解决?只有优化。
换个思路,既然消息堆积,能不能快速处理消息,固然能够,可是又不能真的去处理消息生成扣款指令,由于太耗时间了,哎~~~~~~
我可不能够这样,在处理消息的时候只是生成相应的记录,也就是生成那个用户要被扣钱了,并非真的去处理它,而后起一个调度器去处理他。
因而方案变成了:
一、接受消息。
二、消息落地
三、起一个调度器处理消息生成用户扣款记录
四、起一个调度器处理用户扣款记录生成扣款指令
五、起一个调度器处理扣款指令调用支付宝受托
~~~~~~~~~~~~~~~~~~~~~~~~~~~完美!哈哈,终于经过了测试能够上线了。线上运行了一段时间,没有什么问题,好开心。直到有一天的来临,大促!!!!!!!!!!妈的,大促的时候,随着用户的购买,卖家的收钱,帐上会不断有资金流入,不停地产生资金驱动消息,并且都是小批量的,数量太大了,上千万条,简直崩溃了。看来仍是须要优化!
进一步处理,在观察大促的时候发现,单位时间内,一个商户会不停地有少许资金的不断流入,可是观察发现,其实数据都是同样的,简单都说无非是我收了1块钱,2块钱。只有额度不一样,其余的都同样。那么我能够把它们都当作重复记录。哇塞,能够这么处理。我能够去重,而后生成还款指令。
继续优化方案:加油!!!
一、接受消息,并消息落地。
二、处理消息生产前置还款指令(aegis_repay_pretreat)
三、由于前置还款指令会有大量的重复记录,去重而后产生还款命令(aeghis_repay_cmd),同时删去aegis_repay_pretreat中的记录。
四、处理还款命令生产扣款命令。
五、处理扣款命令,调用支付宝受托。
大概须要4个定时任务调度。终于解决了问题了,也处理高并发场景,太爽了################################
真的没问题了么??在测试的时候发现,因为调度器会不停地抢锁,扫表,oracle吃不消啊!!!!!!!!!!
进一步优化,通常用的调度任务,除了Linux的crontab,再就是spring的quartz调度,固然支付宝有比较牛逼的分布式调度中心。可是鉴于优化访问oracle的性能,选用本身搞quartz。
开始了:
假设我有10台机器,他们都须要从同一个表中捞取数据,那么须要加锁,而后更改任务状态,别人就捞不到了。可是若是不停地捞取,频繁扫表,数据库性能极具降低,可是这种定时任务都是每时每刻在执行,如何解决?
首先作一个阻塞队列arrayBlockingQueue,而后先加锁而后捞数据,抢不到锁就sleep几秒,而后对捞取的数据判断,若是没有捞到,那么说明此时表中并无多少数据,那能够再sleep,看起来没什么问题。
可不能够进一步优化,由于咱们知道通常用到线程池,都须要关注他的coreThreadCount,maxThreadCount,队列长度。具体处理是当前线程数量小于coreThreadCount,则会建立线程,若是大于,则加入队列,若是放不进去建立线程,若是大于maxThreadCount抛异常。
因此,能够看到,若是线程比较忙的时候,阻塞队列数量会不少,线程压力会比较大。那么能够这样,每次处理判断下队列的长度,若是大于饥饿值就sleep,下降线程压力。
还能不能再进一步优化?
既然全部的处理都围绕着队列长度,捞取的数量,可不能够计算下捞取的数量,也就是由于每次捞取队列当前容量size - 队列当前长度length 的指令数量,那么,若是说捞取后,发现队列中的长度仍是低于整个队列容量的1/3是否是表示此时表中并无多少数据,仍然能够sleep以此下降扫表频率。
get it
------------------------------------------------------------------------
通过一番折腾,终于解决了!这里留下一个问题:如何解决抢锁频繁,更改状态会在机器重启或者发布时卡主指令如何处理?留给你们讨论下,欢迎交流。发完了以后,有些小伙伴问我,为啥处理这么复杂,在这里说一下,通常用户还款大概峰值是几百万比,可是在大促的时候洪峰大概上亿比。因此只能这样处理。