天天看點

《淘寶技術這十年》讀書筆記 (四). 分布式時代和中間件

        前面兩篇文章介紹了淘寶的發展曆程、java時代的變遷和淘寶開始創新技術:

        這篇文章主要講述分布式時代和中間件相關知識,包括服務化、hsf、notify和tddl。同時裡面有我們經常遇見的編碼錯誤等相關問題,希望文章對你有所幫助!

        在系統發展的過程中,架構師的眼光至關重要,作為程式員,隻要把功能實作即可,但作為架構師,要考慮系統的擴充性、重用性,對于這種敏銳的感覺,有人說是一種“代碼潔癖”。

        淘寶早期有幾個架構師就具備了這種感覺,周銳虹開發的webx是一個擴充性很強的架構,行癫在這個架構上插入了資料分庫路由的子產品、session架構等。在做淘寶背景系統時,同樣需要這幾個子產品,行癫指導我把這些子產品單獨打成jar包。

        上面說的都是比較小的複用子產品,到2006年,我們做了一個商品類目屬性的改造,在類目中引入了屬性的概念。項目代号叫“泰山”,這是一個舉足輕重的項目,這個改變是一個劃時代的創新。

        在這之前三年時間内,商品的分類都是按照樹狀一級一級的節點來分的,随着商品數量增長,類目也變得越來越深、複雜。這樣,買家如果查找一件商品,就要逐級打開類目,找商品之前要弄清商品的分類。一個很嚴重的問題,例如男裝裡有t恤、t恤下面有耐克、耐克有純棉的,女裝也有t恤、t恤下面還是有耐克、耐克下有純棉,那是先分男女裝,再分款式、品牌和材質呢?還是先分品牌,再分款式、材質和男女裝呢?

        這時一燈說品牌、款式、材質等都可以叫做“屬性”,屬性是類似tag(标簽)的一個概念,與類目相比更加靈活,這樣也縮減了類目的深度。這個思想解決了分類的難題!

        從系統角度來看,我們建立了“屬性”這樣一個資料結構,由于除了類目的子節點有屬性外,父節點也可能有屬性,于是類目屬性合起來也是一個結構化的資料對象。把它獨立出來作為一個服務,叫做catserver(category server)。跟類目屬性密切關聯的商品搜尋功能獨立出來,叫做hesper(金星)。catserver和hesper供淘寶的前背景系統調用。

        現在淘寶的商品類目屬性已經是全球最大的,幾乎沒有什麼類目的商品在淘寶上找不到(除違禁品),但最初的類目屬性改造完之後,缺乏屬性資料,尤其是數位類。從哪裡弄這些資料呢?我們跟“中關村線上”合作,拿到了很多資料。

        有了類目屬性給營運工作帶來了很大的便利,我們知道淘寶的營運主要就是類目的營運,什麼季節推出什麼商品,都要在類目屬性上做調整,讓買家容易找到。所屬商品的賣家要編輯一次自己的商品,如冬天把羽絨衣調整到女裝一級目錄下,但随着商品量的增長,賣家的工作量越來越大。

        到了2008年,我們研究了超市裡前背景商品的分類,發現超市前背景商品可以随季節和關聯來調整擺放場景(例如著名的啤酒和尿布的關聯),背景倉庫裡要按照自然類目來存儲,二者密切關聯,卻又互相分開。淘寶前台展示的是根據營運需要擺放商品的類目和屬性。改造後的類目屬性服務取名為forest(森林,與類目屬性有點神似。catserver還用于提供賣家授權、品牌服務、關鍵詞等相關服務)。類目屬性的服務化是淘寶在系統服務化方面做的第一個探索。

        雖然個别架構師具備了“代碼潔癖”,但淘寶前台系統的業務量和代碼量還是呈爆炸式的增長。

        業務方總在後面催,開發人員不夠就繼續招人,招來的人根本看不懂原來的業務,隻好摸索着在“合适的地方”加上一些“合适的代碼”,看看運作起來像那麼回事後,就釋出上線。

        在這樣的惡性循環中,系統越來越腫,業務的耦合性越來越高(高内聚、低耦合),開發的效率越來越低。借用當時較流行的一句話:“你寫一段代碼,編譯一下能通過,半個小時過去了;編譯一下沒通過,半天就過去了。”在這種情況下,系統出錯的機率也逐漸增長,這讓開發人員苦不堪言。感覺現在很多公司招實習生都是這種感覺。

        2007年年底的時候,研發部空降了一位從矽谷來的高管——空聞大師。他是一位溫厚的長者,他告訴我們一切要以穩定為中心,所有影響系統穩定的因素都要解決掉。例如:每做一個日常修改,都必須對整個系統回歸測試一遍;多個日常修改如果放在一個版本中,要是一個功能沒有測試通過,整個系統都不能釋出。我們把這個叫做“火車模型”,即任何一個乘客沒有上車,都不許發車。這樣做最直接的後果就是火車一直晚點,新功能上線更慢,我們能明顯感覺到業務放的不滿,壓力非常大。

        現在回過頭來看,其實我們并沒有了解背後的思路。正是在這種要求下,我們不得不開始改變些東西,例如:把回歸測試日常化,每天晚上都跑一遍整個系統的回歸。

        另外,在這種要求下,我們不得不對這個超級複雜的系統做肢解和重構,其中複用性最高的一個子產品:使用者資訊子產品開始拆分出來,我們叫它uic(user

information center)。在uic中,它隻處理最基礎的使用者資訊操作,例如getuserbyid、getuserbyname等。

        在另一方面,還有兩個新興的業務對系統基礎功能的拆分也提出了要求。在那時候,我們做了淘寶旅行(trip.taobao.com)和淘寶彩票(caipiao.taobao.com)兩個新業務,這兩個新業務在商品的展示和交易的流程上都跟主站的業務不一樣,機票是按照航班資訊展示的,彩票是按照雙色球、數字和足球的賽程來展示的。但用到的會員功能和交易功能是與主站差不多的,當時做起來很糾結,因為如果在主站中做,會有一大半跟主站無關的東西,如果重新做一個,會有很多重複建設。

        最終我們決定不再給主站添亂了,就另起爐竈做了兩個新的業務系統,從查詢商品、購買商品、評價回報、檢視訂單這一整個流程都重新寫了一套。現在在“我的淘寶”中檢視交易記錄,還能發現“已買到的寶貝”中把機票和彩票另外列出來了,他們沒加入到普通訂單中。

