天天看點

[轉] 面向海量服務的設計原則和政策總結

     網際網路服務的特點就是面向海量級的使用者,面向海量級的使用者如何提供穩定的服務呢?這裡,對這幾年的一些經驗積累和平時接觸的一些理念做一個總結。

      一、原則

      1.Web服務的CAP原理

      CAP指的是三個要素:一緻性(Consistency)、可用性(Availability)、分區容忍性(Partition tolerance)。CAP原理指的是這三個要素最多隻能同時實作兩點,不可能三者兼顧,對于海量級服務,一般這是一條常記心中的基準準則。

一緻性:可以參考資料庫的一緻性。每次資訊的讀取都需要反映最新更新後的資料。

可用性:高可用性意味着每一次請求都可以成功完成并受到響應資料

分區寬容度:這個是容錯機制的要求。一個服務需要在局部出錯的情況下,沒有出錯的那部分被複制的資料分區仍然可以支援部分服務的操作,可以簡單的了解為可以很容易的線上增減機器以達到更高的擴充性,即所謂的橫向擴充能力。

      面向海量級的分布式服務設計,基本上分區容忍性(Partition tolerance)是第一要素,是以根據業務的情況,我們需要在一緻性(Consistency)和可用性(Availability)之間做取舍。對 于一些業務,譬如支付寶或财付通,一緻性會是第一考慮要素,即使是延遲的不一緻也是不可接受的,這種時候隻能犧牲可用性以保證一緻性。對于一些應用,譬如 淘寶或拍拍交易中的評價資訊,一般使用者是可以接受延遲的一緻性,這種時候可以優先考慮可用性,而用最終一緻性來保證資料一緻,譬如通過某種對帳機制。對于 一些應用,甚至一緻性都并非要求,隻需要保證差不多一緻性即可,譬如Q-zone中的農場遊戲中的偷菜。

      根據我們應用的業務需求,選擇合适的一緻性級别,以更好地保證系統的分區容忍性和可用性。

      2.柔性可用

      面向海量級的分布式服務設計,我們要意識到,一切都是不可靠的,在不可靠的環境的環境中建構可靠的應用,其中最重要的一點就是保持系統的柔性。

      1)不可靠的環境

      我們可能已經見慣一個遠端服務提供不了服務了,運作一段時間後WebServer突然不響應了,資料庫随着負載的不斷增加再放上一條SQL語句就會垮掉。 但是,硬碟壞掉、電源斷掉、光纖中斷,聽起來似乎多麼不可思議,然而,當一個海量服務需要成千上萬台伺服器、需要部署全國各地的數十個資料中心、需要橫跨 電信網通教育網三大網絡的時候,一切聽起來不可思議的事情會變成常态。一切都是不可靠的,唯一可靠的就是不可靠本身。

      2)劃分服務級别

      我們應該意識到,在這種不可靠的環境中提供完美的服務,本身就是一個神話,即使不是說完全不可能,但至少是代價高昂的,是以,當某些問題發生(環境變地不 可靠的時候),我們必須做出取舍,選擇為使用者提供使用者最關心的服務,這種服務雖然聽起來是有損的(至少是不完美的),但卻能在一定程度上滿足使用者大部分的 需求。譬如,當網絡帶寬無法為使用者提供最好的體驗而擴容又不是短期可以達到的時候,選擇降低一些非重要服務的體驗是一個比較好的選擇。

      在面向海量網際網路的設計當中,對服務進行分級,當系統變地不可靠的時候,優先提供重要優先級的服務。

      3)盡快響應

      網際網路使用者的耐心是非常有限的,如果一個頁面需要3秒以上才能看到,也許大部分使用者的第一選擇就是關掉浏覽器。在建構柔性可用的網際網路服務的時候,響應時 間大部分情況下都是需要最優先考慮。還是一句話,環境是不可靠的,當我們無法盡快從遠端服務獲得資料、當資料庫已經慢如蝸牛,也許當背景還在吭哧吭哧幹活 的時候,使用者老早已經關閉了頁面,處理傳回的資料也隻是在浪費表情,面向網際網路使用者,響應就是生命。

       二、政策

       如何讓我們的應用提供更高品質的服務呢,這裡是一些在日常開發使用到或者觀察到的一些政策的總結:

       1.資料sharding

      海量服務相應也意味着海量的使用者和海量的使用者資料,大家都知道,即使是再強大的資料庫、再強大伺服器,在單表上億規模的資料足夠讓一條簡單的SQL語句慢 如蝸牛(甚至于在百萬、千萬級别上,如果沒有采取合适的政策,都無法滿足服務要求),一般處理這種千萬上億級資料的大家基本上都會想到的就是資料 sharding,将資料切割成多個資料集,分散到多個資料庫的多個表中(譬如将使用者資料按使用者ID切割成4個資料庫每個資料庫100個表共400個 表),由于每個表資料足夠小可以讓我們的SQL語句快速地執行。而至于如何切割,實際上跟具體的業務政策有關系。

       當然,我們要認識到,這種資料sharding并非全無代價的,這也意味着我們需要做出一些折中,譬如可能很難進行跨表資料集的查詢、聯表和排序也變地非 常困難、同時資料庫client程式編寫也會變地更加複雜、保證資料一緻性在某些情況下會變地困難重重。sharding并非萬能藥,選擇是否 sharding、如何sharding、為sharding如何換用一個近似的業務描述方式,這是業務設計需要仔細考慮的問題。

       2.Cache

       經驗會告訴我們,基本上大部分系統的瓶頸會集中在IO/資料庫上,常識也告訴我們,網絡和記憶體的速度比IO/資料庫會提升甚至不止一個數量級。面向海量服 務,Cache基本上是一個必選項,分布式Cache更是一個不二選擇,根據我們的需要,我們可以選擇memcached(非持久化)、 memcachedb/Tokyo Tyrant(持久化),甚至建構自己的cache平台。

       在使用Cache上,下面是需要仔細考慮的點:

