當前位置:
首頁 > 知識 > 快速成長期的雲原生應用架構實踐

快速成長期的雲原生應用架構實踐

快速成長期的雲原生應用架構實踐

在經過了最初的業務原型驗證和上線運行期之後,用戶業務進入了高速成長階段。在這一階段,業務重點不再是方向上的調整,而是在原來基礎上的不斷深挖、擴展;開發不僅是功能的實現,還需要兼顧成本和性能;系統不再是單體架構,還會涉及系統的擴展和多系統之間的通信;高可用也不僅是服務自動拉起或者並行擴展,還需要考慮數據可靠、對用戶影響,以及服務等級協議(SLA)。

本文將以上述挑戰為出發點,介紹如何通過引入新的工具、新的架構,對原有系統進行升級和優化,來更好滿足這一階段需求,並為產品的進一步發展打下基礎。

關鍵業務需求



隨著用戶業務的發展,原來的功能已經無法滿足要求,需要增強或者增加新的功能。在用戶數和訪問量達到一定規模後,原先單體架構下的簡單功能,如計數和排序,將變得複雜;隨著業務深入,定期舉行的秒殺、促銷等活動,給系統帶了巨大的壓力;由於數據量的飛速增長,單純的資料庫或者內存檢索已經無法滿足不斷增加的各種查詢需求;隨著業務數據量的增加,產品價值的提高,如何收集系統運行數據,分析業務運行狀態也成了基本需求。接下來我們聚焦這一階段的關鍵業務需求,並給出相應的解決方案。

計數與排序

在單體架構下,通過簡單的內存數據和對應演算法就可以實現計數和排序功能。但是在大量數據和多節點協作的環境下,基於單點內存操作的實現會遇到高並發、數據同步、實時獲取等問題。在這一階段,通用方法是使用Redis的原生命令來實現計數和排序。

計數

在Redis中可用於計數的對象有字元串(string)、哈希表(hash)和有序集合(zset)3種,對應的命令分別是incr/incrby、hincrby和zincrby。

網站可以從用戶的訪問、交互中收集到有價值的信息。通過記錄各個頁面的被訪問次數,我們可以根據基本的訪問計數信息來決定如何緩存頁面,從而減少頁面載入時間並提升頁面的響應速度,優化用戶體驗。

計數器

要實現網頁點擊量統計,需要設計一個時間序列計數器,對網頁的點擊量按不同的時間精度(1s、5s、1min、5min、1h、5h、1d等)計數,以對網站和網頁監視和分析。

數據建模以網頁的地址作為KEY,定義一個有序集合(zset),內部各成員(member)分別由計數器的精度和計數器的名字組成,所有成員的分值(score)都是0。這樣所有精度的計數器都保存在了這個有序集合里,不包含任何重複元素,並且能夠允許一個接一個地遍歷所有元素。

對於每個計數器及每種精度,如網頁的點擊量計數器和5s,設計使用一個哈希表(hash)對象來存儲網頁在每5s時間片之內獲得的點擊量。其中,哈希表的每個原生的field都是某個時間片的開始時間,而原生的field對應的值則存儲了網頁在該時間片內獲得的點擊量。如圖1所示。

  • 更新計數器信息示例代碼。

PRECESION = [1, 5, 60, 300, 3600, 18000, 86400] def update_counter(conn, name, count=1, now=None):

快速成長期的雲原生應用架構實踐

圖1 網頁點擊計數器的實現

  • 獲取計數器信息示例代碼。

def get_counter(conn, name, precision): ----hash = "%s:%s" % (precision, name)

當然,這裡只介紹了網頁點擊量數據的存儲模型,如果我們一味地對計數器進行更新而不執行任何清理操作的話,那麼程序最終將會因為存儲了過多的數據而導致內存不足,由於我們事先已經將所有已知的計數器都記錄到一個有序集合裡面,所以對計數器進行清理只需要遍歷這個有序集合,並刪除其中的舊計數器即可。

排序

在Redis中可用於排序的有天然有序的有序集合(zset)和鍵(keys)類型中的SORT命令,其中SORT命令的功能非常強大,不僅可以對列表(list)、集合(set)和有序集合(zset)進行排序,還可以完成與關係型資料庫中的連接查詢相類似的任務,下面分別以兩個例子來介紹各自的應用。

帖子排序

論壇中的帖子通常會有各種排序方式方便用戶查看,比如按發帖時間排序、按回復時間排序、按回複數量排序、按閱讀量排序等,這些TOP N場景對響應時間要求比較高,非常適宜用有序集合(zset)來緩存排序信息,其中排序欄位即為分值(score)欄位。

  • 例子

127.0.0.1:6379> zadd page_rank 10 google.com 8 bing.com 6 163.com 9 baidu.com

SORT命令

SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]

SORT命令提供了多種參數,可以對列表,集合和有序集合進行排序,此外還可以根據降序升序來對元素進行排序(DESC、ASC);將元素看作是數字還是二進位字元串來進行排序(ALPHA);使用排序元素之外的其他值作為權重來進行排序(BY pattern)。

下面代碼清單展示了SORT命令的具體功能使用。

  • 對列表(list)進行排序

1.順序

127.0.0.1:6379> lpush mylist 30 10 8 19

2.逆序

127.0.0.1:6379> sort price desc

3.使用alpha修飾符對字元串進行排序

127.0.0.1:6379> lpush website www.163.com www.kaola.com www.baidu.com

  • 使用limit修飾符限制返回結果

127.0.0.1:6379> rpush num 1 4 2 7 9 6 5 3 8 10

  • 使用外部key進行排序

可以使用外部key的數據作為權重,代替默認的直接對比鍵值的方式來進行排序。假設現在有用戶數據如表1所示。


uid user_name_{uid} user_level_{uid}
1 helifu 888
2 netease 666
3 kaola 777
4 ncr 4444

表1 用戶數據示例

以下將哈希表(hash)作為by和get的參數,by和get選項都可以用key->field的格式來獲取哈希表中的域的值,其中key表示哈希表鍵,而field則表示哈希表的域。

1.數據輸入到Redis中

127.0.0.1:6379> hmset user_info_1 name helifu level 888

2.by選項

通過使用by選項,讓uid按其他鍵的元素來排序。

例如以下代碼讓uid鍵按照user_info_*->level的大小來排序。

127.0.0.1:6379> sort uid by user_info_*->level

3.get選項

使用get選項,可以根據排序的結果來取出相應的鍵值。

例如以下代碼先讓uid鍵按照user_info_*->level的大小來排序,然後再取出

user_info_ *->name的值。 127.0.0.1:6379> sort uid by user_info_*->level get user_info_*->name

現在的排序結果要比只使用by選項要直觀得多。

4.排序獲取多個外部key

可以同時使用多個get選項,獲取多個外部鍵的值。

127.0.0.1:6379> sort uid get # get user_info_*->level get user_info_*->name

5.不排序獲取多個外部key

127.0.0.1:6379> sort uid by not-exists-key get # get user_info_*->level get user_info_*->name 1) "4" 2) "4444" 3) "nc" 4) "3" 5) "777" 6) "kaola" 7) "2" 8) "666" 9) "netease" 10) "1" 11) "888" 12) "helifu"

  • 保存排序結果

127.0.0.1:6379> lrange old 0 -1 1) "1" 2) "3" 3) "5" 6) "2" 7) "4"

SORT命令的時間複雜度用公式表示為O(N+M*log(M)),其中N為要排序的列表或集合內的元素數量,M為要返回的元素數量。如果只是使用SORT命令的get選項獲取數據而沒有進行排序,時間複雜度為O(N)。

