1 采用異步設計
假設使用同步實作,偽裝代碼如下:
Transfer(accountFrom, accountTo, amount) { // 先從 accountFrom 的賬戶中減去相應的錢數 Add(accountFrom, -1 * amount) // 再把減去的錢數加到 accountTo 的賬戶中 Add(accountTo, amount) return OK }
上面的偽代碼首先從 accountFrom 的賬戶中減去相應的錢數,再把減去的錢數加到 accountTo 的賬戶中,這種同步實作是一種很自然方式,簡單直接,那么性能表現如何呢?假設微服務 Add 的平均回應時延是 50ms,那么可以計算出我們實作的微服務 Transfer 的平均回應時延大約等于執行 2 次 Add 的時延,也就是 100ms,那隨著呼叫Transfer 服務的請求越來越多,會出現什么情況呢?
在這種實作中,每處理一個請求需要耗時 100ms,并在這 100ms 程序中是需要獨占一個執行緒的:每個執行緒每秒鐘最多可以處理 10 個請求,每臺計算機上的執行緒資源并不是無限的,假設我們使用的服務器同時打開的執行緒數量上限是 10,000,可以計算出這臺服務器每秒鐘可以處理的請求上限是: 10,000(個執行緒)* 10(次請求每秒) =100,000 次每秒,如果請求速度超過這個值,那么請求就不能被馬上處理,只能阻塞或者排隊,這時候 Transfer 服務的回應時延由 100ms 延長到了:排隊的等待時延 + 處理時延 (100ms),也就是說,在大量請求的情況下,我們的微服務的平均回應時延變長了,
這是不是已經到了這臺服務器所能承受的極限了呢?其實遠遠沒有,如果我們監測一下服務器的各項指標,會發現無論是 CPU、記憶體,還是網卡流量或者是磁盤的 IO 都空閑的很,那我們 Transfer 服務中的那 10,000 個執行緒在干什么呢?對,絕大部分執行緒都在等待 Add 服務回傳結果,
采用異步處理
TransferAsync(accountFrom, accountTo, amount, OnComplete()) { // 異步從 accountFrom 的賬戶中減去相應的錢數,然后呼叫 OnDebit 方法, AddAsync(accountFrom, -1 * amount, OnDebit(accountTo, amount, OnAllDone(OnComplete()))) } // 扣減賬戶 accountFrom 完成后呼叫 OnDebit(accountTo, amount, OnAllDone(OnComplete())) { // 再異步把減去的錢數加到 accountTo 的賬戶中,然后執行 OnAllDone 方法 AddAsync(accountTo, amount, OnAllDone(OnComplete())) } // 轉入賬戶 accountTo 完成后呼叫 OnAllDone(OnComplete()) { OnComplete() }
由于沒有了執行緒的數量的限制,總體吞吐量上限會大大超過同步實作,并且在服務器 CPU、網路帶寬資源達到極限之前,回應時延不會隨著請求數量增加而顯著升高,可以一直保持約 100ms 的平均回應時延,
2 序列化與反序列化資料的壓縮優化
比如我們要序列化一個 User 物件,它包含 3 個屬性,姓名 zhangsan,年齡:23,婚姻狀況:已婚,
User: name: "zhangsan" age: 23 married: true
使用 JSON 序列化后:
{"name":"zhangsan","age":"23","married":"true"}
實作高性能的序列化,對于同樣的 User 物件,我們可以把它序列化成這樣:
03 | 08 7a 68 61 6e 67 73 61 6e | 17 | 01 User | z h a n g s a n | 23 | true
03 表示這是一個 User 型別的物件,可以約定按照 name、age、married 這個固定順序來序列化這三個屬性,按照順序,第一個欄位是 name,不存欄位名,直接存欄位值“zhangsan”就可以了,由于名字的長度不固定,用第一個位元組 08 表示這個名字的長度是 8 個位元組,后面的 8 個位元組就是 zhangsan,第二個欄位是年齡,直接用一個位元組表示就可以了,23 的 16 進制是 17 ,最后一個欄位是婚姻狀態,用一個位元組來表示,01 表示已婚,00 表示未婚,這里面保存一個 01,
可以看到,同樣的一個 User 物件,JSON 序列化后需要 47 個位元組,這里只要 12 個位元組就夠了,
3 雙工收發協議
4 使用批量訊息提升服務端處理能力
5 利用 PageCache 加速訊息讀寫
PageCache 是現代作業系統都具有的一項基本特性,通俗地說,PageCache 就是作業系統在記憶體中給磁盤上的檔案建立的快取,無論我們使用什么語言撰寫的程式,在呼叫系統的 API 讀寫檔案的時候,并不會直接去讀寫磁盤上的檔案,應用程式實際操作的都是 PageCache,也就是檔案在記憶體中快取的副本,應用程式在寫入檔案的時候,作業系統會先把資料寫入到記憶體中的 PageCache,然后再一批一批地寫到磁盤上,讀取檔案的時候,也是從 PageCache 中來讀取資料,這時候會出現兩種可能情況,
一種是 PageCache 中有資料,那就直接讀取,這樣就節省了從磁盤上讀取資料的時間;另一種情況是,PageCache 中沒有資料,這時候作業系統會引發一個缺頁中斷,應用程式的讀取執行緒會被阻塞,作業系統把資料從檔案中復制到 PageCache 中,然后應用程式再從 PageCache 中繼續把資料讀出來,這時會真正讀一次磁盤上的檔案,這個讀的程序就會比較慢,用戶的應用程式在使用完某塊 PageCache 后,作業系統并不會立刻就清除這個 PageCache,而是盡可能地利用空閑的物理記憶體保存這些 PageCache,除非系統記憶體不夠用,作業系統才會清理掉一部分 PageCache,清理的策略一般是 LRU 或它的變種演算法,這個演算法我們不展開講,它保留 PageCache 的邏輯是:優先保留最近一段時間最常使用的那些 PageCache,
例如Kafka 在讀寫訊息檔案的時候,充分利用了 PageCache 的特性,一般來說,訊息剛剛寫入到服務端就會被消費,按照 LRU 的“優先清除最近最少使用的頁”這種策略,讀取的時候,對于這種剛剛寫入的 PageCache,命中的幾率會非常高,也就是說,大部分情況下,消費讀訊息都會命中 PageCache,帶來的好處有兩個:一個是讀取的速度會非常快,另外一個是,給寫入訊息讓出磁盤的 IO 資源,間接也提升了寫入的性能,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/235383.html
標籤:架構設計
上一篇:C語言實作單鏈表的增刪查改
