作者:馮忠旗
juejin.im/post/5cfde01bf265da1bba58f863
一、背景
對于互聯網應用和企業大型應用而言,多數都盡可能地要求做到7*24小時不間斷運行,而要做到完全不間斷運行可以說“難于上青天”,為此,對應用可用性程度的衡量標準一般有3個9到5個9,

對于一個功能和資料量不斷增加的應用,要保持比較高的可用性并非易事,為了實作高可用,「付錢拉」從避免單點故障、保證應用自身的高可用、解決交易量增長等方面做了許多探索和實踐,
在不考慮外部依賴系統突發故障,如網路問題、三方支付和銀行的大面積不可用等情況下,「付錢拉」的服務能力可以達到99.999%,
本文重點討論如何提高應用自身的可用性,關于如何避免單點故障和解決交易量增長問題會在其他系列討論,
為了提高應用的可用性,首先要做的就是盡可能避免應用出現故障,但要完全做到不出故障是不可能的,互聯網是個容易產生“蝴蝶效應”的地方,任何一個看似很小的、發生概率為0的事故都可能出現,然后被無限放大,
大家都知道RabbitMQ本身是非常穩定可靠的,「付錢拉」最開始也一直在使用單點RabbitMQ,并且從未出現運行故障,所以大家在心理上都認為這個東西不太可能出問題,
直到某天,這臺節點所在的物理主機硬體因為年久失修壞掉了,當時這臺RabbitMQ就無法提供服務,導致系統服務瞬間不可用,
故障發生了也不可怕,最重要的是及時發現并解決故障,「付錢拉」對自身系統的要求是,秒級發現故障,快速診斷和解決故障,從而降低故障帶來的負面影響,
二、問題
以史為鑒,首先我們簡單的回顧一下,「付錢拉」曾經碰到的一些問題:
(1) 新來的開發同事在處理新接入的三方通道時,由于經驗不足忽視了設定超時時間的重要性,就是這樣一個小小的細節,導致這個三方佇列所在的交易全部堵塞,同時影響到其他通道的交易;
(2) 「付錢拉」系統是分布式部署的,并且支持灰度發布,所以環境和部署模塊非常多而且復雜,某次增加了一個新模塊,由于存在多個環境,且每個環境都是雙節點,新模塊上線后導致資料庫的連接數不夠用,從而影響其他模塊功能;
(3) 同樣是超時問題,一個三方的超時,導致耗盡了當前所配置的所有worker threads, 以至于其他交易沒有可處理的執行緒;
(4) A三方同時提供鑒權,支付等介面,其中一個介面因為「付錢拉」交易量突增,從而觸發A三方在網路運營商那邊的DDoS限制,通常機房的出口IP都是固定的,從而被網路運營商誤認為是來自這個出口IP的交易是流量攻擊,最終導致A三方鑒權和支付介面同時不可用,
(5) 再說一個資料庫的問題,同樣是因為「付錢拉」交易量突增引發的,建立序列的同事給某個序列的上限是999,999,999,但資料庫存的這個欄位長度是32位,當交易量小的時候,系統產生的值和欄位32位是匹配的,序列不會升位,可是隨著交易量的增加,序列不知不覺的升位數了,結果導致32位就不夠存放,
類似這樣的問題對于互聯網系統非常常見,并且具有隱蔽性,所以如何避免就顯得非常重要了,
三、解決方案
下面我們從三個方面來看「付錢拉」所做的改變,
3.1 盡可能避免故障
3.1.1 設計可容錯的系統