雲環境下的實踐

在雲服務中實現計數和排序,可以自己使用雲主機搭建Redis服務,也可以使用雲計算服務商提供的Redis服務。

對於高可用和性能有要求的場景,建議使用雲計算服務商提供的Redis服務。專業的服務商會從底層到應用本身進行良好的優化,可用率、性能指標也遠高於自己搭建的Redis實例。同時,由於服務商提供了各種工具,開發運維成本也更低。

以網易云為例,網易雲基礎服務提供了名為NCR(Netease Cloud Redis)的緩存服務,兼容開源Redis協議。並根據用戶具體使用需求和場景,提供了主從版本和分散式集群版本兩種架構。

主從服務版

如圖2所示,主從版本實例都提供一主一從兩個Redis實例,分別部署在不同可用域的節點上,以確保服務安全可靠。在單點故障時,主從服務通過主備切換來實現高可用。

主從版本使用較低的成本提供了高可用服務,但是也存在無法並行擴展等問題,因此適合數據量有限、對高可用有要求的產品使用。

快速成長期的雲原生應用架構實踐

圖2 主從服務架構

分散式集群

分散式集群採用官方Redis集群方案,gossip/p2p的無中心節點設計實現,無代理設計客戶端直接與Redis集群的每個節點連接,計算出Key所在節點直接在對應的Redis節點上執行命令,如圖3所示,詳細的過程請參考後續Redis Cluster的相關介紹。

分散式集群採用多活模式,支持並行擴展,因此在性能、可用率方面有明顯優勢。但是由於分散式集群最少需要3個節點,因此成本會較高,適合對可用率、性能有較高要求的用戶使用。

快速成長期的雲原生應用架構實踐

圖3 分散式集群架構

秒殺

把秒殺服務單列出來進行分析,主要有下面兩個原因。

  • 秒殺服務的重要性:秒殺活動本身已經是很多業務推廣的重要方式之一,大部分的電商類業務都會涉及這一促銷方式。很多非直接秒殺的業務(如火車購票),在實際運行時也會碰到類似秒殺的場景。秒殺實際上就是在瞬時極大並發場景下如何保證系統正常運行的問題,而這種場景對很多系統都是無法避免的,因此在系統設計時,我們往往要考慮到秒殺的影響。

  • 系統實現難度:秒殺最能考驗系統負載能力,瞬間湧入平時數十倍甚至數百倍的壓力,對開發和運維人員來說都是噩夢,這也為系統設計帶來了巨大的挑戰。針對秒殺活動的處理,是一個系統性的設計,並不是單一模塊或者層面可以解決的問題,需要從系統設計整體進行考量。

處理秒殺的指導思路

秒殺的核心問題就是極高並發處理,由於系統要在瞬時承受平時數十倍甚至上百倍的流量,這往往超出系統上限,因此處理秒殺的核心思路是流控和性能優化。

流控

  • 請求流控

儘可能在上游攔截和限制請求,限制流入後端的量,保證後端系統正常。

因為無論多少人參與秒殺,實際成交往往是有限的,而且遠小於參加秒殺的人數,因此可以通過前端系統進行攔截,限制最終流入系統的請求數量,來保證系統正常進行。

  • 客戶端流控

在客戶端進行訪問限制,較為合適的做法是屏蔽用戶高頻請求,比如在網頁中設置5s一次訪問限制,可以防止用戶過度刷介面。這種做法較為簡單,用戶體驗也尚可,可以攔截大部分小白用戶的異常訪問,比如狂刷F5。關鍵是要明確告知用戶,如果像一些搶購系統那樣假裝提交一個排隊頁面但又不回應任何請求,就是赤裸裸的欺騙了。

  • Web端流控

對客戶端,特別是頁面端的限流,對稍有編程知識或者網路基礎的用戶而言沒有作用(可以簡單修改JS或者模擬請求),因此服務端流控是必要的。服務端限流的配置方法有很多種,現在的主流Web伺服器一般都支持配置訪問限制,可以通過配置實現簡單的流控。

但是這種限制一般都在協議層。如果要實現更為精細的訪問限制(根據業務邏輯限流),可以在後端伺服器上,對不同業務實現訪問限制。常見做法是可以通過在內存或緩存服務中加入請求訪問信息,來實現訪問量限制。

  • 後端系統流控

上述的流控做法只能限制用戶異常訪問,如果正常訪問的用戶數量很多,就有後端系統壓力過大甚至異常宕機的可能,因此需要後端系統流量控制。

對於後端系統的訪問限制可以通過非同步處理、消息隊列、並發限制等方式實現。核心思路是保證後端系統的壓力維持在可以正常處理的水平。對於超過系統負載的請求,可以選擇直接拒絕,以此來對系統進行保護,保證在極限壓力的情況下,系統有合理範圍內的處理能力。

系統架構優化

除了流控之外,提高系統的處理能力也是非常重要的,通過系統設計和架構優化,可以提高系統的吞吐量和抗壓能力。關於通用系統性能的提升,已經超出本節的範圍,這裡只會提幾點和秒殺相關的優化。

  • 讀取加速:在秒殺活動中,數據需求一般都是讀多寫少。20萬人搶2000個商品,最後提交的訂單最多也就2000個,但是在秒殺過程中,這20萬人會一直產生大量的讀取請求。因此可以使用緩存服務對用戶請求進行緩存優化,把一些高頻訪問的內容放到緩存中去。對於更大規模的系統,可以通過靜態文件分離、CDN服務等把用戶請求分散到外圍設施中去,以此來分擔系統壓力。

  • 非同步處理和排隊:通過消息隊列和非同步調用的方式可以實現介面非同步處理,快速響應用戶請求,在後端有較為充足的時間來處理實際的用戶操作,提高對用戶請求的響應速度,從而提升用戶體驗。通過消息隊列還可以隔離前端的壓力,實現排隊系統,在湧入大量壓力的情況下保證系統可以按照正常速率來處理請求,不會被流量壓垮。

  • 無狀態服務設計:相對於有狀態服務,無狀態服務更容易進行擴展,實現無狀態化的服務可以在秒殺活動前進行快速擴容。而雲化的服務更是有著先天的擴容優勢,一般都可以實現分鐘級別的資源擴容。

系統擴容

這項內容是在雲計算環境下才成為可能,相對於傳統的IT行業,雲計算提供了快速的系統交付能力(min VS. day),因此可以做到按需分配,在業務需要時實現資源的並行擴展。

對一次成功的秒殺活動來說,無論如何限流,如何優化系統,最終產生數倍於正常請求的壓力是很正常的。因此臨時性的系統擴容必不可少,系統擴容包括以下3個方面。

  • 增加系統規格:可以預先增加系統容量,比如提高系統帶寬、購買更多流量等。

  • 服務擴展:無狀態服務+負載均衡可以直接進行水平擴展,有狀態的服務則需要進行較為複雜的垂直擴展,增大實例規格。

  • 後端系統擴容:緩存服務和資料庫服務都可以進行容量擴展。

秒殺服務實踐

一般來說,流控的實現,特別是業務層流控,依賴於業務自身的設計,因此雲計算提供的服務在於更多、更完善的基礎設計,來支持用戶進行更簡單的架構優化和擴容能力。

系統架構優化

通過CDN服務和對象存儲服務來分離靜態資源,實現靜態資源的加速,避免伺服器被大量靜態資源請求過度佔用。要實現非同步的消息處理,可以使用隊列服務來傳輸消息,以達到消息非同步化和流控。

系統擴容