當時如果已經把會員、交易、商品、評價這些子產品都拆分出來,就不用什麼都重做一遍了。

《淘寶技術這十年》讀書筆記 (四). 分布式時代和中間件

        到2008年初,整個主動系統(有了機票、彩票系統之後,把原來的系統叫做主站)的容量已經達到了瓶頸,商品數在1億個以上,pv在2.5億個以上,會員數超過了5000萬個。這時oracle的連接配接池數量都不夠用了,資料庫的容量到了極限,即使上層系統加機器也無法繼續擴容,我們隻有把底層的基礎服務繼續拆分,從底層開始擴容,上層才能擴充,這才能容納未來三五年的增長。

于是我們啟動了一個更大的項目,即把交易這個核心業務子產品拆分出來。

        原來的淘寶交易除了跟商品管理耦合在一起,還在支付寶和淘寶之間轉換,跟支付寶耦合在一起,這會導緻系統很複雜,使用者體驗也很不好。我們把交易的底層業務拆分出來,叫交易中心(tradecenter,tc),所謂底層業務,就如建立訂單、減庫存、修改訂單狀态等原子型的操作;交易的上層業務叫交易管理(trademanager,tm)例如拍下一件普通商品要對訂單、庫存、物流進行操作,拍下虛拟商品不需要對物流進行操作,這些在tm中完成。

        類目屬性、使用者中心、交易中心,随着這些子產品逐漸拆分和服務化改造,我們在系統架構方面也積累了不少經驗。到2008年年底就做了一個更大的項目,把淘寶所有的業務都子產品化,這是繼2004年從lamp架構到java架構之間的第二次脫胎換骨。

        我們對這個項目取了一個很霸氣的名字——“五彩石”(女娲煉石補天用的石頭)。這個系統重構的工作非常驚險,有人稱為“給一架高速飛行的飛機換發動機”。他們把淘寶的系統拆分成了如下架構。