選擇合适的Cache分布算法,基本上我們會發現使用取模方式決定Cache位置是不可靠的,因為壞節點的摘除或者節點擴量會讓我們的Cache 命中率在短時間内下降到冰點,甚至會導緻系統在短期内的負載迅速增長甚至于崩潰,選擇一種合适的分布算法非常重要,譬如穩定的一緻性哈希

Cache管理:為每個應用配置獨立的Cache通常不是一個好主意,我們可以選擇在大量的機器上,隻要有空閑記憶體,則運作Cache執行個體,将Cache執行個體分成多個組,每個組就是一個完整的Cache池,而多個應用共享一個Cache池

合理的序列化格式:使用緊湊的序列化方案存儲Cache資料,盡量少存儲備援資料,一方面可以最大能力地榨取Cache的存儲使用率,另一方面, 可以更友善地進行容量預估。此外,不可避免地,随着業務的更新,存儲的資料的格式有可能會變更,序列化也需要注意向上相容的問題,讓新格式的用戶端能夠支 持舊的資料格式。

容量估算:在開始運作之前,先為自己的應用可能使用到的容量做一個容量預估,以合理地配置設定合适的Cache池,同時為可能的容量擴充提供參考。

容量監控:Cache命中率怎麼樣,Cache的存儲飽和度怎麼樣,Client的Socket連接配接數等等,對這些資料的采集和監控,将為業務的調整和容量的擴充提供了資料支援。

