寫在開頭,好多人都問我在支付寶是如何處理高并發(fā)的务豺。哎~~~~磨总,那我今天就說一下,我是怎么解決每天上百萬的還款的笼沥,順便介紹一下高并發(fā)的思路蚪燕。
首先,先看一個圖(看不懂沒關(guān)系奔浅,稍后我會解釋)馆纳。
首先,介紹下汹桦,我在支付寶主要負責開發(fā)信貸核心系統(tǒng)鲁驶,它是網(wǎng)商銀行的核心系統(tǒng)(不是皮系統(tǒng)),通俗的說就是貸款和還款舞骆,當然實際上非常復雜钥弯,舉個例子,現(xiàn)在國內(nèi)銀行的信貸系統(tǒng)都是從IBM或者oracle買的(開發(fā)太難了)督禽,而網(wǎng)商這邊開發(fā)了自己的一套系統(tǒng)寿羞。
有一天,有這么一個大需求:資金驅(qū)動赂蠢。我來解釋一下,首先辨泳,如果用戶被打上了逾期或者強制扣款標志虱岂,那么,如果你的支付寶賬號有money菠红,支付寶受托系統(tǒng)就會給我發(fā)消息第岖,然后我處理,最后調(diào)用支付寶受托扣款试溯。這是為什么蔑滓,因為怕用戶還不上錢或者不還錢,不然你想想貸出去的錢都收不回來,馬爸爸不哭暈在廁所~~~~
言歸正傳键袱,這個需求看起來特別的清楚明了燎窘,但是一般惡心都看起來簡單(以往的編程經(jīng)驗)。
看到這個蹄咖,我于是想到了這個處理辦法:
1褐健、接收消息
2、消息落地
3澜汤、處理消息蚜迅,掉支付寶受托接口。
ok俊抵,思路清晰谁不,并且完成了。
~~~~~~~~~~~~~~~~~~~~~~~沾沾自喜徽诲,當自測的時候刹帕,哭暈在廁所,媽的馏段,處理消息的時候簡直太慢了轩拨,因為對于一個用戶,要找出他所有未結(jié)清的支用(借據(jù))院喜,每筆支用還包含支用賬單(分期賬單)亡蓉,需要先結(jié)息,計算每筆分期賬單的表內(nèi)喷舀,表外的正常利息砍濒,逾期利息,逾本罰硫麻,逾利罰爸邢,滯納金,還有寬限期處理拿愧。杠河。。浇辜。券敌。(業(yè)務極其復雜,學過會計學的就懂了)柳洋。造成的問題就是時間太長了待诅,一個事務時間非常長,再加上調(diào)支付寶受托接口的時間熊镣,太長了卑雁。而且網(wǎng)絡訪問放在了事務中募书,簡直是噩夢(原因很簡單:時間太長,事務可能會自動終止测蹲,連接不能及時歸還導致數(shù)據(jù)庫掛了)莹捡。
考慮到以上這個問題,我又想了一個方案:
1弛房、處理消息后將消息落地道盏。
2、起一個調(diào)度任務處理消息生成扣款指令文捶。
3荷逞、再起一個調(diào)度任務負責撈取扣款指令掉支付寶接口。
~~~~~~~~~~~~~~~~~~~~~~~~~so easy粹排,但是當調(diào)試的時候發(fā)現(xiàn)种远,我太天真,因為對于每個消息處理很慢(上面介紹過)顽耳,等我生成扣款指令坠敷,消息列表都他媽的都滿了,導致消息多的處理不過來射富,怎么解決膝迎?只有優(yōu)化。
換個思路胰耗,既然消息堆積限次,能不能快速處理消息,當然可以柴灯,但是又不能真的去處理消息生成扣款指令卖漫,因為太耗時間了,哎~~~~~~
我可不可以這樣羊始,在處理消息的時候只是生成相應的記錄突委,也就是生成那個用戶要被扣錢了,并不是真的去處理它忙灼,然后起一個調(diào)度器去處理他酸舍。
于是方案變成了:
1忽舟、接受消息叮阅。
2、消息落地
3、起一個調(diào)度器處理消息生成用戶扣款記錄
4、起一個調(diào)度器處理用戶扣款記錄生成扣款指令
5弊决、起一個調(diào)度器處理扣款指令調(diào)用支付寶受托
~~~~~~~~~~~~~~~~~~~~~~~~~~~完美先改!哈哈貌嫡,終于通過了測試可以上線了狈茉。線上運行了一段時間蹭秋,沒有什么問題羽莺,好開心。直到有一天的來臨礁哄,大促6崞ⅰ\约獭_职取!K附摺7撇纭!E伤骸M竦!媽的终吼,大促的時候镀赌,隨著用戶的購買,賣家的收錢际跪,賬上會不斷有資金流入商佛,不停地產(chǎn)生資金驅(qū)動消息,而且都是小批量的姆打,數(shù)量太大了良姆,上千萬條,簡直崩潰了幔戏÷曜罚看來還是需要優(yōu)化!
進一步處理闲延,在觀察大促的時候發(fā)現(xiàn)痊剖,單位時間內(nèi)伯复,一個商戶會不停地有少量資金的不斷流入,但是觀察發(fā)現(xiàn)邢笙,其實數(shù)據(jù)都是一樣的,簡單都說無非是我收了1塊錢侍匙,2塊錢氮惯。只有額度不同,其他的都一樣想暗。那么我可以把它們都看成重復記錄妇汗。哇塞,可以這么處理说莫。我可以去重杨箭,然后生成還款指令。
繼續(xù)優(yōu)化方案:加油4⑾痢;バ觥!
1辽狈、接受消息慈参,并消息落地。
2刮萌、處理消息生產(chǎn)前置還款指令(aegis_repay_pretreat)
3驮配、因為前置還款指令會有大量的重復記錄,去重然后產(chǎn)生還款命令(aeghis_repay_cmd)着茸,同時刪去aegis_repay_pretreat中的記錄壮锻。
4、處理還款命令生產(chǎn)扣款命令涮阔。
5猜绣、處理扣款命令,調(diào)用支付寶受托澎语。
大概需要4個定時任務調(diào)度途事。終于解決了問題了,也處理高并發(fā)場景擅羞,太爽了################################
真的沒問題了么尸变??在測試的時候發(fā)現(xiàn)减俏,由于調(diào)度器會不停地搶鎖召烂,掃表,oracle吃不消巴蕹小W喾颉E屡瘛!P镏纭@任健!B橄鳌U舯浴!呛哟!
進一步優(yōu)化叠荠,一般用的調(diào)度任務,除了Linux的crontab扫责,再就是spring的quartz調(diào)度榛鼎,當然支付寶有比較牛逼的分布式調(diào)度中心。但是鑒于優(yōu)化訪問oracle的性能鳖孤,選用自己搞quartz者娱。
開始了:
假設(shè)我有10臺機器,他們都需要從同一個表中撈取數(shù)據(jù)淌铐,那么需要加鎖肺然,然后更改任務狀態(tài),別人就撈不到了腿准。但是如果不停地撈取际起,頻繁掃表,數(shù)據(jù)庫性能極具下降吐葱,但是這種定時任務都是每時每刻在執(zhí)行街望,如何解決?
首先做一個阻塞隊列arrayBlockingQueue弟跑,然后先加鎖然后撈數(shù)據(jù)灾前,搶不到鎖就sleep幾秒,然后對撈取的數(shù)據(jù)判斷孟辑,如果沒有撈到哎甲,那么說明此時表中并沒有多少數(shù)據(jù),那可以再sleep饲嗽,看起來沒什么問題炭玫。
可不可以進一步優(yōu)化,因為我們知道一般用到線程池貌虾,都需要關(guān)注他的coreThreadCount吞加,maxThreadCount,隊列長度。具體處理是當前線程數(shù)量小于coreThreadCount衔憨,則會創(chuàng)建線程叶圃,如果大于,則加入隊列践图,如果放不進去創(chuàng)建線程掺冠,如果大于maxThreadCount拋異常。
所以码党,可以看到赫舒,如果線程比較忙的時候,阻塞隊列數(shù)量會很多闽瓢,線程壓力會比較大。那么可以這樣心赶,每次處理判斷下隊列的長度扣讼,如果大于饑餓值就sleep,降低線程壓力缨叫。
還能不能再進一步優(yōu)化椭符?
既然所有的處理都圍繞著隊列長度,撈取的數(shù)量耻姥,可不可以計算下?lián)迫〉臄?shù)量销钝,也就是因為每次撈取隊列當前容量size - 隊列當前長度length 的指令數(shù)量,那么琐簇,如果說撈取后蒸健,發(fā)現(xiàn)隊列中的長度還是低于整個隊列容量的1/3是不是表示此時表中并沒有多少數(shù)據(jù),仍然可以sleep以此降低掃表頻率婉商。
get it
------------------------------------------------------------------------
經(jīng)過一番折騰似忧,終于解決了!這里留下一個問題:如何解決搶鎖頻繁丈秩,更改狀態(tài)會在機器重啟或者發(fā)布時卡主指令如何處理盯捌?留給大家討論下,歡迎交流蘑秽。發(fā)完了之后饺著,有些小伙伴問我,為啥處理這么復雜肠牲,在這里說一下幼衰,一般用戶還款大概峰值是幾百萬比,但是在大促的時候洪峰大概上億比埂材。所以只能這樣處理塑顺。