比如重路由,對于用戶支付來說,用戶并不關心自己的錢具體是從哪個通道支付出去的,用戶只關心成功與否,「付錢拉」連接30多個通道,有可能A通道支付不成功,這個時候就需要動態重路由到B或者C通道,這樣就可以通過系統重路由避免用戶支付失敗,實作支付容錯,
還有針對OOM做容錯,像Tomcat一樣,系統記憶體總有發生用盡的情況,如果一開始就對應用本身預留一些記憶體,當系統發生OOM的時候,就可以catch住這個例外,從而避免這次OOM,
3.1.2 某些環節快速失敗“fail fast原則”
Fail fast原則是當主流程的任何一步出現問題的時候,應該快速合理地結束整個流程,而不是等到出現負面影響才處理,
舉個幾個例子:
(1)「付錢拉」啟動的時候需要加載一些佇列資訊和配置資訊到快取,如果加載失敗或者佇列配置不正確,會造成請求處理程序的失敗,對此最佳的處理方式是加載資料失敗,JVM直接退出,避免后續啟動不可用;
(2)「付錢拉」的實時類交易處理回應時間最長是40s,如果超過40s前置系統就不再等待,釋放執行緒,告知商戶正在處理中,后續有處理結果會以通知的方式或者業務線主動查詢的方式得到結果;
(3)「付錢拉」使用了redis做快取資料庫,用到的地方有實時報警埋點和驗重等功能,如果連接redis超過50ms,那么這筆redis操作會自動放棄,在最壞的情況下這個操作帶給支付的影響也就是50ms,控制在系統允許的范圍內,
3.1.3 設計具備自我保護能力的系統
系統一般都有第三方依賴,比如資料庫,三方介面等,系統開發的時候,需要對第三方保持懷疑,避免第三方出現問題時候的連鎖反應,導致宕機,
(1)拆分訊息佇列
「付錢拉」提供各種各樣的支付介面給商戶,常用的就有快捷,個人網銀,企業網銀,退款,撤銷,批量代付,批量代扣,單筆代付,單筆代扣,語音支付,余額查詢,身份證鑒權,銀行卡鑒權,卡密鑒權等,與其對應的支付通道有微信支付,ApplePay,支付寶等30多家支付通道,并且接入了幾百家商戶,在這三個維度下,如何確保不同業務、三方、商戶、以及支付型別互不影響,「付錢拉」所做的就是拆分訊息佇列,下圖是部分業務訊息佇列拆分圖:

(2)限制資源的使用
對于資源使用的限制設計是高可用系統最重要的一點,也是容易被忽略的一點,資源相對有限,用的過多了,自然會導致應用宕機,為此「付錢拉」做了以下功課:
- 限制連接數
隨著分布式的橫向擴展,需要考慮資料庫連接數,而不是無休止的最大化,資料庫的連接數是有限制的,需要全域考量所有的模塊,特別是橫向擴展帶來的增加,
- 限制記憶體的使用
記憶體使用過大,會導致頻繁的GC和OOM,記憶體的使用主要來自以下兩個方面:
A:集合容量過大;
B:未釋放已經不再參考的物件,比如放入ThreadLocal的物件一直會等到執行緒退出的時候回收,
- 限制執行緒創建
執行緒的無限制創建,最終導致其不可控,特別是隱藏在代碼中的創建執行緒方法,
當系統的SY值過高時,表示linux需要花費更多的時間進行執行緒切換,Java造成這種現象的主要原因是創建的執行緒比較多,且這些執行緒都處于不斷的阻塞(鎖等待,IO等待)和執行狀態的變化程序中,這就產生了大量的背景關系切換,
除此之外,Java應用在創建執行緒時會操作JVM堆外的物理記憶體,太多的執行緒也會使用過多的物理記憶體,
對于執行緒的創建,最好通過執行緒池來實作,避免執行緒過多產生背景關系切換,
- 限制并發
做過支付系統的應該清楚,部分三方支付公司是對商戶的并發有要求的,三方給開放幾個并發是根據實際交易量來評估的,所以如果不控制并發,所有的交易都發給三方,那么三方只會回復“請降低提交頻率”,
所以在系統設計階段和代碼review階段都需要特別注意,將并發限制在三方允許的范圍內,
我們講到「付錢拉」為z實作系統的可用性做了三點改變,其一是盡可能避免故障,接下來講后面兩點,
3.2 及時發現故障
故障就像鬼子進村,來的猝不及防,當預防的防線被沖破,如何及時拉起第二道防線,發現故障保證可用性,這時候報警監控系統的開始發揮作用了,一輛沒有儀表盤的汽車,是無法知道車速和油量,轉向燈是否亮,就算“老司機”水平再高也是相當危險的,同樣,系統也是需要監控的,最好是出現危險的時候提前報警,這樣可以在故障真正引發風險前解決,
3.2.1 實時報警系統
如果沒有實時報警,系統運行狀態的不確定性會造成無法量化的災難,「付錢拉」的監控系統指標如下:
-
實時性-實作秒級監控;
-
全面性-覆寫所有系統業務,確保無死角覆寫;
-
實用性-預警分為多個級別,監控人員可以方便實用地根據預警嚴重程度做出精確的決策;
-
多樣性-預警方式提供推拉模式,包括短信,郵件,可視化界面,方便監控人員及時發現問題,
報警主要分為單機報警和集群報警,而「付錢拉」屬于集群部署,實時預警主要依靠各個業務系統實時埋點資料統計分析實作,因此難度主要在資料埋點和分析系統上,
3.2.2 埋點資料
要做到實時分析,又不影響交易系統的回應時間,「付錢拉」在系統各個模塊中通過redis實時做資料埋點,然后將埋點資料匯總到分析系統,分析系統根據規則進行分析報警,
3.2.3 分析系統
分析系統最難做的是業務報警點,例如哪些報警只要一出來就必須出警,哪些報警一出來只需要關注,下面我們對分析系統做一個詳細介紹:
(1)系統運行架構