選擇在哪層上進行Cache,譬如資料層Cache、應用層Cache和Web層Cache,越靠近資料,Cache的通用性越高,越容易保持 Cache資料的一緻性,但相應的處理流程也越長,而越靠近使用者,Cache的通用性越差,越難保證Cache資料的一緻性,但是響應也越快,根據業務的 特點,選擇合适的Cache層是非常重要的。一般而言,我們會選擇将粗粒度、極少變更、資料對使用者不敏感(即可以一定程度上接受誤差)、并且非針對使用者級 的資料,在最靠近使用者的層面上Cache,譬如圖檔Cache、TOP X等資料;而将一些細粒度、變更相對頻繁、使用者相對敏感的資料或者是針對使用者級的資料放在靠近資料的一段,譬如使用者的Profile、關系鍊等。

       3.服務叢集

       面向海量服務,系統的橫向擴充基本上是第一要素,在我的經驗和經曆中,服務叢集需要考慮如下因素:

分層:合理地對系統進行分層,将系統資源要求不同的部分進行合理地邏輯/實體分層,一般對于簡單業務,Client層、WebServer層和 DB層已經足夠,對于更複雜業務,可能要切分成Client層、WebServer層、業務層、資料通路層(業務層和資料通路層一般傾向于在實體上處于同 一層)、資料存儲層(DB),太多的分層會導緻處理流程變長,但相應系統地靈活性和部署性會更強。

功能細粒度化:将功能進行細粒度的劃分,并使用獨立的程序部署,一方面能更有利于錯誤的隔離,另一方面在功能變更的時候避免一個功能對其他功能産生影響

快慢分離/按優先級分離部署:不同的服務具備不同的特點,有些服務通路速度快有些通路速度慢,通路慢的服務可能會阻塞住導緻整個服務不可用,有些 服務優先級别比較高(譬如打款之類的使用者比較關心的服務),有些服務優先級别低(譬如日志記錄、發郵件之類),優先級别低的服務可能會阻塞住優先級别高的 服務,在部署上将不同特點的應用分離部署避免互相影響是一個常用的做法

按資料集部署:如果每一層都允許對下一層所有的服務接口進行通路,将存在幾個嚴重的缺陷,一是随着部署服務的增長,會發現下一層必須允許數量非常 龐大的Socket連接配接進來,二是我們可能必須把不同的服務部署在不同的資料中心(DC)的不同機房上,即便是上G的光纖專線,機房間的穿梭流量也會變地 不可接受,三是每個服務節點都是全資料容量接入,并不利于做一些有效的内部優化機制,四是隻能采用代碼級控制的灰階釋出和部署。當部署規模達到一定數量級 的時候,按資料集橫向切割成多組服務集合,每組服務集合隻為特定的資料集服務,在部署上,每組服務集合可以部署在獨立的相同的資料中心(DC)上。

無狀态:狀态将為系統的橫向擴容帶來無窮盡的煩惱。對于狀态資訊比少的情況,可以選擇将全部狀态資訊放在請求發器端,對于狀态資訊比較多的情況,可以考慮維持一個統一的Session中心。

選擇合适的負載均衡器和負載均衡政策:譬如在L4上負載均衡的LVS、L7上負載均衡的Nginx、甚至是專用的負載均衡硬體F5(L4),對于 在L7上工作的負載均衡器,選擇合适的負載均衡政策也非常重要,一般讓使用者總是負載均衡到同一台後端Server是一個很好的方式

      4.灰階釋出

      當系統的使用者增長到一定的規模,一個小小功能的釋出也會産生非常大的影響,這個時候,将功能先對一小部分使用者開放,并慢慢擴充到全量使用者是一個穩妥的做法,使用灰階化的釋出将避免功能的BUG産生大面積的錯誤。如下是一些常見的灰階控制政策:

白名單控制:隻有白名單上的使用者才允許通路,一般用于全新功能Alpha階段,隻向被邀請的使用者開放功能

準入門檻控制:常見的譬如gmail出來之初的邀請碼、QQ農場開始階段的X級的黃鑽準入,同樣一般用于新功能的Beta階段,慢慢通過一步一步降低門檻,避免在開始之處由于系統可能潛在的問題或者容量無法支撐導緻整個系統的崩潰。

