對(duì)于互聯(lián)網(wǎng)應(yīng)用和企業(yè)大型應(yīng)用而言,多數(shù)都盡可能地要求做到7*24小時(shí)不間斷運(yùn)行绿鸣,而要做到完全的不間斷運(yùn)行可以說(shuō)“難于上青天”。
為此,對(duì)應(yīng)用的可用性程度一般衡量標(biāo)準(zhǔn)有三個(gè)9到五個(gè)9砸逊。
對(duì)于一個(gè)功能和數(shù)據(jù)量不斷增加的應(yīng)用,要保持比較高的可用性并非易事掌逛。為了實(shí)現(xiàn)高可用师逸,「付錢拉」?從避免單點(diǎn)故障、保證應(yīng)用自身的高可用豆混、解決交易量增長(zhǎng)等方面做了許多探索和實(shí)踐篓像。
在不考慮外部依賴系統(tǒng)突發(fā)故障,如網(wǎng)絡(luò)問(wèn)題皿伺、三方支付和銀行的大面積不可用等情況下员辩,「付錢拉」的服務(wù)能力可以達(dá)到99.999%。
本文重點(diǎn)討論如何提高應(yīng)用自身的可用性鸵鸥,關(guān)于如何避免單點(diǎn)故障和解決交易量增長(zhǎng)問(wèn)題會(huì)在其他系列討論奠滑。
為了提高應(yīng)用的可用性,首先要做的就是盡可能避免應(yīng)用出現(xiàn)故障,但要完全做到不出故障是不可能的养叛≈帜牛互聯(lián)網(wǎng)是個(gè)容易產(chǎn)生“蝴蝶效應(yīng)”的地方,任何一個(gè)看似很小的弃甥、發(fā)生概率為0的事故都可能出現(xiàn)爽室,然后被無(wú)限放大。
大家都知道RabbitMQ本身是非常穩(wěn)定可靠的淆攻,「付錢拉」最開(kāi)始也一直在使用單點(diǎn)RabbitMQ阔墩,并且從未出現(xiàn)運(yùn)行故障,所以大家在心理上都認(rèn)為這個(gè)東西不太可能出問(wèn)題瓶珊。
直到某天啸箫,這臺(tái)節(jié)點(diǎn)所在的物理主機(jī)硬件因?yàn)槟昃檬迚牡袅耍?dāng)時(shí)這臺(tái)RabbitMQ就無(wú)法提供服務(wù)伞芹,導(dǎo)致系統(tǒng)服務(wù)瞬間不可用忘苛。
故障發(fā)生了也不可怕,最重要的是及時(shí)發(fā)現(xiàn)并解決故障唱较。「付錢拉」對(duì)自身系統(tǒng)的要求是扎唾,秒級(jí)發(fā)現(xiàn)故障,快速診斷和解決故障南缓,從而降低故障帶來(lái)的負(fù)面影響胸遇。
首先我們簡(jiǎn)單的回顧一下,「付錢拉」曾經(jīng)碰到的一些問(wèn)題:
以史為鑒
1
新來(lái)的開(kāi)發(fā)同事在處理新接入的三方通道時(shí)汉形,由于經(jīng)驗(yàn)不足忽視了設(shè)置超時(shí)時(shí)間的重要性纸镊。就是這樣一個(gè)小小的細(xì)節(jié),導(dǎo)致這個(gè)三方隊(duì)列所在的交易全部堵塞概疆,同時(shí)影響到其他通道的交易逗威。
2
「付錢拉」系統(tǒng)是分布式部署的,并且支持灰度發(fā)布届案,所以環(huán)境和部署模塊非常多而且復(fù)雜庵楷。某次增加了一個(gè)新模塊,由于存在多個(gè)環(huán)境楣颠,且每個(gè)環(huán)境都是雙節(jié)點(diǎn)尽纽,新模塊上線后導(dǎo)致數(shù)據(jù)庫(kù)的連接數(shù)不夠用,從而影響其他模塊功能童漩。
3
同樣是超時(shí)問(wèn)題弄贿,一個(gè)三方的超時(shí),導(dǎo)致耗盡了當(dāng)前所配置的所有worker threads, 以至于其他交易沒(méi)有可處理的線程矫膨。
4
A三方同時(shí)提供鑒權(quán)差凹,支付等接口期奔,其中一個(gè)接口因?yàn)?b>「付錢拉」交易量突增,從而觸發(fā)A三方在網(wǎng)絡(luò)運(yùn)營(yíng)商那邊的DDoS限制危尿。通常機(jī)房的出口IP都是固定的呐萌,從而被網(wǎng)絡(luò)運(yùn)營(yíng)商誤認(rèn)為是來(lái)自這個(gè)出口IP的交易是流量攻擊,最終導(dǎo)致A三方鑒權(quán)和支付接口同時(shí)不可用谊娇。
5
再說(shuō)一個(gè)數(shù)據(jù)庫(kù)的問(wèn)題肺孤,同樣是因?yàn)?b>「付錢拉」交易量突增引發(fā)的。建立序列的同事給某個(gè)序列的上限是999济欢,999赠堵,999,但數(shù)據(jù)庫(kù)存的這個(gè)字段長(zhǎng)度是32位法褥,當(dāng)交易量小的時(shí)候茫叭,系統(tǒng)產(chǎn)生的值和字段32位是匹配的,序列不會(huì)升位半等∽岢睿可是隨著交易量的增加,序列不知不覺(jué)的升位數(shù)了杀饵,結(jié)果導(dǎo)致32位就不夠存放吗垮。類似這樣的問(wèn)題對(duì)于互聯(lián)網(wǎng)系統(tǒng)非常常見(jiàn),并且具有隱蔽性凹髓,所以如何避免就顯得非常重要了。
◆◆◆◆
下面我們從三個(gè)方面來(lái)看「付錢拉」所做的改變怯屉。
(一)盡可能避免故障
設(shè)計(jì)可容錯(cuò)的系統(tǒng)
比如重路由蔚舀,對(duì)于用戶支付來(lái)說(shuō),用戶并不關(guān)心自己的錢具體是從哪個(gè)通道支付出去的锨络,用戶只關(guān)心成功與否赌躺。「付錢拉」連接30多個(gè)通道,有可能A通道支付不成功羡儿,這個(gè)時(shí)候就需要?jiǎng)討B(tài)重路由到B或者C通道礼患,這樣就可以通過(guò)系統(tǒng)重路由避免用戶支付失敗,實(shí)現(xiàn)支付容錯(cuò)掠归。
還有針對(duì)OOM做容錯(cuò)缅叠,像Tomcat一樣。系統(tǒng)內(nèi)存總有發(fā)生用盡的情況虏冻,如果一開(kāi)始就對(duì)應(yīng)用本身預(yù)留一些內(nèi)存肤粱,當(dāng)系統(tǒng)發(fā)生OOM的時(shí)候,就可以catch住這個(gè)異常厨相,從而避免這次OOM领曼。
某些環(huán)節(jié)快速失敗“fail fast原則”
Fail fast原則是當(dāng)主流程的任何一步出現(xiàn)問(wèn)題的時(shí)候鸥鹉,應(yīng)該快速合理地結(jié)束整個(gè)流程,而不是等到出現(xiàn)負(fù)面影響才處理庶骄。
舉個(gè)幾個(gè)例子:
1-「付錢拉」啟動(dòng)的時(shí)候需要加載一些隊(duì)列信息和配置信息到緩存毁渗,如果加載失敗或者隊(duì)列配置不正確,會(huì)造成請(qǐng)求處理過(guò)程的失敗单刁,對(duì)此最佳的處理方式是加載數(shù)據(jù)失敗灸异,JVM直接退出,避免后續(xù)啟動(dòng)不可用幻碱;
2-「付錢拉」的實(shí)時(shí)類交易處理響應(yīng)時(shí)間最長(zhǎng)是40s绎狭,如果超過(guò)40s前置系統(tǒng)就不再等待,釋放線程褥傍,告知商戶正在處理中儡嘶,后續(xù)有處理結(jié)果會(huì)以通知的方式或者業(yè)務(wù)線主動(dòng)查詢的方式得到結(jié)果;
3-「付錢拉」使用了redis做緩存數(shù)據(jù)庫(kù)恍风,用到的地方有實(shí)時(shí)報(bào)警埋點(diǎn)和驗(yàn)重等功能蹦狂。如果連接redis超過(guò)50ms,那么這筆redis操作會(huì)自動(dòng)放棄朋贬,在最壞的情況下這個(gè)操作帶給支付的影響也就是50ms凯楔,控制在系統(tǒng)允許的范圍內(nèi)。
設(shè)計(jì)具備自我保護(hù)能力的系統(tǒng)
系統(tǒng)一般都有第三方依賴锦募,比如數(shù)據(jù)庫(kù)摆屯,三方接口等。系統(tǒng)開(kāi)發(fā)的時(shí)候糠亩,需要對(duì)第三方保持懷疑虐骑,避免第三方出現(xiàn)問(wèn)題時(shí)候的連鎖反應(yīng),導(dǎo)致宕機(jī)赎线。
(1)拆分消息隊(duì)列
「付錢拉」提供各種各樣的支付接口給商戶廷没,常用的就有快捷,個(gè)人網(wǎng)銀垂寥,企業(yè)網(wǎng)銀颠黎,退款,撤銷滞项,批量代付狭归,批量代扣,單筆代付文判,單筆代扣唉铜,語(yǔ)音支付,余額查詢律杠,身份證鑒權(quán)潭流,銀行卡鑒權(quán)竞惋,卡密鑒權(quán)等。與其對(duì)應(yīng)的支付通道有微信支付灰嫉,ApplePay拆宛,支付寶等30多家支付通道,并且接入了幾百家商戶讼撒。在這三個(gè)維度下浑厚,如何確保不同業(yè)務(wù)、三方根盒、商戶钳幅、以及支付類型互不影響,「付錢拉」所做的就是拆分消息隊(duì)列炎滞。下圖是部分業(yè)務(wù)消息隊(duì)列拆分圖:
(2)限制資源的使用
對(duì)于資源使用的限制設(shè)計(jì)是高可用系統(tǒng)最重要的一點(diǎn)敢艰,也是容易被忽略的一點(diǎn),資源相對(duì)有限册赛,用的過(guò)多了钠导,自然會(huì)導(dǎo)致應(yīng)用宕機(jī)。為此「付錢拉」做了以下功課:
限制連接數(shù)
隨著分布式的橫向擴(kuò)展森瘪,需要考慮數(shù)據(jù)庫(kù)連接數(shù)牡属,而不是無(wú)休止的最大化。數(shù)據(jù)庫(kù)的連接數(shù)是有限制的扼睬,需要全局考量所有的模塊逮栅,特別是橫向擴(kuò)展帶來(lái)的增加。
限制內(nèi)存的使用
內(nèi)存使用過(guò)大窗宇,會(huì)導(dǎo)致頻繁的GC和OOM证芭,內(nèi)存的使用主要來(lái)自以下兩個(gè)方面:
1.集合容量過(guò)大;
2.未釋放已經(jīng)不再引用的對(duì)象担映,比如放入ThreadLocal的對(duì)象一直會(huì)等到線程退出的時(shí)候回收。
限制線程創(chuàng)建
線程的無(wú)限制創(chuàng)建叫潦,最終導(dǎo)致其不可控蝇完,特別是隱藏在代碼中的創(chuàng)建線程方法。
當(dāng)系統(tǒng)的SY值過(guò)高時(shí)矗蕊,表示linux需要花費(fèi)更多的時(shí)間進(jìn)行線程切換短蜕。Java造成這種現(xiàn)象的主要原因是創(chuàng)建的線程比較多,且這些線程都處于不斷的阻塞(鎖等待傻咖,IO等待)和執(zhí)行狀態(tài)的變化過(guò)程中朋魔,這就產(chǎn)生了大量的上下文切換。
除此之外卿操,Java應(yīng)用在創(chuàng)建線程時(shí)會(huì)操作JVM堆外的物理內(nèi)存警检,太多的線程也會(huì)使用過(guò)多的物理內(nèi)存孙援。對(duì)于線程的創(chuàng)建,最好通過(guò)線程池來(lái)實(shí)現(xiàn)扇雕,避免線程過(guò)多產(chǎn)生上下文切換拓售。
限制并發(fā)
做過(guò)支付系統(tǒng)的應(yīng)該清楚,部分三方支付公司是對(duì)商戶的并發(fā)有要求的镶奉。三方給開(kāi)放幾個(gè)并發(fā)是根據(jù)實(shí)際交易量來(lái)評(píng)估的础淤,所以如果不控制并發(fā),所有的交易都發(fā)給三方哨苛,那么三方只會(huì)回復(fù)“請(qǐng)降低提交頻率”鸽凶。
所以在系統(tǒng)設(shè)計(jì)階段和代碼review階段都需要特別注意,將并發(fā)限制在三方允許的范圍內(nèi)建峭。
▼
上篇完