一文了解高性能架構和系統設計經驗

高性能和高併發,聽着就有點類似,並且他們還經常一起提及,比如提高我們的併發性能,顯然,高性能可以提高我們的併發,但是細化來看,他們是有區別的,他們的考量點的維度不同。高性能需要我們從單機維度到整體維度去考慮,更多的是先從編碼角度、架構使用角度去讓我們的單機(單實例)有更好的性能,然後再從整個系統層面來擁有更好的性能;高併發則直接是全局角度來讓我們的系統在全鏈路下都能夠抗住更多的併發請求。

一、高性能架構和系統設計的幾個層面

**高性能架構設計主要集中在單機優化、服務集羣優化、編碼優化三方面。**但架構層面的設計是高性能的基礎,如果架構層面的設計沒有做到高性能,僅依靠優化編碼,對整體系統的提升是有限的。我們從一個全局角度來看高性能的系統設計,需要整體考慮的包括如下幾個層面:

二、前端層面

後端優化的再好,如果前端(客戶端)的性能不 ok,那麼對用戶而言,他們的體感還是很差的,因此前端層也是有必要考慮的,只是不在我們本文的設計範圍之內,在實際工作中是需要進行探討的。

這裏簡單說明下,從我個人工作的經歷來看,前端(客戶端)這裏可以優化的點包括但不限於:數據預加載、數據本地緩存、業務邏輯前置處理、CDN 加速、請求壓縮、異步處理、合併請求、長連接、靜態資源等

三、編碼實現層面

編碼實現層面:代碼邏輯的分層、分模塊、協程、資源複用(對象池,線程池等)、異步、IO 多路複用(異步非阻塞)、併發、無鎖設計、設計模式等。

多線程、多協程

大多數情況下,多進程、多線程、多協程都可以大大提高我們的併發性能,尤其是是多協程。

在網絡框架層面,現在一般成熟的後端系統框架(服務化框架)都是支持多線程、多協程的,因此對於網絡框架這點,我們只要是引用相對成熟的服務化框架來實現我們的業務,基本上可以不用過多考慮和設計。

在業務層面,如果是 Go 語言,天然支持大量併發,並且創建 Go 的協程非常容易,一個 go 關鍵字就搞定,因此多協程那就非常容易了,Go 裏面可以創建大量協程來提高我們的併發性能。如果是其他語言,我們儘可能的使用多協程、多線程去執行我們的業務邏輯。

無鎖設計(lock free)

在多線程、多協程的框架下,如果我們併發的線程(協程)之間訪問共享資源,那麼需要特別注意,要麼通過加鎖、要麼通過無鎖化設計,否則沒有任何處理的訪問共享資源會產生意想不到的結果。而加鎖的設計,在併發較大的時候,如果鎖的力度不合適,或者頻繁的加鎖解鎖,又會使我們的性能嚴重下降。

爲此,在追求高性能的時候,大家就比較推崇無鎖化的設計。目前很多後臺底層設計,爲了避免共享資源的競爭,都採用了無鎖化設計,特別是在底層框架上。無鎖化主要有兩種實現,無鎖隊列和原子操作。

數據序列化

爲什麼要說 數據序列化協議?因爲我們的系統,要麼就是各個後端微服務之間通過 RPC 做交互,要麼就是通過 HTTP/TCP 協議和前端 (終端) 做交互,因此不可避免的需要我們進行網絡數據傳輸。而數據,只有序列化後,才方便進行網絡傳輸。

序列化就是將數據結構或對象轉換成二進制串的過程,也就是編碼的過程,序列化後,會把數據轉換爲二進制串,然後可以進行網絡傳輸;反序列化就是在序列化過程中所生成的二進制串轉換成數據結構或者對象的過程,將二進制轉換爲對象後業務纔好進行後續的邏輯處理。

常見的序列化協議如下

常見的序列化協議的對比在網上有各種性能的對比,這裏就不在貼相關截圖了,只說結論:從性能上和使用廣泛度上來看,後端服務之間現在一般推薦使用 PB。如果和前端交互,由於 HTTP 協議只能支持 JSON,因此一般只能 JSON。

池化技術(資源複用)

池化技術是非常常見的一個提高性能的技術,池化的核心思想就是對資源進行復用,減少重複創建銷燬所帶來的開銷。複用就是創建一個池子,然後再在這個池子裏面對各種資源進行統一分配和調度,不是創建後就釋放,而是統一放到池子裏面來複用,這樣可以減少重複創建和銷燬,從而提高性能。而這個資源就包括我們編程中常見到如 線程資源、網絡連接資源、內存資源,具體到對應的池化技術層面就是 線程池(協程池)、連接池、內存池等。