雲服務會提供按需計費的資源分配方式和分鐘級甚至秒級的資源交付能力,根據需要快速進行資源定製和交付。

內部系統可以通過負載均衡等服務實現並行擴展,在網易雲基礎服務中,用戶可以直接使用Kubernetes的Replication Controller服務實現在線水平擴容。對於對外提供的Web系統,可以通過負載均衡服務實現水平在線擴展。

對於後端系統來說,建議使用雲計算服務商提供的基礎服務來實現並行擴展。例如,網易雲基礎服務就提供了分散式緩存服務和資料庫服務,支持在線擴容。

全文檢索

搜索,是用戶獲取信息的主要方式。在日常生活中,我們不管是購物(淘寶)、吃飯(大眾點評、美團網)還是旅遊(攜程、去哪兒),都離不開搜索的應用。搜索幾乎成為每個網站、APP甚至是操作系統的標配。在用戶面前,搜索通常只是展示為一個搜索框,非常乾淨簡潔,但它背後的原理可沒那麼簡單,一個框的背後包含的是一整套搜索引擎的原理。假如我們需要搭建一個搜索系統為用戶提供服務,我們又需要了解什麼呢?

基本原理

首先,我們需要知道全文檢索的基本原理,了解全文檢索系統在實際應用中是如何工作的。

通常,在文本中查找一個內容時,我們會採取順序查找的方法。譬如現在手頭上有一本計算機書籍,我們需要查找出裡面包含了「計算機」和「人工智慧」關鍵字的章節。一種方法就是從頭到尾閱讀這本計算機書籍,在每個章節都留心是否包含了「計算機」和「人工智慧」這兩個詞。這種線性掃描就是最簡單的計算機文檔檢索方式。這個過程通常稱為grepping,它來自於Unix下的一個文本掃描命令grep。在文本內進行grepping掃描很快,使用現代計算機會更快,並且在掃描過程中還可以通過使用正則表達式來支持通配符查找。總之,在使用現代計算機的條件下,對一個規模不大的文檔集進行線性掃描非常簡單,根本不需要做額外的處理。但是,很多情況下只採用上述掃描方式是遠遠不夠的,我們需要做更多的處理。這些情況如下所述。

  • 大規模文檔集條件下的快速查找。用戶的數據正在進行爆發性的增長,我們可能需要在幾十億到上萬億規模下的數據進行查找。

  • 有時我們需要更靈活的匹配方式。比如,在grep命令下不能支持諸如「計算機NEAR人工智慧」之類的查詢,這裡的NEAR操作符的定義可能為「5個詞之內」或者「同一句子中」。

  • 需要對結果進行排序。很多情況下,用戶希望在多個滿足自己需求的文檔中得到最佳答案。

此時,我們不能再採用上面的線性掃描方式。一種非線性掃描的方式是事先給文檔建立索引(Index)。回到上面所述的例子,假設我們讓計算機對整書本預先做一遍處理,找出書中所有的單詞都出現在了哪幾個章節(由於單詞會重複,通常都不會呈現爆炸式增長),並記錄下來。此時再查找「計算機」和「人工智慧」單詞出現在哪幾個章節中,只需要將保存了它們出現過的章節做合併等處理,即可快速尋找出結果。存儲單詞的數據結構在信息檢索的專業術語中叫「倒排索引」(Inverted Index),因為它和正向的從章節映射到單詞關係相反,是倒著的索引映射關係。

這種先對整個文本建立索引,再根據索引在文本中進行查找的過程就是全文檢索(Full-text Search)的過程。圖4展示了全文檢索的一般過程。

快速成長期的雲原生應用架構實踐

圖4 全文檢索的一般過程

首先是數據收集的過程(Gather Data),數據可以來源於文件系統、資料庫、Web抓取甚至是用戶輸入。數據收集完成後我們對數據按上述的原理建立索引(Index Documents),並保存至Index索引庫中。在圖的右邊,用戶使用方面,我們在頁面或API等獲取到用戶的搜索請求(Get Users』 Query),並根據搜索請求對索引庫進行查詢(Search Index),然後對所有的結果進行打分、排序等操作,最終形成一個正確的結果返回給用戶並展示(Present Search Results)。當然在實現流程中還包含了數據抓取/爬蟲、鏈接分析、分詞、自然語言處理、索引結構、打分排序模型、文本分類、分散式搜索系統等技術,這是最簡單抽象的流程描述。關於索引過程和搜索過程更詳細的技術就不做更多介紹了,感興趣的同學請參考其他專業書籍。

開源框架

根據上面的描述我們知道了全文檢索的基本原理,但要是想自己從頭實現一套搜索系統還是很困難的,沒有一個專業的團隊、一定的時間基本上做出不來,而且系統實現之後還需要面臨生產環境等各種問題的考驗,研發和維護成本都無比巨大。不過,現代的程序開發環境早已今非昔比,開源思想深入人心,開源軟體大量湧現。沒有特殊的需求,沒有人會重新開發一套軟體。我們可以站在開源巨人的肩膀上,直接利用開源軟體的優勢。目前市面上有較多的開源搜索引擎和框架,比較成熟和活躍的有以下幾種。

  • Lucene

  • Solr

  • Elasticsearch

  • Sphinx

我們分別介紹這幾種開源方案,並比較一下它們的優劣。

Lucene

Lucene是一個Java語言開發的搜索引擎類庫,也是目前最火的搜索引擎內核類庫。但需要注意的是,Lucene本身還不是一套完整的搜索引擎解決方案,它作為一個類庫只是包含了核心的搜索、索引等功能,如果想使用Lucene,還需要做一些額外的開發工作。

優點:

  • Apache頂級項目,仍在持續快速進步。

  • 成熟的解決方案,有很多成功案例。

  • 龐大而活躍的開發社區,大量的開發人員。

  • 雖然它只是一個類庫,但經過簡單的定製和開發,就可以滿足絕大部分常見的需求;經過優化,可以支持企業級別量級的搜索。

缺點:

  • 需要額外的開發工作。系統的擴展、分散式、高可用性等特性都需要自己實現。

Solr

Solr是基於Lucene開發、並由開發Lucene的同一幫人維護的一套全文搜索系統,Solr的版本通常和Lucene一同發布。Solr是最流行的企業級搜索引擎之一。

優點:

  • 由於和Lucene共同維護,Solr也有一個成熟、活躍的社區。

  • Solr支持高級搜索特性,比如短語搜索、通配符搜索、join、group等。

  • Solr支持高吞吐流量、高度可擴展和故障恢復。

  • 支持REST風格API,支持JSON、XML、CSV甚至二進位格式的數據;功能強大的綜合管理頁面、易於監控。

  • Solr已比較成熟、穩定。

缺點:

  • 系統部署、配置及管理配置上的使用較為複雜。

Elasticsearh

Elasticsearch是一個實時的分散式搜索和分析引擎,和Solr一樣,它底層基於Lucene框架。但它具有簡單、易用、功能/性能強大等優點,雖然晚於Solr出現,但迅速成長,已成為目前當仁不讓的最熱門搜索引擎系統。

優點:

  • 支持REST風格的HTTP API,並且事件完全受API驅動,幾乎所有的操作都可以通過使用JSON格式的RESTful HTTP API完成。

  • 支持多租戶/多索引(multi-tenancy),支持全面的高級搜索特性。

  • 索引模式自由(Schema Free),支持JSON格式數據;部署、配置簡單,便於使用。

  • 強大的聚合(Aggregation)分析功能(取代Lucene傳統的Facets),便於用戶對數據進行統計分析。