按資料集開放:一般常用于成熟的功能的新功能開發,避免新功能的錯誤産生大面積的影響

      5.設計自己的通信協定:二進制協定、向上/下相容

      随着系統的穩定運作通路量的上漲,慢慢會發現,一些看起來工作良好的協定性能變地不可接受,譬如基于xml的協定xml-rpc,将會發現xml解析和包 體的增大變地不可接受,即便是接近于二進制的hessian協定,多出來的字段描述資訊(按我的了解,hessian協定是類似于map結構的,包含字段 的名稱資訊)和基于文本的http頭将會使協定效率變地低下。也許,在開始合适的時候而不是到最後不得已的時候,去設計一個良好的基于二進制的高效的内部 通信協定是一個好的方式。按我的經驗,設計自己的通信協定需要注意如下幾點:

協定緊湊性,否則早晚你會為你浪費的空間痛心疾首

協定擴充性,早晚會發現舊的協定格式不能适應新的業務需求,而在早期預留變地非常地重要,基本上,參見一些常用的規範,魔術數(對于無效果的請求可以迅速丢棄)、協定版本資訊、協定頭、協定Body、每個部分(包括結構體資訊中的對象)的長度這些資訊是不能省的

向下相容和向上相容:但功能被大規模地調用的時候,釋出一個新的版本,讓所有的client同時更新基本上是不可接受的,是以在設計之處就需要考慮好相容性的問題

      6.設計自己的Application Server

      事情進行到需要自己設計通信協定,自己建構Application Server也變地順理成章,下面是在自己開發Application Server的時候需要處理的常見的問題:

過載保護:當系統的某個部件出現問題的時候,最常見的情況是整個系統的負載出現爆炸性的增長而導緻雪崩效應,在設計application server的時候,必須注意進行系統的過載保護,當請求可以預期無法處理的時候(譬如排隊滿載或者排隊時間過長),丢棄是一個明智的選擇,TCP的 backlog參數是一個典型的範例。

頻率控制:即便是同一系統中的其他應用在調用,一個糟糕的程式可能會将服務的所有資源占完,是以,應用端必須對此做防範措施,頻率控制是其中比較重要的一個

異步化/無響應傳回:對于一些業務,隻需要保證請求會被處理即可,用戶端并不關心什麼時候處理完,隻要最終保證處理就行,甚至最終沒有處理也不是很嚴重的事情,譬如郵件,對于這種應用,應快速響應避免占着寶貴的連接配接資源,而将請求進入異步處理隊列慢慢處理。

自我監控:Application Server本身應該具備自我監控的功能,譬如性能資料采集、為外部提供内部狀态的查詢(譬如排隊情況、處理線程、等待線程)等

預警:譬如當處理變慢、排隊太多、發生請求丢棄的情況、并發請求太多的時候,Application Server應該具備預警的能力以快速地對問題進行處理

子產品化、子產品間松耦合、機制和政策分離:如果不想一下子面對所有的複雜性或不希望在修改小部分而不得不對所有的測試進行回歸的話,子產品化是一個很 好的選擇,将問題進行子產品切割,每個子產品保持合理的複雜度,譬如對于這裡的Application Server,可以切分成請求接收/管理/響應、協定解析、業務處理、資料采集、監控和預警等等子產品。這裡同時要注意塊間使用松耦合的方式互動,譬如,請 求接收和業務處理之間則可以使用阻塞隊列通信的方式降低耦合。另外還需要注意的是機制和政策的分離,譬如協定可能會變更、性能采集和告警的方式可能會變化 等等,事先的機制和政策分離,政策變更的處理将變地更加簡單。

       7.Client

      很多應用會作為服務的Client,去調用其他的服務,如下是在做為Client應該注意的一些問題:

服務不可靠:作為Client永遠要記住的一點就是,遠端服務永遠是不可靠的,是以作為Client自己要注意做自我保護,當遠端服務如果無法通路時,做折中處理