異步 IO 和 異步流程

異步有兩個層面的意思:

異步是相對同步而言的,同步就是要等待前面一個事情執行完畢才能繼續執行,異步就是可以不用等待,可想而知,異步的性能要比同步好很多。

IO 層面的異步調用

針對 IO 層面的異步調用,就是我們常說的 I/O 模型,有 阻塞、非阻塞、同步、異步這幾種類型。在 Linux 操作系統內核中,內置了 5 種不同的 IO 交互模式,分別是阻塞 IO、非阻塞 IO、多路複用 IO、信號驅動 IO、異步 IO

針對網絡 IO 模型而言,Linux 下,使用最多性能較好的是同步非阻塞模型,具體代表是 AIO,而 Windows 下的代表作 IOCP 則實現了真正的異步非阻塞 I/O。

業務邏輯層面的異步流程

業務邏輯層面的異步流程,就是指讓我們的應用程序在業務邏輯上可以異步的執行。通常比較複雜的業務,都會有很多步驟流程,如果所有步驟都是同步的話,那麼當這些步驟中有一步卡住,那麼整個流程都會卡住,這樣的流程顯然性能不會很高。爲此,在業內,我們如果想要提高性能,提高併發,那麼基本上都會採用異步流程的方式。

舉個實際的應用案例,針對 IM 系統的發送消息的這個場景,比如微信發送消息,那麼當客戶端發送的消息,服務端收到後,這個消息肯定要落地存儲,這個發送的流程才能算完畢,但是,如果每條消息,服務端都真正存儲到 DB 後再返回給客戶端說已經正確收到,那麼這個性能顯然會很低,因爲我們知道,寫 DB 的性能是很低的,尤其是像微信這種每天有大量消息的 APP。那麼這個流程就可以異步化,服務端收到消息後,先把消息寫入消息隊列,寫入隊列成功就返回給客戶端,然後異步流程去從消息隊列裏面消費數據然後落地存儲到 DB 裏面,這樣性能就非常高了,因爲消息隊列的性能會很高。而比較低性能的操作都是異步處理。

併發流程

併發流程,同樣是針對我們上層的應用程序而言的,我們在處理業務邏輯的時候,尤其是相對負責的業務邏輯,一般下游都可能會有多個請求,或者說多個流程,如果依賴的下游多個請求之間沒有強依賴關係,那麼我們可以將這些請求的流程併發處理,這個是後端系統設計裏面非常常見的優化手段。

通過併發的處理流程,可以將串行的疊加處理耗時優化爲單個處理耗時,這樣就大大的降低了整體耗時,舉個例子,一個商品活動頁面,渲染的數據包括 用戶基本信息、用戶活動積分、用戶推薦商品列表。那麼當收到這個用戶的請求的時候,我們需要 查詢用戶的基本信息、用戶的活動積分,還有用戶的商品推薦,而這 3 個步驟完全是沒有相互依賴關係的,因此,我們可以併發去分別查詢,這樣可以極大的減少耗時,從而提高我們的性能。

四、單機架構設計層面

單機優化的關鍵點

單機優化層面就是要儘量提升單機的性能,將單機的性能發揮到極致的其中一個關鍵點就是我們服務器採取的併發模型,然後在這個模型下,去設計好我們的服務器對連接的管理、對請求的處理流程。而這些就涉及到我們的多協程、多線程的進程模型和異步非阻塞、同步非阻塞的 IO 模型。

**在具體實現細節上,針對連接的管理,要想提高性能,那麼就要採用 IO 多路複用技術,**可以參考 I/O Multiplexing 查看,I/O 多路複用技術的兩個關鍵點在於:

IO 多路複用(epoll 模型)

基本上來說,異步 I/O 模型的發展技術是:select -> poll -> epoll -> aio -> libevent -> libuv。

而且現在大家比較熟悉和使用的最多的恐怕就是 epoll 和 aio ,尤其是 epoll 模型,基本是 Linux 後端系統下的大部分框架和軟件都是採用 epoll 模型。

但是,需要特別強調的是,僅僅依靠 epoll 不是萬能的,連接數太多的時候單進程的 epoll 也是不行的。

Reactor 和 Proactor 架構模式