缺點:

  • 幾乎無缺點,在性能和資源上較C++ 開發的Sphinx稍差。

Sphinx

Sphinx是基於C++ 開發的一個全文檢索引擎,從最開始設計時就注重性能、搜索相關性及整合簡單性等方面。Sphinx可以批量索引和搜索SQL資料庫、NoSQL存儲中的數據,並提供和SQL兼容的搜索查詢介面。

優點:

  • Sphinx採用C++ 開發,因此支持高速的構建索引及高性能的搜索。

  • 支持SQL/NoSQL搜索。

  • 方便的應用集成。

  • 更高級的相似度計算排序模型。

缺點:

  • 社區、生態等不如Lucene系發達,可見的成功案例較少。

應用選型

通過上面的介紹,相信大家對各個開源搜索系統所適用的場景有了一定了解。

如果是中小型企業,想快速上手使用搜索功能,可以選擇Elasticsearch或Solr。如果對搜索性能和節省資源要求比較苛刻,可以考慮嘗試Sphinx。如果有很多定製化的搜索功能需求,可以考慮在Lucene基礎上做自定義開發。如果用於日誌搜索,或者有很多的統計、分析等需求,可以選擇Elasticsearch。

開源方案實踐

如上述介紹,在實際開發當中已有了很多現成的開源方案可供選擇,但我們還是需要額外再做一些事情。譬如集群搭建、維護和管理、高可用設計、故障恢復、最基本的機房申請及機器採購部署等。這也需要投入較高的人力和成本(雖然較自己研發已經節省很多),並且還需要配備專業的搜索開發及運維人員。

而在網易雲中,我們基於開源Elasticsearch系統提供了一套簡單的方案。我們把專業、複雜的搜索系統服務化和簡單化,並降低准入門檻和成本,讓用戶可以直接使用平台化的搜索服務。我們提供了全面的近實時、高可用、高可靠、高擴展等搜索系統強大功能,易於用戶使用。用戶使用網易雲的雲搜索後不再需要處理搜索系統的搭建與配置工作,而只需要在雲搜索服務的產品管理平台申請建立服務實例並配置索引數據格式,申請完成後雲搜索平台就會自動生成索引服務實例並提供全文檢索服務。

日誌收集

日誌,一組隨時間增加的有序記錄,是開發人員最熟悉的一種數據。通常,日誌可以用來搜索查看關鍵狀態、定位程序問題,以及用於數據統計、分析等。

日誌也是企業生產過程中產生的偉大財富。日誌可以用來進行商業分析、用戶行為判斷和企業戰略決策等,良好地利用日誌可以產生巨大的價值。所以,日誌的收集不管是在開發運維還是企業決策中都十分重要。

第三方數據收集服務

在日誌收集領域內,目前已經存在了種類繁多的日誌收集工具,比較典型的有:rsyslog、syslog-ng、Logstash、FacebookScribe、ClouderaFlume、Fluentd和GraylogCollector等。

rsyslog是syslog的增強版,Fedora、Ubuntu、RHEL6、CentOS6、Debian等諸多Linux發行版都已使用rsyslog替換syslog作為默認的日誌系統。rsyslog採用C語言實現,佔用的資源少,性能高。專註於安全性及穩定性,適用於企業級別日誌記錄需求。rsyslog可以傳輸100萬+/s(日誌條數)的數據到本地目的地。即使通過網路傳輸到遠程目的地,也能達到幾萬至幾十萬條/每秒的級別。rsyslog默認使用inotify實現文件監聽(可以更改為polling模式,實時性較弱),實時收集日誌數據。

rsyslog支持實時監聽日誌文件、支持通配符匹配目錄下的所有文件(支持輸出通配符匹配的具體文件名)、支持記錄文件讀取位置、支持文件Rotated、支持日誌行首格式判斷(多行合併成一行)、支持自定義tag、支持直接輸出至file/mysql/kafka/elasticsearch等、支持自定義日誌輸出模板,以及支持日誌數據流控。

syslog-ng具有開源、可伸縮和可擴展等特點,使用syslog-ng,你可以從任何來源收集日誌,以接近實時的處理,輸出到各種各樣的目的源。syslog-ng靈活地收集、分析、分類和關聯日誌,存儲和發送到日誌分析工具。syslog-ng支持常見的輸入,支持BSDsyslog(RFC3164)、RFC5424協議、JSON和journald消息格式。數據提取靈活,內置一組解析器,可以構建非常複雜事情。簡化複雜的日誌數據,syslog-ng patterndb可以轉化關聯事件為一個統一格式。支持資料庫存儲,包括SQL(MySQL,PostgreSQL,Oracle)、MongoDB和Redis。syslog-ng支持消息隊列,支持高級消息隊列協議(AMQP)和面向簡單的文本消息傳遞協議(STOMP)與更多的管道。syslog-ng設計原則主要包括更好消息過濾粒度和更容易在不同防火牆網段轉發信息。前者能夠進行基於內容和優先權/facility的過濾。後者支持主機鏈,即使日誌消息經過了許多計算機的轉發,也可以找出原發主機地址和整個轉發鏈。

Logstash是一個開源的伺服器端數據處理管道,採集多種數據源的數據,轉換後發送到指定目的源。數據通常以各種格式分散在許多孤立系統上。Logstash支持種類豐富的inputs,以事件的方式從各種源獲取輸入,包括日誌、Web應用、數據存儲設備和AWS服務等。在數據流從源到存儲設備途中,Logstash過濾事件,識別欄位名,以便結構化數據,更有利於數據分析和創造商業價值。除了Elasticsearch,Logstash支持種類豐富的outputs,可以把數據轉發到合適的目的源。表2是幾個典型產品的特性對比。


名稱 開發語言 性能 所佔資源 支持I/O插件種類 社區活躍度
rsyslog C
syslog-ng C
LogStash/Beats LogStash:Ruby Beats:Go
FacebookScribe C++
ClouderaFlume Java
Fluentd Ruby

表2 典型日誌收集產品特性比較

技術選型

由於日誌收集的需求並非很複雜,此類工具大體上比較相似,用戶只需要根據其特性選擇合適自己需求的產品。

通常來說,對於日誌收集客戶端資源佔用要求較高的,可以選擇C語音開發的rsyslog、syslog-ng。對於易用性要求較高,可以選擇Logstash、Beats。對於日誌收集後接入的後端有特殊需求,可以參考Fluentd是否可以滿足。如果公司用的是Java技術棧,可以選用Cloudera Flume。

架構實踐



除了基本的功能需求之外,一個互聯網產品往往還有訪問性能、高可用、可擴展等需要,這些統稱為非功能需求。一般來說,功能需求往往可以通過開發業務模塊來滿足,而非功能需求往往要從系統架構設計出發,從基礎上提供支持。

隨著用戶的增加,系統出現問題的影響也會增大。試想一下,一個小公司的主頁,或者個人開發維護的一個App的無法訪問,可能不會有多少關注。而支付寶、微信的宕機,則會直接被推到新聞頭條(2015年支付寶光纖被挖路機挖斷),並且會給用戶帶來嚴重的影響:鼓足勇氣表白卻發現信息丟失?掏出手機支付卻發生支付失敗,關鍵是還沒帶現金!在用戶使用高峰時,一次故障就會給產品帶來很大的傷害,如果頻繁出現故障則基本等同於死刑判決。