《淘寶技術這十年》讀書筆記 (四). 分布式時代和中間件

        其中,uic和forest在上文已說過,tc、ic、sc分别是交易中心(trade center)、商品中心(item center)、店鋪中心(shop center),這些中心級别的服務隻提供原子級的業務邏輯,如根據id查找商品、建立交易、減少庫存等操作。

        再往上一次是業務系統tm(trade manager,交易業務)、im(item manager,商品業務)、sm(shop manager,後來改名叫ss,即shop system,店鋪業務)、detail(商品詳情)。

        拆分之後,系統之間的互動關系變得非常複雜。

系統這麼拆分的好處顯而易見,拆分之後每個系統可以單獨部署,業務簡單,友善擴容;有大量可重用的子產品便于開發新的業務;能夠做到專人專事,讓技術人員更加專注于某一個領域。

        這樣要解決的問題也很明顯,拆分後,系統之間還是必須要打交道的,越往底層的系統,調用它的客戶越多,這要求底層系統必須具有超大規模的容量和非常高的可用性。

另外,拆分之後的系統如何通信?這裡需要兩種中間件系統,一種是實時調用的中間件(淘寶的hsf,高性能服務架構),一種是異步消息通知的中間件(淘寶的notify)。另外,一個需要解決的問題是使用者在a系統登入後,到b系統的時候,使用者的登入資訊怎麼儲存?這又設計一個session架構。再者,還有一個軟體工程方面的問題,這麼多層的一套系統,怎麼去測試它?

        其實網際網路網站發展過程類似于超市經營(此處省略超市銷售收銀的例子,可以想象下沃爾瑪排隊購物付款的場景吧),隻是在技術層面用其他名詞來表達而已,例如:有叢集、分工、負載均衡、根據qos配置設定資源等。

        叢集:所有收銀員提供的都是收銀功能,每個收銀員都可以完成收款,可以認為所有的收銀員構成了一個叢集。網際網路叢集會受限于排程、資料庫、機房等。

        分工:收銀員和打掃衛生的人分開,這種分工容易解決,而這種分工在網際網路中是一項重要而複雜的技術,涉及的主要有按功能和資料庫的不同拆分系統等。如何拆分和拆分後如何互動是需要面臨的兩個挑戰。隐藏會有高性能通信架構、soa平台、消息中間件、分布式資料層等基礎産品的誕生。

        負載均衡:讓每個收銀台排隊差不多長,設立小件通道、團購通道、vip通道等,這些都可認為是叢集帶來的負載均衡的問題,從技術層面上實作自然比生活中複雜得多。

        根據qos(quality of service,服務品質)配置設定資源:部分員工僅在晚上加班的機制在生活中不難實作,但對網際網路應用而言,就是一件複雜而且極具挑戰的事。

        而且生活中面對使用者增長的情況下,想出這些招應該不難。不過要掌握以上四點涉及的技術就相當複雜了,而且網際網路中涉及的其他很多技術還沒有在這個例子中展現出來。例如緩存、cdn等優化手段;運轉狀況監測、功能降級、資源劣化、流控等可用性手段;自建機房、硬體組裝等成本控制手段。是以,建構一個網際網路網站确實是不容易的,技術含量十足,當然,經營一家超市也不簡單。

        服務拆分之後,如何取得我需要的服務呢?

        在“電視機”上,把每個叢集能提供的服務顯示出來。你不需要關心哪個人為你服務,當你有需要的時候,頭頂的電視機會告訴你哪個服務在哪個區域。當你去到這個區域時,系統會給你找到一個最快的服務通道。

《淘寶技術這十年》讀書筆記 (四). 分布式時代和中間件

        這就是hsf(high-speed service framework)的設計思想:

服務的提供者啟動時通過hsf架構向configserver(類似超市的電視機)注冊服務資訊(接口、版本、逾時時間、序列化方式等),configserver上定義了所有可供調用的服務(同一個服務也可能有不同的版本);

        服務調用者啟動時向configserver注冊對哪些服務感興趣(接口、版本),當服務提供者的資訊變化時,configserver向趕興趣的服務調用者推送新的服務資訊清單;調用者在調用時則根據服務資訊的清單直接通路相應的服務提供者,無須經過configserver。

        我們注意configserver并不會把服務提供者的ip位址推送給服務的調用者,hsf架構會根據負載狀況來選擇具體的伺服器,傳回結果給調用者,這不僅統一了服務調用的方式,也實作了“軟負載均衡”。平時configserver通過和服務提供者的心跳來感應服務提供者的存活狀态。

        在hsf的支援下,服務叢集對調用者來說是“統一”的,服務之間是“隔離”的,這保證了服務的擴充性和應用的統一性。再加上hsf本身提供的“軟負載均衡”,服務層對應用層來說就是一片“私有雲”了。