epoll 只是一個 IO 多路複用的模型,在後端系統設計裏面,要想實現單機的高性能,那在 IO 多路複用基礎之上,我們的整個網絡框架,還需要配合池化技術來提高我們的性能。因此,業界一般都是採用 I/O 多路複用 + 線程池(協程池、進程池)的方式來提高性能。與之對應的,在業界常用的兩個單機高性能的架構模式就是 Reactor 和 Proactor 模式。Reactor 模式屬於非阻塞同步網絡模型,Proactor 模式屬於非阻塞異步網絡模型。

在業內開源軟件裏面,Redis 採用的是 單 Reactor 單進程的方式,Memcache 採用的是 多 Reactor 多線程的方式,Nginx 採用的是多 Reactor 多進程的方式。關於 的詳細介紹,可以查看 The Design and Implementation of the Reactor。

Redis 可以用單進程 Reactor 模式的是因爲 Redis 的應用場景是內部訪問,併發數一般不會超過 1w,而 Nginx 必須用多進程 Reactor 模式是因爲 Nginx 是外網訪問,併發數很容易超過 1w,因此我們的網絡架構模式,必須要通過 I/O 多路複用 + 線程池(協程池、進程池)來配合。

可以看到,單機優化層面其實和編碼層面上的多協程、異步 IO、 池化技術都是有強關聯的。這裏也是一個知識相通的典型,我們所學的一些基礎層面的知識點,在架構層面、模型層面都是有用武之地的。

五、系統架構設計層面

架構設計層面:架構分層、業務分模塊、集羣(集中式、分佈式)、緩存(多級緩存、本地緩存)、消息隊列(異步、削峯)

架構和模塊劃分的設計

整個系統想要有一個高性能,那麼首先就需要有個合理的架構設計,這裏需要根據一些架構設計原則,比如高內聚低耦合,職責單一等來去構建我們的架構。最有效的方式包括架構分層設計、業務分模塊設計。

這麼設計之後,在整體的系統性能優化上,後面就會有比較大的優化空間,從而不至於後面想要優化就根本無從下手,只能重構系統。

服務化框架的設計

目前的互聯網時代,我們基本上都是採用微服務來搭建我們的系統,而微服務化的必要條件就是要有一套服務化框架,這個服務化框架最核心的功能包括 RPC 請求和最基礎的服務治理策略(服務註冊和發現、負載均衡等)。

爲此,這裏服務化框架的性能就尤爲重要,這裏主要包括這個服務化框架裏面實現:

負載均衡

負載均衡系統是水平擴展的關鍵技術,通過負載均衡,相當於可以把流量分散到不同的機器的不同的服務實例裏面,這樣每個服務實例都可以承擔一部分請求,從而可以提高我們的整體系統的性能。

對於負載均衡的方式,大都是在客戶端發現模式 (client-side) 來實現服務路和負載均衡,一般也都會支持常見的負載均衡策略,如隨機,輪訓,hash,權重,連接數【連接數越少,優先級越高】。

合理採用各種隊列

在後端系統設計裏面,很多流程和請求並不要求實時處理,更不需要做到強一致,大部分情況下,我們只需要實現最終一致性就可以了。故而,我們通過隊列,就可以使我們的系統能夠實現異步處理邏輯、流程削峯、業務模塊解耦、柔性事務等多種效果,從而可以完成最終一致性,並且能夠極大的提高我們系統的性能。

我們常見的隊列包括

各級緩存的設計

分佈式緩存

分佈式緩存的代表作有 Redis、Memcache。通過分佈式緩存,我們可以不直接讀數據庫,而是讀取緩存來獲取數據,可以極大的提高我們讀數據的性能。而一般的業務都是讀多寫少,因此,對我們的整體性能的提高是非常有效的手段,而且是必須的手段。

本地緩存

本地緩存可以從幾個維度來看:

多級緩存

多級緩存是一個更爲高級的緩存架構設計,比如最簡單的模式可以是 本地緩存 + 分佈式緩存這樣形成一個多級緩存架構。

我們把全量要緩存的數據都放到分佈式緩存裏面,然後把一些熱點的少量緩存放到本地緩存裏面,這樣大部分熱點數據都能夠從本地直接讀取,而其他非熱點的數據還是通過分佈式緩存讀取,這樣可以極大的提高我們的性能,提高併發能力。

舉個例子,電商系統裏面,我們做一個活動頁,活動頁的前面 10 個商品是特賣商品,然後後面的其他商品就是常規商品,因爲是活動頁面,那麼這個頁面的訪問肯定就會非常大。而活動頁面的前 10 個商品,必然是用戶首先進來頁面就一定會看到的,而用戶想要繼續看其他商品,那麼就需要在手機上手動上滑刷新一下。這個場景下,前面 10 個商品的訪問量無疑是最大的,而用戶手動上滑刷新後的請求就會少很多。爲此,我們可以把全量商品都緩存在分佈式緩存如 redis 裏面,然後再在這個基礎之上,把前面 10 個商品的信息緩存到本地,這樣,當活動開始後,拉取的第一頁 10 個商品數據,都是從本地緩存拉取的,本地讀取性能會非常高,因爲內存讀取就行,完全不需要網絡交互。