同樣,對一個小產品來說,偶發的延時、卡頓可能並不會有大的影響(可能已經影響到了用戶,只是範圍、概率較小)。而對於一個較為成熟的產品,良好的性能則是影響產品生死存亡的基本問題。試想一下,如果支付寶、微信經常出現卡頓、變慢,甚至在訪問高峰時崩潰,那它們還能支撐起現在的用戶規模,甚至成為基礎的服務設施嗎?可以說,良好的訪問性能,是一個產品從幼稚到成熟所必須解決的問題,也是一個成功產品的必備因素。實際上,很多有良好創意、商業前景很好的互聯網產品,就是因無法滿足用戶增長帶來的性能壓力而夭折。

隨著性能需求的不斷增長,所需要考慮的因素越多,出問題的概率也越大。因此,用戶數的不斷增長帶來的挑戰和問題幾乎呈幾何倍數增加,如果沒有良好的設計和規劃,隨著產品和業務的不斷膨脹,我們往往會陷入「修改→引入新問題→繼續調整→引入更多問題」的泥潭中無法自拔。

在這一階段,架構設計的重點不再是業務本身功能實現和架構的構建,而是如何通過優化系統架構,來滿足系統的高可用、並行擴展和系統加速等需要。

前端系統擴展

可擴展性是大規模系統穩定運行的基石。隨著互聯網用戶的不斷增加,一個成功產品的用戶量往往是數以億計,無論多強大的單點都無法滿足這種規模的性能需求。因此系統的擴展是一個成功互聯網產品的必然屬性,無法進行擴展的產品,註定沒有未來。

由於擴展性是一個非常大的範疇,並沒有一個四海皆準的手段或者技術來實現,因此本節主要介紹較為通用的可擴展系統設計,並以網易云為例,來介紹基礎設施對可擴展性的支持。

無狀態服務設計

要實現系統的並行擴展,需要對原有的系統進行服務化拆分。在服務實現時,主要有兩種實現方式,分別是無狀態服務和有狀態服務。

特點

無狀態服務

指的是服務在處理請求時,不依賴除了請求本身外的其他內容,也不會有除了響應請求之外的額外操作。如果要實現無狀態服務的並行擴展,只需要對服務節點進行並行擴展,引入負載均衡即可。

有狀態服務

指的是服務在處理一個請求時,除了請求自身的信息外,還需要依賴之前的請求處理結果。

對於有狀態服務來說,服務本身的狀態也是正確運行的一部分,因此有狀態服務相對難以管理,無法通過簡單地增加實例個數來實現並行擴展。

對比

從技術的角度來看,有狀態服務和無狀態服務只是服務的兩種狀態,本身並沒有優劣之分。在實際的業務場景下,有狀態服務和無狀態服務相比,有各自的優勢。

有狀態服務的特性如下。

  • 數據局部性:數據都在服務內部,不需要依賴外部數據服務強並發,有狀態服務可以把狀態信息都放在服務內部,在並發訪問時不用考慮衝突等問題,而本地數據也可以提供更好的存取效率。因此,單個有狀態服務可以提供更強的並發處理能力。

  • 實現簡單:可以把數據信息都放在本地,因此可以較少考慮多系統協同的問題,在開發時更為簡單。

無狀態服務的特性如下。

  • 易擴展:可以通過加入負載均衡服務,增加實例個數進行簡單的並行擴展易管理,由於不需要上下文信息,因此可以方便地管理,不需要考慮不同服務實例之間的差異。

  • 易恢復:對於服務異常,不需要額外的狀態信息,只需要重新拉起服務即可。而在雲計算環境下,可以直接建立新的服務實例,替代異常的服務節點即可。

總體來看,有狀態服務在服務架構較為簡單時,有易開發、高並發等優勢,而無狀態服務的優勢則體現在服務管理、並行擴展方面。隨著業務規模的擴大、系統複雜度的增加,無狀態服務的這些優勢,會越來越明顯。因此,對於一個大型系統而言,我們更推薦無狀態化的服務設計。

實踐

下面,我們根據不同的服務類型,來分析如何進行狀態分離。

常見的狀態信息

  • Web服務:在Web服務中,我們往往需要用戶狀態信息。一個用戶的訪問過程,我們稱為一個會話(Session),這也是Web服務中最為常見的狀態信息。

  • 本地數據:在業務運行過程中,會把一些運行狀態信息保留到本地內存或者磁碟中。

  • 網路狀態:一些服務在配置時,會直接使用IP地址訪問,這樣在服務訪問時就依賴相應的網路配置。一旦地址改變,就需要修改對應的配置文件。

狀態分離

要把有狀態的服務改造成無狀態的服務,一般有以下兩種做法。

  • 請求附帶全部狀態信息:這種做法適用於狀態信息比較簡單的情況(如用戶信息,登錄狀態等)。優點是實現較為簡單,不需要額外設施。缺點是會導致請求內容增加,因此在狀態信息較多時並不適用。

  • 狀態分離:即通過將狀態信息分離到外部的獨立存儲系統中(一般是高速緩存資料庫等),來把狀態信息從服務中剝離出去。

Web服務狀態分離

在Web服務中,兩種狀態分離模式都可以實現狀態分離。

  • 使用Cookie:把會話信息保存在加密後的Cookie之中,每次請求時解析Cookie內容來判斷是否登錄。這種做法的優點是實現簡單,不需要額外的系統支持。缺點是Cookie的大小有限制,不能保持較大的狀態信息,還會增加每次請求的數據傳輸量,同時Cookie必須要使用可靠的加密協議進行加密,否則會有被人篡改或者偽造的風險。因此這種做法一般用來保持用戶登錄狀態。

  • 共享Session:將Session信息保存在外部服務(共享內存、緩存服務、資料庫等)中,在請求到來時再從外部存儲服務中獲取狀態信息。這種做法沒有狀態信息大小的限制,也不會增加請求大小。但是需要可靠、高效的外部存儲服務來進行支持。一般來說,可以直接使用雲計算服務商提供的緩存服務。

伺服器本身狀態分離

對於依賴本地存儲的服務,優先做法是把數據保存在公共的第三方存儲服務中,根據內容的不同,可以保存在對象存儲服務或者資料庫服務中。

如果很難把數據提取到外部存儲上,也不建議使用本地盤保存,而是建議通過掛載雲硬碟的方式來保持本地狀態信息。這樣在服務異常時可以直接把雲硬碟掛載在其他節點上來實現快速恢復。

對於網路信息,最好的做法是不要通過IP地址,而是通過域名來進行訪問。這樣當節點異常時,可以直接通過修改域名來實現快速的異常恢復。在網易雲基礎服務中,我們提供了基於域名的服務訪問機制,直接使用域名來訪問內部服務,減少對網路配置的依賴。

在線水平擴展

在線水平擴展能力是一個分散式系統需要提供的基本能力,也是在架構設計時需要滿足的重要功能點。而水平擴展能力也是業務發展的硬性需求,從產品的角度出發,產品的業務流量往往存在著很大波動,具體如下。

  • 產品業務量增長:在這個信息病毒式傳播的時代,一些熱點應用的業務量可能會在很短時間內大量增長。

  • 周期性業務:客服服務、證券服務及春運購票等。這類活動往往都存在著很明顯的周期性特徵,會按照一定的周期(月、天、小時、分鐘)進行波動。波峰和波谷的流量往往會有一個數量級以上的差異。

  • 活動推廣:一次成功的活動推廣往往會帶來數倍甚至數十倍的流量,需要業務可以快速擴展,在很短的時間內提供數十倍甚至上百倍的處理能力。

  • 秒殺:秒殺活動是彈性伸縮壓力最大的業務,會帶來瞬時大量流量。

    為了應對這些場景,需要業務在一個很短的時間內提供強大的處理能力。而在業務低谷期,可以相應回收過剩的計算資源,減少消耗,達到系統性能和成本之間的平衡,提高產品的競爭力。