《淘寶技術這十年》讀書筆記 (四). 分布式時代和中間件

        hsf架構以sar包的方式部署到jboss、jetty或tomcat下,在應用啟動時,hsf(high-speed service framework,在開發團隊内部有一些人稱hsf為“好舒服”)服務随機啟用。hsf旨在為淘寶的應用提供一個分布式的服務架構,hsf從分布式應用層面以及統一的釋出/調用方式層面為大家提供支援,更容易地開發分布式應用或使用公用功能子產品,而不用考慮分布式領域中的各種細節技術,例如:遠端通訊、性能損耗、調用的透明化、同步異步調用的問題。

        hsf是一個分布式的标準service方式的rpc(remoteprocedure call protocol,遠端過程調用協定)架構。service的定義基于osgi的方式,通訊層采用tcp/ip協定。

        參考:畢玄的分布式服務架構理論博

        hsf系統目前每天承擔300億次以上的服務調用,一些讀者可能會疑問:既然淘寶的服務化是漸進式的,那麼在hsf出現之前,系統之間的調用采用什麼方式呢?

        這個有點“五花八門”。對于類目的調用方式是forest打包成一個jar包,在應用啟動時裝載到記憶體中,僅這個jar包所占用的記憶體就有800mb之多(因為淘寶的類目資料龐大),對于當時一般隻有2gb記憶體的開發機來說,加載完類目資訊後,機器運作速度就非常慢。對于使用者資訊(uic)來說,一開始調用方式是hessian接口,還有一些系統是通過webservice、socket甚至是http請求來互相調用的。

       每種調用方式都涉及各種逾時、資訊的加解/密、參數的定義等問題,由此可見,在沒有hsf之前,系統之間的調用是錯綜複雜的。而随着系統拆分得越來越多,必須由一個統一的中間層來處理這種問題,hsf就是在這種背景下誕生的。

       hsf解決了服務調用的問題,我們再提出一個很早就說過的問題:使用者在銀行的網關付錢後,銀行需要通知到支付寶,但銀行的系統不一定能發出通知;如果通知發出了,不一定能通知到;如果通知到了,不一定不重複通知一遍。

        這個狀況在支付寶持續了很長時間,非常痛苦。支付寶從淘寶剝離出來時,淘寶和支付寶之間的通信也面臨同樣的問題,支付寶架構師魯肅提出用mq(message queue)的方式來解決這個問題,我負責淘寶這邊讀取消息的子產品。但消息數量上來後,常常造成擁堵,消息的順序也會出錯,系統挂掉消息也會挂掉。

        然後魯肅提出做一個系統架構上的解決方案,把要發出的通知存到資料庫中,如果實時發送失敗,再用一個時間程式來周期性地發送這些通知,系統記錄下消息中間狀态和時間戳,這樣就保證了消息一定能發出,也一定能通知到,且通知帶有時間順序,甚至可以實作失去性的操作。

       (ps:這個技術感覺以前做android類似微信的随手拍軟體時非常适用)

        在“千島湖”項目和“五彩石”項目後,淘寶系統拆分成了很多個,他們之間也需要類似的通知。例如,拍下一件商品,在交易管理系統中完成時,它需要通知商品管理系統減少庫存,同時旺旺服務系統發送旺旺提醒,通知物流系統上門取貨,通知sns系統分享訂單,通知警察局的系統這是騙子等等。

        使用者一次請求,在底層系統可能産生10次的消息通知。這一大堆的通知資訊是異步調用的(如果同步,系統耦合在一起就達不到拆分的目的),這些消息通知需要一個強大的系統提供支援,從消息的數量級上看,比支付寶和淘寶之間的消息量又上了一個層次,于是按照類似的思路,一個更加強大的消息中間件系統就誕生了,它的名字叫做notify。