其他的模式,可以 本地緩存 + 二級分佈式緩存 + 一級分佈式緩存,也就是針對分佈式緩存再做一層分級,這樣每一級的緩存都能抗一部分的量,因此整體來看,能夠對外提供的性能就足夠高。

緩存預熱

通過異步任務提前將接下來要大量訪問的數據預熱到我們緩存裏面。這樣當有請求的突峯的時候,可以從容應對。

其他高性能的 NoSQL

除了 Redis、本地緩存這些,其他的一些 NoSQL 中,MongoDB、Elasticserach 也是常見的性能很高的組件,我們可以根據適用場景,合理選用。

比如我們在電商系統裏面,我們針對商品的搜索、推薦都是採用 Elasticserach 來實現。

存儲的設計

數據分區

數據分區是把數據按一定的方式分成多個區(比如通過地理位置),不同的數據區來分擔不同區的流量,這需要一個數據路由的中間件,但會導致跨庫的 Join 和跨庫的事務非常複雜。

將數據分佈到多個分區有兩種比較典型的方案:

分庫分表

一般來說,影響數據庫最大的性能問題有兩個,一個是對數據庫的操作,一個是數據庫中數據的大小。對於前者,我們需要從業務上來優化。一方面,簡化業務,不要在數據庫上做太多的關聯查詢,而對於一些更爲複雜的用於做報表或是搜索的數據庫操作,應該把其移到更適合的地方。比如,用 ElasticSearch 來做查詢,用 Hadoop 或別的數據分析軟件來做報表分析。對於後者,一般就是拆分。分庫分表技術,有些地方也稱爲 Sharding、分片,通過分庫分表可以提高我們的讀寫性能

分庫分表有垂直切分和水平切分兩種:

分表包括垂直切分和水平切分,而分區只能起到水平切分的作用。

讀寫分離

互聯網系統大多數都是讀多寫少,因此讀寫分離可以幫助主庫抗量,讀寫分離就是將讀的請求量改爲從庫承擔,寫還是主庫來承擔。一般我們都是一主多從的架構,既可以抗量,又可以保證數據不丟。

冷熱分離

針對業務場景而言,如果數據有冷熱之分的話,可以將歷史冷數據與當前熱數據分開存儲,這樣可以減輕當前熱數據的存儲量,可以提高性能。

我們常見的存儲系統比如 MySQL、Elasticserach 等都可以支持。

分佈式數據庫

分佈式數據庫的基本思想是將原來集中式數據庫中的數據分散存儲到多個通過網絡連接的數據存儲節點上,以獲取更大的存儲容量和更高的併發訪問量,從而提高我們的性能。現在傳統的關係型數據庫已經開始從集中式模型向分佈式架構發展了。一般雲服務廠商,都會提供分佈式數據庫的解決方案,比如騰訊雲的 TDSQL MySQL 版,TDSQL for MySQL 是騰訊打造的一款分佈式數據庫產品,具備強一致高可用、全球部署架構、分佈式水平擴展、高性能、企業級安全等特性,同時提供智能 DBA、自動化運營、監控告警等配套設施,爲客戶提供完整的分佈式數據庫解決方案。

六、基礎建設層面

基礎建設層面,大體分爲 3 大塊:

七、運維部署層面

在運維部署層面做好相關建設,是有助於提高我們系統的整體性能的。比如,我們可以通過容器化部署做到彈性伸縮,通過彈性伸縮的能力,可以使得我們的服務,在資源分配使用上,一直保持合理的 CPU、內存等資源的使用率。

八、性能測試優化層面

我們從架構設計層面、編碼實現層面按照高性能的解決方案和思路實現了我們系統之後,理論上,我們的系統性能不會太差,但是,具體我們的系統性能如何?是否存在可優化點?代碼的實現是否有性能問題?我們的依賴服務是否存在性能問題?等等,這些對我們大部分人來說,如果沒有一個合理的性能壓測和分析,那麼可能還是黑盒的。

因此,針對我們研發人員而言,在高性能架構設計方面的最後一個環節,就是進行性能測試優化,具體包括三個環節:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/vtc3hs_9p7DxFkF-u3RfmQ