| 本文主要結構如下,讀完大概需要 12 分鐘。 
 老司機簡介 白輝,人稱七公,2014年以前在阿里巴巴B2B主要負責Aliexpress資金中心、評價、任務中心等系統(tǒng)。14年8月離開阿里出國游歷,15年回國后加入蘑菇街,目前在蘑菇街、美麗說、淘世界大集團共享的電商基礎平臺負責購物車下單小組及交易平臺架構工作。 我總結了一下,一般電商系統(tǒng)發(fā)展到中期都會面臨三個方面的問題(如圖)。 第一方面是業(yè)務問題。比如,一開始做業(yè)務的時候可能很隨意,一是并不考慮業(yè)務模型、系統(tǒng)架構,二是業(yè)務之間的耦合比較嚴重,比如交易和資金業(yè)務,有可能資金和外部第三方支付公司的交互狀態(tài)耦合在交易系統(tǒng)里,這些非常不利于業(yè)務發(fā)展。 第二方面是系統(tǒng)問題。2014年我們面臨單體應用,400人開發(fā)一個大應用,擴展性很差,業(yè)務比較難做。 第三方面是支撐問題,比如關于環(huán)境、開發(fā)框架和質量工具等。這些是電商系統(tǒng)發(fā)展到中期都會面臨的問題,中期的概念是用戶過了千萬,PV過了1億。 我們來看一下蘑菇街2015年初面臨的問題。蘑菇街2015年用戶過億,PV過10億,業(yè)務在超高速發(fā)展,每年保持3倍以上的增長。電商促銷、交易、支付等業(yè)務形態(tài)都在快速膨脹,我們需要快速支持業(yè)務發(fā)展,而不是成為業(yè)務的瓶頸。那么就是要去做系統(tǒng)的拆分和服務化。 系統(tǒng)拆分與服務化過程 2015年3月我來到蘑菇街之后,先制訂了服務化的規(guī)范,探討了到底什么是標準的服務化。在做服務化的過程中,發(fā)現(xiàn)大家代碼風格完全不一樣,所以制定編碼規(guī)范非常重要。 2015年8月,我們完成了各個模塊的改造,包括用戶、商品、交易、訂單、促銷、退款等,然后有了服務化架構1.0的體系。在此基礎之上,我們進一步做了提升流量和穩(wěn)定性等更深度的建設。2015年9月,我們實施了分庫分表和鏈路性能提升優(yōu)化,2015年10月做了服務治理和服務保障。 接下來,以服務架構和服務體系建設為主線,講一下去年整個網(wǎng)站架構升級的過程。 在服務化1.0體系完成之后,我們得到了一個簡單的體系,包含下單服務、營銷服務、店鋪服務、商品服務和用戶服務,還有簡單的RPC框架Tesla。當時,我們并沒有做很多性能優(yōu)化的事情,但是通過業(yè)務流程化簡和邏輯優(yōu)化,每秒最大訂單數(shù)從400提升到1K,基礎服務也都搭建了起來。 有了1.0初步的服務化體系之后,更進一步,我們一是要繼續(xù)深入網(wǎng)站如資金等的服務化,二是要做服務內部的建設,比如容量、性能,這也是接下來要講的內容。 這個鏈路(見圖)是比較典型的電商鏈路,有商品頁、下單、支付、營銷和庫存等內容。一開始每個點都有瓶頸,每個瓶頸都是一個籬笆,我們要正視它,然后翻越它。 我們先來看第一個籬笆墻:下單的瓶頸。 2015年“3.21”大促的時候,DB崩潰了,這個瓶頸很難突破。下一個訂單要插入很多條數(shù)據(jù)記錄到單DB的DB表。我們已經(jīng)用了最好的硬件,但是瓶頸依然存在,最主要的問題就是DB單點,需要去掉單點,做成可水平擴展的。 流量上來了,到DB的行寫入數(shù)是2萬/秒,對DB的壓力很大。寫應該控制在一個合理的量,DB負載維持在較低水平,主從延時也才會在可控范圍內。所以DB單點的問題非常凸顯,這座大山必須邁過去,我們做了一個分庫分表組件TSharding來實施分庫分表。 將我們寫的分庫分表工具與業(yè)界方案對比,業(yè)界有淘寶TDDL Smart Client的方式,還有Google的Vitess等的Proxy方式,這兩種成熟方案研發(fā)和運維的成本都太高,短期內我們接受不了,所以借鑒了Mybatis Plugin的方式,但Mybatis Plugin不支持數(shù)據(jù)源管理,也不支持事務。 我大概花了一周時間寫了一個組件——自研分庫分表組件TSharding(https://github.com/baihui212/tsharding),然后快速做出方案,把這個組件應用到交易的數(shù)據(jù)庫,在服務層和DAO層,訂單容量擴展到千億量級,并且可以繼續(xù)水平擴展。TSharding上線一年之后,我們將其開放出來。 
 第二個籬笆墻就是營銷服務RT的問題。促銷方式非常多,包括各種紅包、滿減、打折、優(yōu)惠券等。實際上促銷的接口邏輯非常復雜,在“雙11”備戰(zhàn)的時候,面對這個復雜的接口,每輪鏈路壓測促銷服務都會發(fā)現(xiàn)問題,之后優(yōu)化再壓測,又發(fā)現(xiàn)新的問題。 我們來一起看看遇到的各種問題以及是如何解決的。首先是壓測出現(xiàn)接口嚴重不可用,這里可以看到DB查詢頻次高,響應很慢,流量一上來,這個接口就崩潰了。那怎么去排查原因和解決呢? 首先是SQL優(yōu)化,用工具識別慢SQL,即全鏈路跟蹤系統(tǒng)Lurker。 
 這張圖我簡單介紹一下。遇到SQL執(zhí)行效率問題的時候,就看是不是執(zhí)行到最高效的索引,掃表行數(shù)是不是很大,是不是有filesort。有ORDER BY的時候,如果要排序的數(shù)據(jù)量不大或者已經(jīng)有索引可以走到,在數(shù)據(jù)庫的內存排序緩存區(qū)一次就可以排序完。 如果一次不能排序完,那就先拿到1000個做排序,然后輸出到文件,然后再對下1000個做排序,最后再歸并起來,這就是filesort的大致過程,效率比較低。所以盡量要走上索引,一般類的查詢降低到2毫秒左右可以返回。 其次是要讀取很多優(yōu)惠規(guī)則和很多優(yōu)惠券,數(shù)據(jù)量大的時候DB是很難扛的,這時候我們要做緩存和一些預處理。特別是查詢DB的效率不是很高的時候,盡量緩存可以緩存的數(shù)據(jù)、盡量緩存多一些數(shù)據(jù)。但如果做緩存,DB和緩存數(shù)據(jù)的一致性是一個問題。 在做數(shù)據(jù)查詢時,首先要看本地緩存有沒有開啟,如果本地緩存沒有打開,就去查分布式緩存,如果分布式緩存中沒有就去查DB,然后從DB獲取數(shù)據(jù)過來。需要盡量保持DB、緩存數(shù)據(jù)的一致性,如果DB有變化,可以異步地做緩存數(shù)據(jù)失效處理,數(shù)據(jù)百毫秒內就失效掉,減少不一致的問題。 另外,如果讀到本地緩存,這個內存訪問比走網(wǎng)絡請求性能直接提升了一個量級,但是帶來的弊端也很大,因為本地緩存沒有辦法及時更新,平時也不能打開,因為會帶來不一致問題。 但大促高峰期間我們會關閉關鍵業(yè)務數(shù)據(jù)變更入口,開啟本地緩存,把本地緩存設置成一分鐘失效,一分鐘之內是可以緩存的,也能容忍短暫的數(shù)據(jù)不一致,所以這也是一個很好的做法。 同樣的思路,我們也會把可能會用到的數(shù)據(jù)提前放到緩存里面,做預處理。在客戶端進行數(shù)據(jù)預處理,要么直接取本地數(shù)據(jù),或者在本地直接做計算,這樣更高效,避免了遠程的RPC。大促期間我們就把活動價格信息預先放到商品表中,這樣部分場景可以做本地計價,有效解決了計價接口性能的問題。 再就是讀容量問題,雖然緩存可以緩解壓力,但是DB還是會有幾十K的讀壓力,單點去扛也是不現(xiàn)實的,所以要把讀寫分離,如果從庫過多也有延時的風險,我們會把數(shù)據(jù)庫的并行復制打開。 
 我們來看一下數(shù)據(jù)。這是去年“雙11”的情況(如圖)。促銷服務的RT得到了有效控制,所以去年“雙11”平穩(wěn)度過。 
 接下來講一個更基礎、更全局的優(yōu)化,就是異步化。比如說下單的流程,有很多業(yè)務是非實時性要求的,比如下單送優(yōu)惠券,如果在下單的時候同步做,時間非常長,風險也更大,其實業(yè)務上是非實時性或者準實時性的要求,可以做異步化處理,這樣可以減少下單對機器數(shù)量的要求。 另外是流量高峰期的一些熱點數(shù)據(jù)。大家可以想象一下,下單的時候,一萬個人競爭同一條庫存數(shù)據(jù),一萬個節(jié)點鎖在這個請求上,這是多么恐怖的事情。 所以我們會有異步隊列去削峰,先直接修改緩存中的庫存數(shù)目,改完之后能讀到最新的結果,但是不會直接競爭DB,這是異步隊列削峰很重要的作用。還有,數(shù)據(jù)庫的競爭非常厲害,我們需要把大事務做拆分,盡量讓本地事務足夠小,同時也要讓多個本地事務之間達到一致。 異步是最終達到一致的關鍵,異步的處理是非常復雜的。可以看一下這個場景(見圖),這是一個1-6步的處理過程,如果拆分成步驟1、2、3、4、end,然后到5,可以異步地做;6也一樣,并且5和6可以并行執(zhí)行。同時,這個步驟走下來鏈路更短,保障也更容易;步驟5和6也可以單獨保障。所以異步化在蘑菇街被廣泛使用。 
 異步化之后面臨的困難也是很大的,會有分布式和一致性的問題。交易創(chuàng)建過程中,訂單、券和庫存要把狀態(tài)做到絕對一致。但下單的時候如果先鎖券,鎖券成功了再去減庫存,如果減庫存失敗了就是很麻煩的事情,因為優(yōu)化券服務在另外一個系統(tǒng)里,如果要同步調用做券的回滾,有可能這個回滾也會失敗,這個時候處理就會非常復雜。 我們的做法是,調用服務超時或者失敗的時候,我們就認為失敗了,就會異步發(fā)消息通知回滾。優(yōu)惠券服務和庫存服務被通知要做回滾時,會根據(jù)自身的狀態(tài)來判斷是否要回滾,如果鎖券成功了券就回滾,減庫存也成功了庫存做回滾;如果庫存沒有減就不用回滾。 所以我們是通過異步發(fā)消息的方式保持多個系統(tǒng)之間的一致性;如果不做異步就非常復雜,有的場景是前面所有的服務都調用成功,第N個服務調用失敗。另外的一致性保障策略包括Corgi MQ生產(chǎn)端發(fā)送失敗會自動重試保證發(fā)成功,消費端接收ACK機制保證最終的一致。另外,與分布式事務框架比起來,異步化方案消除了二階段提交等分布式事務框架的侵入性影響,降低了開發(fā)的成本和門檻。 
 另一個場景是,服務調用上會有一些異步的處理。以購物車業(yè)務為例,購物車列表要調用10個Web服務,每一個服務返回的時間都不一樣,比如第1個服務20毫秒返回,第10個服務40毫秒返回,串行執(zhí)行的效率很低。 而電商類的大多數(shù)業(yè)務都是IO密集型的,而且數(shù)據(jù)量大時還要分批查詢。所以我們要做服務的異步調用。比如下圖中這個場景,步驟3處理完了之后callback馬上會處理,步驟4處理完了callback也會馬上處理,步驟3和4并不相互依賴,且處理可以同時進行了,提高了業(yè)務邏輯執(zhí)行的并行度。 目前我們是通過JDK7的Future和Callback實現(xiàn)的,在逐步往JDK8的Completable Future遷移。這是異步化在網(wǎng)站整體的應用場景,異步化已經(jīng)深入到我們網(wǎng)站的各個環(huán)節(jié)。 
 剛才我們講了鏈路容量的提升、促銷RT的優(yōu)化,又做了異步化的一些處理。那么優(yōu)化之后怎么驗證來優(yōu)化的效果呢?到底有沒有達到預期?我們有幾個壓測手段,如線下單機壓測識別應用單機性能瓶頸,單鏈路壓測驗證集群水位及各層核?系統(tǒng)容量配比,還有全鏈路壓測等。 這是去年“雙11”之前做的壓測(見圖),達到了5K容量的要求。今年對每個點進一步深入優(yōu)化,2016年最大訂單提升到了10K,比之前提升了25倍。實際上這些優(yōu)化可以不斷深入,不僅可以不斷提高單機的性能和單機的QPS,還可以通過對服務整體上的優(yōu)化達到性能的極致,并且可以引入一些廉價的機器(如云主機)來支撐更大的量。 
 我們?yōu)槭裁匆鲞@些優(yōu)化?業(yè)務的發(fā)展會對業(yè)務系統(tǒng)、服務框架提出很多很高的要求。因此,我們對Tesla做了這些改善(見圖),服務的配置推送要更快、更可靠地到達客戶端,所以有了新的配置中心Metabase,也有了Lurker全鏈路監(jiān)控,服務和服務框架的不斷發(fā)展推動了網(wǎng)站其他基礎中間件產(chǎn)品的誕生和發(fā)展。2015年的下半年我們進行了一系列中間件的自研和全站落地。 
 我們得到了服務架構1.5的體系(見圖),首先是用戶服務在最底層,用戶服務1200K的QPS,庫存250K,商品服務400K,營銷200K,等等。 接下來我們看一下這一階段,Tesla開始做服務管控,真正成為了一個服務框架。我們最開始做發(fā)布的時候,客戶端、服務端由于做的只是初級的RPC調用,如果服務端有變更,客戶端可能是幾秒甚至數(shù)十秒才能拉到新配置,導致經(jīng)常有客戶投訴。 有了對服務變更推送更高的要求后,我們就有了Matabase配置中心,服務端如果有發(fā)布或者某一刻崩潰了,客戶端馬上可以感知到,這樣就完成了整個服務框架連接優(yōu)化的改進,真正變成服務管控、服務治理框架的開端。 
 有了上面講到的服務化改進和性能提升之后,是不是大促的時候看一看監(jiān)控就行了?其實不是。大流量來的時候,萬一導致整個網(wǎng)站崩潰了,一分鐘、兩分鐘的損失是非常大的,所以還要保證服務是穩(wěn)的和高可用的。只有系統(tǒng)和服務是穩(wěn)定的,才能更好地完成業(yè)務指標和整體的經(jīng)營目標。 下面會講一下服務SLA保證的內容。 
 首先SLA體現(xiàn)在對容量、性能、程度的約束,包括程度是多少的比例。那么要保證這個SLA約束和目標達成,首先要把關鍵指標監(jiān)控起來;第二是依賴治理、邏輯優(yōu)化;第三是負載均衡、服務分組和限流;第四是降級預案、容災、壓測、在線演練等。 這是我們服務的關鍵指標的監(jiān)控圖(見上圖)。支付回調服務要滿足8K QPS,99%的RT在30ms內,但是圖中監(jiān)控說明SLA未達到,RT程度指標方面要優(yōu)化。 服務的SLA保證上,服務端超時和限流非常重要。如果沒有超時,很容易引起雪崩。 我們來講一個案例,有次商品服務響應變慢,就導致上層的其他服務都慢,而且商品服務積壓了很多請求在線程池中,很多請求響應過慢導致客戶端等待超時,客戶端早就放棄調用結果結束掉了,但是在商品服務線程池線程做處理時拿到這個請求還會處理,客戶都跑了,再去處理,客戶也拿不到這個結果,最后還會造成上層服務請求的堵塞,堵塞原因緩解時產(chǎn)生洪流。 
 限流是服務穩(wěn)定的最后一道保障。一個是HTTP服務的限流,一個是RPC服務的限流。我們服務的處理線程是Tesla框架分配的,所以服務限流可以做到非常精確,可以控制在服務級別和服務方法級別,也可以針對來源做限流。 我們做了這樣一系列改造之后,服務框架變成了有完善的監(jiān)控、有負載均衡、有服務分組和限流等完整管控能力的服務治理框架。服務分組之后,如果通用的服務崩潰了,購買鏈路的服務可以不受影響,這就做到了隔離。 這樣的一整套服務體系(如圖)就構成了我們的服務架構2.0,最終網(wǎng)站的可用性做到了99.979%,這是今年6月份的統(tǒng)計數(shù)據(jù)。我們還會逐步把服務的穩(wěn)定性和服務質量做到更好。 
 
 最后總結一下,服務框架的體系完善是一個漫長的發(fā)展過程,不需要一開始就很強、什么都有的服務框架,最早可能就是一個RPC框架。服務治理慢慢隨著業(yè)務量增長也會發(fā)展起來,服務治理是服務框架的重要組成部分。 另外,Tesla是為蘑菇街業(yè)務體系量身打造的服務框架??梢哉f服務框架是互聯(lián)網(wǎng)網(wǎng)站架構的核心和持續(xù)發(fā)展的動力。選擇開源還是自建,要看團隊能力、看時機。我們要深度定制服務框架,所以選擇了自研,以后可能會開源出來。 
 服務框架是隨著業(yè)務發(fā)展不斷演變的,我們有1.0、1.5和2.0架構的迭代。要前瞻性地謀劃和實施,要考慮未來三年、五年的容量。有一些系統(tǒng)瓶頸可能是要提前解決的,每一個場景不一樣,根據(jù)特定的場景選擇最合適的方案。 容量和性能關鍵字是一切可擴展、Cache、IO、異步化。目前我們正在做的是服務治理和SLA保障系統(tǒng)化,未來會做同城異地的雙活。 謝謝大家!  今日薦號:聊聊架構 聊聊架構是InfoQ旗下的垂直社區(qū),主要關注傳統(tǒng)企業(yè)以及互聯(lián)網(wǎng)企業(yè)的系統(tǒng)架構,研究并探討架構實踐中的難點和痛點。定期組織線上群分享活動。 ▽ 
 | 
|  |