notify是一個分布式的消息中間件系統,支援消息的訂閱、發送和消費,其架構圖如下所示:

《淘寶技術這十年》讀書筆記 (四). 分布式時代和中間件

        notifyserver在configserver上注冊消息服務,消息的用戶端通過configserver訂閱消息服務。某個用戶端調用notifyserver發送一條消息,notifyserver負責把消息發送到所有訂閱這個消息的用戶端(參照hsf圖)。

        為了保證消息一定能發出,且對方一定能收到,消息資料本身就需要記錄下來,這些資訊存放在資料庫中。由于消息具有中間狀态(已發送、未發送等),應用系統通過notify可以實作分布式事物——base(基本可用basically available、軟狀态soft state、最終一緻eventually consistent)。

        notifyserver可以水準擴充,notifyclient也可以水準擴充,資料庫也可以水準擴充。從理論上講,這個消息系統的吞吐量時沒有上限的,現在notify系統每天承載了淘寶10億次以上的消息通知。

        下圖展示了建立一筆交易後,tc(交易中心)向notify發送一條消息,後續notify所完成的一系列消息通知。

《淘寶技術這十年》讀書筆記 (四). 分布式時代和中間件

        有了hsf和notify的支援,在應用級别中,整個淘寶網的系統可以拆分了,還有一個制約系統規模的更重要的因素就是資料庫,也必須拆分。

        前面講過淘寶很早就對資料進行過分庫的處理,上層系統連接配接多個資料庫,中間有一個叫做dbroute的路由來對資料進行統一通路。dbroute對資料進行多庫的操作、資料的整合,讓上層系統像操作一個資料庫一樣操作多個庫。随着資料量的增長,對于庫表的分發有了更高的要求。例如,你的商品資料到了百億級别時,任何一個庫都無法存放了,于是分成2個、4個…1024個、2048個。分成這麼多,資料能存放了,那怎麼查詢它?

        這時候,資料查詢的中間件就要能夠承擔這個重任了,它對上層來說,必須像查詢一個資料庫一樣來查詢資料,還要想查詢一個資料庫一樣快(每條查詢在幾毫秒内完成),tddl就承擔了這樣一個工作。

        另外,加上資料的備份、複制、主備切換等功能,這一套系統都在tddl中完成。在外面有些系統也用dal(資料通路層)這個概念來命名這個中間件。tddl實作了下面三個主要的特性:

        1).資料通路路由——将針對資料的讀寫請求發送到最适合的地方

        2).資料的多向非對稱複制——一次寫入,多點讀取

        3).資料存儲的自由擴充——不再受限于單台機器的容量瓶頸與速度瓶頸,平滑遷移

        下圖展示了tddl所處的位置:

《淘寶技術這十年》讀書筆記 (四). 分布式時代和中間件

        大家逐漸發現,如果按照業務的發展規模和速度,那麼使用高端存儲和小型機的oracle存儲的成本将難以控制,于是降低成本就成了必然。如何能夠在不影響業務正常發展的前提下,解決成本問題呢?

      “對一部分資料庫使用mysql”,dba們的決策是這樣,于是分布式資料層的重擔就落到了華黎的頭上。當時的需求如下:對外統一一切資料通路、支援緩存和檔案存儲系統、能夠在oracle和mysql之間自由切換、支援搜尋引擎。

那麼,如何實作分布式join(連接配接)?(跨節點後簡單join就會變成m*n台機器的合并,代價太大)如何實作高速多元度查詢?如何實作分布式事務?

        于是動手我們自己做,名字叫taobao distributed data layer(tddl,外号“頭都打了”),學習開源的amoeba proxy。這就是tddl 1.0時代。

        粗略統計下來,tddl已經走過了4年時間,滿足了近700個業務應用的使用需求。其中有交易商品評價使用者等核心資料,也有不那麼有名的中小型應用。量變産生質變,如何能夠更好地幫助這些業務以更低的成本完成業務需求,将成為資料層未來最重要的挑戰。

        最後希望文章對你有所幫助,如果文章有不足或錯誤的地方,還請海涵!文章寫到此處,感覺讀後感還是會應該以精簡為主,下次寫書籍讀後感盡量寫成一篇,而不是大量的摘抄原文。希望大家購買原書看看,非常不錯~