逾時保護:還是上面所說的,遠端服務永遠都是不可靠的,永遠也無法預測到遠端什麼時候會響應,甚至可能不會響應(譬如遠端主機當機),請求方要做好逾時保護,譬如對于主機不可達的情況,在linux環境下,有時會讓用戶端等上幾分鐘TCP層才會最終告訴你服務不可到達。

并發/異步:為了提速響應,對于很多可以并行擷取的資料,我們總是應該并行地去擷取,對于一些我們無法控制的同步接口——譬如讀資料庫或同步讀 cache——雖然不是很完美,但多線程并行去擷取是一個可用的選擇,而對于服務端都是使用自建構的Application Server,使用異步Client接口至關重要,将請求全部發送出去,使用異步IO設定逾時等待傳回即可,甚至于更進一步異步anywhere,在将 client與application server整合到一塊的時候,請求發送出去之後立即傳回,将線程/程序資源歸還,而在請求響應回來符合條件的時候,觸發回調做後續處理。

      8.監控和預警

      基本上我們會見慣了各種網絡裝置或伺服器的監控,譬如網絡流量、IO、CPU、記憶體等監控資料,然而除了這些總體的運作資料,應用的細粒度化的資料也需要 被監控,服務的通路壓力怎麼樣、處理速度怎麼樣、性能瓶頸在哪裡、帶寬主要是被什麼應用占、Java虛拟機的CPU占用情況怎麼樣、各記憶體區的記憶體占用情 況如何,這些資料将有利于我們更好的了解系統的運作情況,并對系統的優化和擴容提供資料指導。

      除了應用總體監控,特定業務的監控也是一個可選項,譬如定時檢查每個業務的每個具體功能點(url)通路是否正常、通路速度如何、頁面通路速度如何(使用者 角度,包括服務響應時間、頁面渲染時間等,即網頁測速)、每個頁面的PV、每個頁面(特别是圖檔)每天占用的總帶寬等等。這些資料将為系統預警和優化提供 資料上的支援,例如對于圖檔,如果我們知道哪些圖檔占用的帶寬非常大(不一定是圖檔本身比較大,而可能是通路比較大),則一個小小的優化會節省大量的網絡 帶寬開銷,當然,這些事情對于小規模的通路是沒有意義的,網絡帶寬開銷節省的成本可能都沒有人力成本高。

      除了監控,有效的預警機制也是必不可少,應用是否在很好地提供服務、響應時間是否能夠達到要求、系統容量是否達到一個閥值。有效的預警機制将讓我們盡快地對問題進行處理。

      9.配置中心化

      當系統錯誤的時候,我們如何盡快地恢複呢,當新增服務節點的時候,如何盡快地讓真個系統感覺到呢?當系統膨脹之後,如果每次摘除服務節點或者新增節點都需要修改每台應用配置,那麼配置和系統的維護将變地越來越困難。

     配置中心化是一個很好的處理這個問題的方案,将所有配置進行統一地存儲,而當發生變更的時候(摘除問題節點或者擴量增加服務節點或新增服務),使用一些通知機制讓各應用重新整理配置。甚至于,我們可以自動地檢測出問題節點并進行智能化的切換。

      三、最後

      建構面向海量使用者的服務,可以說是困難重重挑戰重重,一些原則和前人的設計思路可以讓我們獲得一些幫助,但是更大的挑戰會來源于細節部分,按我們技術老大 的說法,原則和思路隻要看幾本書是個技術人員都會,但決定一個系統架構師能力的,往往卻是對細節的處理能力。是以,在掌握原則和前人的設計思路的基礎上, 更深入地挖掘技術的細節,才是面向海量使用者的服務的制勝之道。

轉自:http://ayufox.iteye.com/blog/676416

本文轉自 zhenjing 部落格園部落格,原文連結:   http://www.cnblogs.com/zhenjing/archive/2012/01/19/2327645.html,如需轉載請自行聯系原作者