(2)系統運行流程

(3)系統業務監控點
「付錢拉」的業務監控點都是在日常運行程序中一點一滴總結出來的,分為出警類和關注類兩大塊,
A:出警類
-
網路例外預警;
-
單筆訂單超時未完成預警;
-
實時交易成功率預警;
-
例外狀態預警;
-
未回盤預警;
-
失敗通知預警;
-
例外失敗預警;
-
回應碼頻發預警;
-
核對不一致預警;
-
特殊狀態預警;
B:關注類
-
交易量例外預警;
-
交易額超過500W預警;
-
短信回填超時預警;
-
非法IP預警;
3.2.4 非業務監控點
非業務監控點主要是指從運維角度的監控,包括網路,主機,存盤,日志等,具體如下:
(1)服務可用性監控
使用JVM采集Young GC/Full GC次數及時間、堆記憶體、耗時Top 10執行緒堆疊等資訊,包括快取buffer的長度,
(2)流量監控
通過Agent監控代理部署在各個服務器上,實時采集流量情況,
(3)外部系統監控
通過間隙性探測來觀察三方或者網路是否穩定,
(4)中間件監控
-
針對MQ消費佇列,通過RabbitMQ腳本探測,實時分析佇列深度;
-
針對資料庫部分,通過安裝插件xdb,實時監控資料庫性能,
(5)實時日志監控
通過rsyslog完成分布式日志的歸集,然后通過系統分析處理,完成日志實時監控和分析,最后,通過開發可視化頁面展示給使用者,
(6)系統資源監控
通過Zabbix監控主機的CPU負載、記憶體使用率、各網卡的上下行流量、各磁盤讀寫速率、各磁盤讀寫次數(IOPS)、各磁盤空間使用率等,
以上就是「付錢拉」實時監控系統所做的,主要分為業務點監控和運維監控兩方面,雖然系統是分布式部署,但是每個預警點都是秒級回應,除此之外,業務系統的報警點也有一個難點,那就是有些報警是少量報出來不一定有問題,大量報警就會有問題,也就是所謂的量變引起質變,
舉一個例子,拿網路例外來說,發生一筆可能是網路抖動,但是多筆發生就需要重視網路是否真的有問題,針對網路例外「付錢拉」的報警樣例如下:
-
單通道網路例外預警:1分鐘內A通道網路例外連續發生了12筆,觸發了預警閥值;
-
多通道網路例外預警1: 10分鐘內,連續每分鐘內網路例外發生了3筆,涉及3個通道,觸發了預警閥值;
-
多通道網路例外預警2:10分鐘內,總共發生網路例外25筆,涉及3個通道, 觸發了預警閥值.
3.2.5 日志記錄和分析系統
對于一個大型系統而言,每天記錄大量的日志和分析日志是有一定的難度的,「付錢拉」每天平均有200W筆訂單量,一筆交易經過十幾個模塊流轉,假設一筆訂單記錄30條日志,可想而知每天會有多么巨大的日志量,
「付錢拉」日志的分析有兩個作用,一個是實時日志例外預警,另外一個是提供訂單軌跡給運營人員使用,
(1)實時日志預警
實時日志預警是針對所有實時交易日志,實時抓取帶有Exception或者Error的關鍵字然后報警,這樣的好處是,如果代碼中有任何運行例外,都會第一時間發現,「付錢拉」針對實時日志預警的處理方式是,首先采用rsyslog完成日志歸集,然后通過分析系統實時抓取,再做實時預警,
(2)訂單軌跡
對于交易系統,非常有必要實時了解一筆訂單的狀態流轉,「付錢拉」最初的做法是通過資料庫來記錄訂單軌跡,但是運行一段時間后,發現訂單量劇增導致資料庫表過大不利于維護,
「付錢拉」現在的做法是,每個模塊通過列印日志軌跡,日志軌跡列印的格式按照資料庫表結構的方式列印,列印好所有日志后,rsyslog來完成日志歸集,分析系統會實時抓取列印的規范日志,進行決議然后按天存放到資料庫中,并展示給運營人員可視化界面,
日志列印規范如下:
2016-07-22 18:15:00.512||pool-73-thread-4||通道配接器||通道配接器-發三方后||CEX16XXXXXXX5751||16201XXXX337||||||04||9000||【結算平臺訊息】處理中||0000105||98XX543210||GHT||03||11||2016-07-22 18:15:00.512||張張||||01||tunnelQuery||true||||Pending||||10.100.140.101||8cff785d-0d01-4ed4-b771-cb0b1faa7f95||10.999.140.101||O001||||0.01||||||||http://10.100.444.59:8080/regression/notice||||240||2016-07-20 19:06:13.000xxxxxxx
||2016-07-22 18:15:00.170||2016-07-22 18:15:00.496xxxxxxxxxxxxxxxxxxxx
||2016-07-2019:06:13.000||||||||01||0103||111xxxxxxxxxxxxxxxxxxxxxxxxx
||8fb64154bbea060afec5cd2bb0c36a752be734f3e9424ba7xxxxxxxxxxxxxxxxxxxx
||622xxxxxxxxxxxxxxxx||9bc195a59dd35a47||f2ba5254f9e22914824881c242d211
||||||||||||||||||||6xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx010||||||||||
簡要日志可視化軌跡如下:

日志記錄和分析系統除了以上兩點,也提供了交易和回應報文的下載和查看,
3.2.6 7*24小時監控室
「付錢拉」以上的報警專案給操作人員提供推拉兩種方式,一種是短信和郵件推送,一種是報表展示,除此之外,由于支付系統相比互聯網其他系統本身的重要性,「付錢拉」采用7*24小時的監控室保證系統的安全穩定,
3.3 及時處理故障
在故障發生之后,特別是生產環境,第一時間要做的不是尋找故障發生的原因,而是以最快速度處理故障,保障系統的可用性,「付錢拉」常見的故障和處理措施如下:
3.3.1 自動修復
針對自動修復部分,「付錢拉」常見的故障都是三方不穩定造成的,針對這種情況,就是上面說的系統會自動進行重路由,
3.3.2 服務降級
服務降級指在出現故障的情況下又無法快速修復的情況下,把某些功能關閉,以保證核心功能的使用,「付錢拉」針對商戶促銷的時候,如果某個商戶交易量過大,會實時的調整這個商戶的流量,使此商戶服務降級,從而不會影響到其他商戶,類似這樣的場景還有很多,具體的服務降級功能會在后續系列介紹,
四、Q&A
Q1: 能講講當年那臺RabbitMQ宕掉的具體細節和處理方案嗎?
A1: RabbitMQ宕機時間引發了對系統可用性的思考,當時我們的RabbitMQ本身并沒有宕機(RabbitMQ還是很穩定的),宕機的是RabbitMQ所在的硬體機器,但是問題就出在當時RabbiMQ的部署是單點部署,并且大家慣性思維認為RabbitMQ不會宕機,從而忽略了它所在的容器,所以這個問題的產生對于我們的思考就是所有的業務不可以有單點,包括應用服務器、中間件、網路設備等,單點不僅僅需要從單點本身考慮,比如整個服務做雙份,然后AB測驗,當然也有雙機房的,
Q2: 貴公司的開發運維是在一起的嗎?
A2: 我們開發運維是分開的,今天的分享主要是站在整個系統可用性層面來考慮的,開發偏多,有一部分運維的東西,這些付錢拉的走過的路,是我一路見證過的,
Q3: 你們的后臺全部使用的Java嗎?有沒有考慮其他語言?
A3: 我們目前系統多數是java,有少數的python、php、C++,這個取決于業務型別,目前java這個階段最適合我們,可能隨著業務的擴展,會考慮其他語言,
Q4: 對第三方依賴保持懷疑,能否舉個具體的例子說明下怎么樣做?萬一第三方完全不了用了怎么辦
A4: 系統一般都有第三方依賴,比如資料庫,三方介面等,系統開發的時候,需要對第三方保持懷疑,避免第三方出現問題時候的連鎖反應,導致宕機,大家都知道系統一旦發生問題都是滾雪球的,越來越大,比如說我們掃碼通道,如果只有一家掃碼通道,當這家掃碼通道發生問題的時候是沒有任何辦法的,所以一開始就對它表示懷疑,通過接入多家通道,如果一旦發生例外,實時監控系統觸發報警后就自動進行路由通道切換,保證服務的可用性;其二,針對不同的支付型別、商戶、交易型別做異步訊息拆分,確保如果一旦有一種型別的交易發生不可預估的例外后,從而不會影響到其他通道,這個就好比高速公路多車道一樣,快車和慢車道互不影響,其實總體思路就是容錯+拆分+隔離,這個具體問題具體對待,
Q5: 支付超時后,會出現網路問題,會不會存在錢已付,訂單丟失,如何做容災及資料一致性,又有沒重放日志,修過資料?
A5:做支付最重要的就是安全,所以針對訂單狀態我們都是保守處理策略,因此對于網路例外的訂單我們都是設定處理中狀態,然后最終通過主動查詢或者被動接受通知來完成和銀行或者三方的最終一致性,支付系統中,除了訂單狀態還有回應碼問題,大家都知道銀行或者三方都是通過回應碼來回應的,回應碼和訂單狀態的翻譯也是一定要保守策略,確保不會出現資金多付少付等問題,總之這個點的總體思路是,資金安全第一,所有的策略都是白名單原則,
Q6: 剛才提到過,若某支付通道超時,路由策略會分發至另一通道,根據那個通道圖可看出,都是不同的支付方式,比如支付寶或微信支付,那如果我只想通過微信支付,為啥不是重試,而要換到另一通道呢?還是通道本身意思是請求節點?
A6:首先針對超時不可以做重路由,因為socket timeout是不能確定這筆交易是否發送到了三方,是否已經成功或者失敗,如果是成功了,再重試一遍如果成功,針對付款就是多付,這種情況的資金損失對公司來說不可以的;其次,針對路由功能,需要分業務型別,如果是單筆代收付交易,用戶是不關心錢是哪個通道出去的,是可以路由的,如果是掃碼通道,用戶如果用微信掃碼,肯定最終是走微信,但是我們有好多中間渠道,微信是通過中間渠道出去的,這里我們可以路由不同的中間渠道,這樣最終對于用戶來說還是微信支付,
Q7: 能否舉例說下自動修復的程序?如何發現不穩定到重路由的細節?
A7: 自動修復也就是通過重路由做容錯處理,這個問題非常好,如果發現不穩定然后去決策重路由,重路由一定是明確當前被重路由的交易沒有成功才可以路由,否則就會造成多付多收的資金問題,我們系統目前重路由主要是通過事后和事中兩種方式來決策的,針對事后比如5分鐘之內通過實時預警系統發現某個通道不穩定,那么就會把當期之后的交易路由到別的通道;針對事中的,主要是通過分析每筆訂單回傳的失敗回應碼,回應碼做狀態梳理,明確可以重發的才做重路由,這里我指列舉這兩點,其他的業務點還非常多,鑒于篇幅原因,不做詳述,但是總體思路是必須有一個記憶體實時分析系統,秒級決策,這個系統必須快,然后結合實時分析和離線分析做決策支撐,我們的實時秒級預警系統就做這個事情,
Q8: 商戶促銷有規律嗎?促銷時峰值與平時相比會有多少差別?有技術演練么?降級的優先級是怎樣的?
A8:商戶促銷一般我們會事先經常和商戶保持溝通,事先了解促銷的時間點和促銷量,然后針對性做一些事情;促銷峰值和平時差距非常大,促銷一般都是2個小時之內的比較多,比如有的賣理財產品,促銷也就集中在1個小時之內,所以峰值非常高;技術演練是我們在了解商戶的促銷量,然后預估系統的處理能力,然后提前做演練;降級的優先級主要是針對商戶的,由于接入我們的商戶支付場景比較多的,有理財,有代收付,有快捷,有掃碼等等,所以我們整體原則就是不同的商戶之間一定不可以相互影響,因為不能因為你家做促銷影響了其他商家,
Q9:rsyslog歸集日志怎么存盤的?
A9: 這個是好問題,剛開始我們的日志也就是訂單軌跡log是記錄在資料庫表中的,結果發現一筆訂單流轉需要好多模塊,這樣一筆訂單的日志軌跡就是10筆左右,如果一天400w筆交易的話,這張資料庫表就有問題了,就算拆分也是會影響資料庫性能的,并且這個屬于輔助業務,不應該這樣做,然后,我們發現寫日志比寫資料庫好,所以把實時日志列印成表格的形式,列印到硬碟上,這塊由于只是實時日志所以日志量不大,就是在日志服務器的一個固定目錄下,由于日志都是在分布式機器上,然后通過歸集日志到一個集中的地方,這塊是通過掛載存盤的,然后有專門運維團隊寫的程式去實時決議這些表格形式的日志,最終通過可視化頁面展示到運營操作頁面,這樣運營人員看到的訂單軌跡幾乎是實時的,您關心的怎么存盤實際上不是啥問題,因為我們分了實時日志和離線日志,然后超過一定時間的離線日志會切割,最終被洗掉,
Q10: 系統監控和性能監控如何配合的?
A10:我理解的系統監控包括了系統性能監控,系統性能監控是系統整體監控的一部分,不存在配合問題,系統性能監控有多個維度,比如應用層面,中間件,容器等,系統的非業務監控可以查看文章分享,
近期熱文推薦:
1.Java 15 正式發布, 14 個新特性,重繪你的認知!!
2.終于靠開源專案弄到 IntelliJ IDEA 激活碼了,真香!
3.我用 Java 8 寫了一段邏輯,同事直呼看不懂,你試試看,,
4.吊打 Tomcat ,Undertow 性能很炸!!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/246401.html
標籤:Java