準備工作

產品對水平擴展的需求是一直存在的,但是受制於傳統IT行業按天甚至按周計算的資源交付能力,彈性伸縮一直是一個美好的願望。直到雲計算這個基礎設施完善之後,才使彈性伸縮的實現成為了可能。如果要實現彈性伸縮,需要以下幾點的支持。

  • 資源快速交付能力:業務可以根據需要,動態並快速地申請、釋放資源。這也是雲計算提供的基礎能力,根據云計算平台的不同,一般都會提供從秒級到分鐘級的資源交付能力,相對於傳統IT管理按天計算的交付水平有了巨大的提升,這也是彈性伸縮的基礎。在雲環境下,絕大部分資源交付、擴容操作都可以在分鐘級別完成,從而為彈性伸縮提供了基礎支撐。

  • 無狀態服務設計:對於有狀態服務來說,由於有著各種各樣的狀態信息,因此會使擴展的難度大大增加。因此,無狀態話的服務設計,是彈性伸縮服務的前提。

  • 業務性能監控:只有了解業務實際的負載情況,才有彈性伸縮的可能。只有對業務承載能力、運行負載有了全面的了解和實時監控,才能制定出相應的擴展指標。對於雲服務廠商來說,基本上都提供了對基礎資源,如CPU、內存、網路等的監控能力。而對應的PaaS服務,還提供了應用層數據的詳細分析,為更細粒度、更加精確的擴展提供了可能。

  • 統一的服務入口:只有提供了統一的服務入口,才可以在不影響用戶的情況下實現後台服務的彈性伸縮。統一服務入口有兩種實現機制,一種是在系統層面,通過負載均衡服務提供統一的流量入口,使用負載均衡服務統一進行管理。另一種是通過服務註冊和發現機制,在服務層實現適配。對於外部訪問,可以使用對外的負載均衡服務,對於內部服務,一般都會提供租戶內部的負載均衡。業務方可以根據需要,使用對應的流量入口。

實現要點

前端系統一般都會採用無狀態化服務設計,擴展相對簡單。在實踐中,有多種擴展方案,如通過DNS服務水平擴展、使用專有的apiserver、在SDK端分流及接入負載均衡等。其中負載均衡方案使用最廣,綜合效果也最好,可以滿足絕大多數場景下的需要,下面就以負載均衡服務為例,介紹前端系統水平擴展的實現要點。

協議選擇

負載均衡服務分為4層和7層服務,這兩種並不是截然分開的,而是有兼容關係。4層負載均衡可以支持所有7層的流量轉發,而且性能和效率一般也會更好。而7層負載均衡服務的優勢在於解析到了應用層數據(HTTP層),因此可以理解應用層的協議內容,從而做到基於應用層的高級轉發和更精細的流量控制。

對於HTTP服務,建議直接採用7層負載均衡,而其他所有類型的服務,如WebSocket、MySQL和Redis等,則可以直接使用TCP負載均衡。

無狀態服務

前端系統可擴展性需要在系統設計層面進行保證,較為通用的做法是無狀態化的服務設計。因為無狀態,所以在系統擴展時只需要考慮外網設施的支持,而不需要改動服務代碼。對於有狀態的服務,則盡量服務改造把狀態分離出去,將狀態拆分到可以擴展的第三方服務中去。

高級流量轉發

對於7層負載均衡來說,由於解析到了協議層,因此可以基於應用層的內容進行流量轉發。除了最常用的粘性會話(Sticky Session)外,最常用的轉發規則有基於域名和URL的流量轉發兩種。

  • 基於域名的流量轉發:外網的HTTP服務默認使用80埠提供,但經常會有多個不同域名的網站需要使用同樣一個出口IP的情況。這時候就需要通過應用層解析,根據用戶的訪問域名把同一個埠的流量分發到不同的後端服務中去。域名流量轉發是通過解析請求頭的Host屬性實現的,當瀏覽器通過域名訪問時,會自動設置Host頭。通過程序訪問HTTP API介面時,一般的第三方庫也會設置這個屬性,但如果自己組裝HTTP請求,則需要主動設置對應的Host頭。

  • 基於URL的流量轉發:對一些大型網站,或者基於REST風格的API介面,單純通過域名進行分流已經無法滿足分流要求。此外,還存在著同一個域名的服務,根據URL分流到不同後端集群的情況,這種情況就可以通過請求中的URL路徑信息,進行進一步分流。一般的URL都會支持模糊匹配。

後端系統擴展

後端系統,一般指用戶接入端(Web系統、長連接伺服器)和各種中間件之後的後台系統。在這一階段,最重要的後端系統就是兩種,緩存服務和資料庫服務。下面,我們分別以Redis緩存服務和MySQL資料庫為例,來介紹後端系統水平擴展的技術和核心技術點。

Redis水平擴展

Redis去年發布了3.0版本,官方支持了Redis cluster即集群模式。至此結束了Redis沒有官方集群的時代,在官方集群方案以前應用最廣泛的就屬Twitter發布的Twemproxy(https://github.com/twitter/twemproxy),國內的有豌豆莢開發的Codis(https://github. com/wandoulabs/codis)。

下面我們介紹一下Twemproxy和Redis Cluster兩種集群水平擴展。

Twemporxy+Sentinel方案

Twemproxy,也叫nutcraker,是Twitter開源的一個Redis和Memcache快速/輕量級代理伺服器。

Twemproxy內部實現多種hash演算法,自動分片到後端多個Redis實例上,Twemproxy支持失敗節點自動刪除,它會檢測與每個節點的連接是否健康。為了避免單點故障,可以平行部署多個代理節點(一致性hash演算法保證key分片正確),Client可以自動選擇一個。

有了這些特性,再結合負載均衡和Sentinel就可以架構出Redis集群,如圖5所示。

快速成長期的雲原生應用架構實踐

圖5 基於Twemporxy的Redis水平擴展

  • 負載均衡:實現Twemproxy的負載均衡,提高proxy的可用性和可擴張能力,使Twemproxy的擴容對應用透明。

  • Twemproxy集群:多個Twemproxy平行部署(配置相同),同時接受客戶端的請求並轉發請求給後端的Redis。

  • Redis Master-Slave主從組:Redis Master存儲實際的數據,並處理Twemproxy轉發的數據讀寫請求。數據按照hash演算法分布在多個Redis實例上。Redis Slave複製master的數據,作為數據備份。在Master失效的時候,由Sentinel把Slave提升為Master。

  • Sentinel集群:檢測Master主從存活狀態,當Redis Master失效的時候,把Slave提升為新Master。

水平擴展實現就可以將一對主從實例加入Sentinel中,並通知Twemporxy更新配置加入新節點,將部分key通過一致性hash演算法分布到新節點上。

Twemproxy方案缺點如下。

  • 加入新節點後,部分數據被動遷移,而Twemproxy並沒有提供相應的數據遷移能力,這樣會造成部分數據丟失。

  • LB(負載均衡)+ Twemproxy + Redis 3層架構,鏈路長,另外加上使用Sentinel集群保障高可用,整個集群很複雜,難以管理。

Redis Cluster方案

Redis Cluster是Redis官方推出的集群解決方案,其設計的重要目標就是方便水平擴展,在1000個節點的時候仍能表現良好,並且可線性擴展。

Redis Cluster和傳統的集群方案不一樣,在設計的時候,就考慮到了去中心化、去中間件,也就是說,集群中的每個節點都是平等的關係,每個節點都保存各自的數據和整個集群的狀態。

數據的分配也沒有使用傳統的一致性哈希演算法,取而代之的是一種叫做哈希槽(hash slot)的方式。Redis Cluster默認分配了16384個slot,當我們set一個key時,會用CRC16演算法來取模得到所屬的slot,然後將這個key分到哈希槽區間的節點上,具體演算法是CRC16(key) % 16384。舉個例子,假設當前集群有3個節點,那麼:

  • 節點r1包含0到5500號哈希槽。

  • 節點r2包含5501到11000號哈希槽。

  • 節點r3包含11001到16384號哈希槽。

集群拓撲結構如圖4-3所示,此處不再重複給出。

Redis Cluster水平擴展很容易操作,新節點加入集群中,通過redis-trib管理工具將其他節點的slot遷移部分到新節點上面,遷移過程並不影響客戶端使用,如圖6所示。

快速成長期的雲原生應用架構實踐

圖6 Redis Cluster水平擴展

為了保證數據的高可用性,Redis Cluster加入了主從模式,一個主節點對應一個或多個從節點,主節點提供數據存取,從節點則是從主節點實時備份數據,當這個主節點癱瘓後,通過選舉演算法在從節點中選取新主節點,從而保證集群不會癱瘓。

Redis Cluster其他具體細節可以參考官方文檔,這裡不再詳細介紹。Redis Cluster方案缺點如下。

  • 客戶端實現複雜,管理所有節點連接,節點失效或變化需要將請求轉移到新節點。

  • 沒有中心管理節點,節點故障通過gossip協議傳遞,有一定時延。

資料庫水平擴展

單機資料庫的性能由於物理硬體的限制會達到瓶頸,隨著業務數據量和請求訪問量的不斷增長,產品方除了需要不斷購買成本難以控制的高規格伺服器,還要面臨不斷迭代的在線數據遷移。在這種情況下,無論是海量的結構化數據還是快速成長的業務規模,都迫切需要一種水平擴展的方法將存儲成本分攤到成本可控的商用伺服器上。同時,也希望通過線性擴容降低全量數據遷移對線上服務帶來的影響,分庫分表方案便應運而生。

分庫分表的原理是將數據按照一定的分區規則Sharding到不同的關係型資料庫中,應用再通過中間件的方式訪問各個Shard中的數據。分庫分表的中間件,隱藏了數據Sharding和路由訪問的各項細節,使應用在大多數場景下可以像單機資料庫一樣,使用分庫分表後的分散式資料庫。

分散式資料庫

網易早在2006年就開始了分散式資料庫(DDB)的研究工作,經過10年的發展和演變,DDB的產品形態已全面趨於成熟,功能和性能得到了眾多產品的充分驗證。

圖7是DDB的完整架構,由cloudadmin、LVS、DDB Proxy、SysDB及數據節點組成。

  • cloudadmin:負責DDB的一鍵部署、備份管理、監控報警及版本管理等功能。

  • LVS:負責將用戶請求均勻分布到多個DDB Proxy上。

  • DDB Proxy:對外提供MySQL協議訪問,實現SQL語法解析、分散式執行計劃生成、下發SQL語句到後端資料庫節點,匯總合併資料庫節點執行結果。

  • SysDB:DDB元數據存儲資料庫,也基於RDS實現高可用。

  • RDS:底層數據節點,一個RDS存儲多個數據分片。

快速成長期的雲原生應用架構實踐

圖7 網易DDB架構

分散式執行計劃

分散式執行計劃定義了SQL在分庫分表環境中各個資料庫節點上執行的方法、順序和合併規則,是DDB實現中最為複雜的一環。如SQL:select * from user order by id limit 10 offset 10。

這個SQL要查詢ID排名在10~20之間的user信息,這裡涉及全局ID排序和全局LIMIT OFFSET兩個合併操作。對全局ID排序,DDB的做法是將ID排序下發給各個資料庫節點,在DBI層再進行一層歸併排序,這樣可以充分利用資料庫節點的計算資源,同時將中間件層的排序複雜度降到最低,例如一些需要用到臨時文件的排序場景,如果在中間件做全排序會導致極大的開銷。

對全局LIMIT OFFSET,DDB的做法是將OFFSET累加到LIMIT中下發,因為單個數據節點中的OFFSET沒有意義,且會造成錯誤的數據偏移,只有在中間件層的全局OFFSET才能保證OFFSET的準確性。

所以最後下發給各個DBN的SQL變為:select * from user order by id limit 20。又如SQL:select avg(age) from UserTet group by name可以通過EXPLAIN語法得到SQL的執行計劃,如圖8所示。

快速成長期的雲原生應用架構實踐

圖8 分散式執行計劃

上述SQL包含GROUP BY分組和AVG聚合兩種合併操作,與全局ORDER BY類似,GROUP BY也可以下發給數據節點、中間件層做一個歸併去重,但是前提要將GROUP BY的欄位同時作為ORDER BY欄位下發,因為歸併的前提是排序。對AVG聚合,不能直接下發,因為得到所有數據節點各自的平均值,不能求出全局平均值,需要在DBI層把AVG轉化為SUM和COUNT再下發,在結果集合併時再求平均。

DDB執行計劃的代價取決於DBI中的排序、過濾和連接,在大部分場景下,排序可以將ORDER BY下發簡化為一次性歸併排序,這種情況下代價較小,但是對GROUP BY和ORDER BY同時存在的場景,需要優先下發GROUP BY欄位的排序,以達到歸併分組的目的,這種情況下,就需要將所有元素做一次全排序,除非GROUP BY和ORDER BY欄位相同。

DDB的連接運算有兩種實現,第一種是將連接直接下發,若連接的兩張表數據分布完全相同,並且在分區欄位上連接,則滿足連接直接下發的條件,因為在不同數據節點的分區欄位必然沒有相同值,不會出現跨庫連接的問題。第二種是在不滿足連接下發條件時,會在DBI內部執行Nest Loop演算法,驅動表的順序與FROM表排列次序一致,此時若出現ORDER BY表次序與表排列次序不一致,則不滿足ORDER BY下發條件,也需要在DBI內做一次全排序。

分庫分表的執行計劃代價相比單機資料庫而言,更加難以掌控,即便是相同的SQL模式,在不同的數據分布和分區欄位使用方式上,也存在很大的性能差距,DDB的使用要求開發者和DBA對執行計劃的原理具有一定認識。

分庫分表在分區欄位的使用上很有講究,一般建議應用中80%以上的SQL查詢通過分區欄位過濾,使SQL可以單庫執行。對於那些沒有走分區欄位的查詢,需要在所有數據節點中並行下發,這對線程和CPU資源是一種極大的消耗,伴隨著數據節點的擴展,這種消耗會越來越劇烈。另外,基於分區欄位跨庫不重合的原理,在分區欄位上的分組、聚合、DISTINCT、連接等操作,都可以直接下發,這樣對中間件的代價往往最小。

分散式事務

分散式事務是個歷久彌新的話題,分庫分表、分散式事務的目的是保障分庫數據一致性,而跨庫事務會遇到各種不可控制的問題,如個別節點永久性宕機,像單機事務一樣的ACID是無法奢望的。另外,業界著名的CAP理論也告訴我們,對分散式系統,需要將數據一致性和系統可用性、分區容忍性放在天平上一起考慮。

兩階段提交協議(簡稱2PC)是實現分散式事務較為經典的方案,適用於中間件這種數據節點無耦合的場景。2PC的核心原理是通過提交分階段和記日誌的方式,記錄下事務提交所處的階段狀態,在組件宕機重啟後,可通過日誌恢復事務提交的階段狀態,並在這個狀態節點重試,如Coordinator重啟後,通過日誌可以確定提交處於Prepare還是PrepareAll狀態,若是前者,說明有節點可能沒有Prepare成功,或所有節點Prepare成功但還沒有下發Commit,狀態恢復後給所有節點下發RollBack;若是PrepareAll狀態,需要給所有節點下發Commit,資料庫節點需要保證Commit冪等。與很多其他一致性協議相同,2PC保障的是最終一致性。

2PC整個過程如圖9所示。

快速成長期的雲原生應用架構實踐

圖9 兩階段提交協議

在網易DDB中,DBI和Proxy組件都作為Coordinator存在,2PC實現時,記錄Prepare和PrepareAll的日誌必須sync,以保障重啟後恢復狀態正確,而Coordinator最後的Commit日誌主要作用是回收之前日誌,可非同步執行。

由於2PC要求Coordinator記日誌,事務吞吐率受到磁碟I/O性能的約束,為此DDB實現了GROUP I/O優化,可極大程度提升2PC的吞吐率。2PC本質上說是一種阻塞式協議,兩階段提交過程需要大量線程資源,因此CPU和磁碟都有額外消耗,與單機事務相比,2PC在響應時間和吞吐率上相差很多,從CAP角度出發,可以認為2PC在一定程度上成全了C,犧牲了A。

另外,目前MySQL最流行的5.5和5.6版本中,XA事務日誌無法複製到從節點,這意味著主庫一旦宕機,切換到從庫後,XA的狀態會丟失,可能造成數據不一致,MySQL 5.7版本在這方面已經有所改善。

雖然2PC有諸多不足,我們依然認為它在DDB中有實現價值,DDB作為中間件,其迭代周期要比資料庫這種底層服務頻繁,若沒有2PC,一次更新或重啟就可能造成應用數據不一致。從應用角度看,分散式事務的現實場景常常無法規避,在有能力給出其他解決方案前,2PC也是一個不錯的選擇。

對購物轉賬等電商和金融業務,中間件層的2PC最大問題在於業務不可見,一旦出現不可抗力或意想不到的一致性破壞,如數據節點永久性宕機,業務難以根據2PC的日誌進行補償。金融場景下,數據一致性是命根,業務需要對數據有百分之百的掌控力,建議使用TCC這類分散式事務模型,或基於消息隊列的柔性事務框架,請參考第5章,這兩種方案都在業務層實現,業務開發者具有足夠掌控力,可以結合SOA框架來架構。原理上說,這兩種方案都是大事務拆小事務,小事務變本地事務,最後通過冪等的Retry來保障最終一致性。

彈性擴容

分庫分表資料庫中,在線數據遷移也是核心需求,會用在以下兩種場景中。

  • 數據節點彈性擴容:隨著應用規模不斷增長,DDB現有的分庫可能有一天不足以支撐更多數據,要求DDB的數據節點具有在線彈性擴容的能力,而新節點加入集群後,按照不同的Sharding策略,可能需要將原有一些數據遷入新節點,如HASH分區,也有可能不需要在線數據遷移,如一些場景下的Range分區。無論如何,具備在線數據遷移是DDB支持彈性擴容的前提。

  • 數據重分布:開發者在使用DDB過程中,有時會陷入困局,比如一些表的分區欄位一開始沒考慮清楚,在業務初具規模後才明確應該選擇其他欄位。又如一些表一開始認為數據量很小,只要單節點分布即可,而隨著業務變化,需要轉變為多節點Sharding。這兩種場景都體現了開發者對DDB在線數據遷移功能的潛在需求。

無論是彈性擴容,還是表重分布,都可當作DDB以表或庫為單位的一次完整在線數據遷移。該過程分為全量遷移和增量遷移兩個階段,全量遷移是將原庫或原表中需要遷移的數據DUMP出來,並使用工具按照分區策略導入到新庫新表中。增量遷移是要將全量遷移過程中產生的增量數據更新按照分區策略應用到新庫新表。

全量遷移的方案相對簡單,使用DDB自帶工具按照特定分區策略DUMP和Load即可。對增量遷移,DDB實現了一套獨立的遷移工具Hamal來訂閱各個數據節點的增量更新,Hamal內部又依賴DBI模塊將增量更新應用到新庫新表,如圖10所示。

快速成長期的雲原生應用架構實踐

圖10 DDB增量遷移工具Hamal

Hamal作為獨立服務,與Proxy一樣由DDB統一配置和管理,每個Hamal進程負責一個數據節點的增量遷移,啟動時模擬Slave向原庫拉取Binlog存儲本地,之後實時通過DBI模塊應用到新庫新表,除了基本的遷移功能外,Hamal具備以下兩個特性。

  • 並行複製:Hamal的並行複製組件,通過在增量事件之間建立有向無環圖,實時判斷哪些事件可以並行執行,Hamal的並行複製與MySQL的並行複製相比快10倍以上。

  • 斷點續傳:Hamal的增量應用具有冪等性,在網路中斷或進程重啟之後可以斷點續傳。

全局表

考慮一種場景:City表記錄了國內所有城市信息,應用中有很多業務表需要與City做聯表查詢,如按照城市分組統計一些業務信息。假設City的主鍵和分區鍵都是CityId,若連接操作發生在中間件層,代價較高,為了將連接操作下發數據節點,需要讓連接的業務表同樣按照CityId分區,而大多數業務表往往不能滿足這個條件。

連接直接下發需要滿足兩個條件,數據分布相同和分區鍵上連接,除此之外,其實還有一種解法,可以把City表冗餘到所有數據節點中,這樣各個數據節點本地連接的集合便是所求結果。DDB將這種類型的表稱之為全局表。

全局表的特點是更新極少,通過2PC保障各個節點冗餘表的一致性。可以通過在建表語句添加相關Hint指定全局表類型,在應用使用DDB過程中,全局表的概念對應用不可見。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 PHP愛好者 的精彩文章:

一位程序員工作10年總結了這些忠告
劣質代碼產生的五個原因
數據分析沒價值?——深思對業務的洞察有多少?
寫給初學前端工程師的一封信

TAG:PHP愛好者 |

您可能感興趣

速成應用怎麼樣?速成應用靠譜嗎?速成應用小程序開發工具
成飛公司「線束自動布線設備工程化應用」取得階段性進展
快速排序和它在工程實踐中的應用
雲計算技術在高速公路上的應用實踐
商業航天「快速」升空:產品周期縮短 應用領域廣泛
快應用生態未來:當快遊戲成撬動遊戲行業的新支點
新技術的應用或可將沙漠變成良田,我國已經開展實驗
鯨准研究院|醫療大數據應用迎來快速增長期,機遇和挑戰何在?
多雲及新應用部署環境中,企業該如何完成應用架構轉型?
浙江移動首個基於阿里雲技術的生產應用上線成功
基於容器的混合雲架構及應用場景
上半年我國IC產業繼續保持高速增長 新興應用將成為半導體市場新動能
快應用——架構在操作系統上的「小程序」
俄央行副行長:雖不夠成熟,區塊鏈規模化應用落地可期
醫療大數據應用迎來快速增長期,機遇和挑戰何在?
應用程序快速啟動工具
迅雷鏈應用暴增,規模化底層生態悄然成形
這款聽寫應用太實用了,實時把語音轉成文字,提升你的工作效率
應加快明確區塊鏈監管框架 落實試點應用
趙毅:「虛擬現實」或成銀行應用新未來