在做了服務化拆分之后,把業務邏輯都拆分到了單獨部署的服務中,那么假設在完成一次完整的請求時,需要調用4~5次服務,計算下來,RPC服務需要承載大概每秒10萬次的請求。那么,你該如何設計RPC框架,來承載如此大的請求量呢?你要做的是:
選擇合適的網絡模型,有針對性地調整網絡參數,以優化網絡傳輸性能;
選擇合適的序列化方式,以提升封包、解包的性能。
- 服務拆分單獨部署后,引入的服務跨網絡通信的問題;
- 在拆分成多個小服務之后,服務如何治理的問題。
RPC框架就封裝了網絡調用的細節,讓你像調用本地服務一樣,調用遠程部署的服務。Dubbo、Grpc、Thrift這些新興的框架才算是RPC框架。
另一個你可能聽過的技術是Web Service,它也可以認為是RPC的一種實現方式。它的優勢是,使用HTTP+SOAP協議,保證了調用可以跨語言,跨平台。只要你支持HTTP協議,可以解析XML,那么就能夠使用Web Service。它由於使用XML封裝數據,數據包大,性能還是比較差。
比方說,電商系統中,商品詳情頁面需要商品數據、評論數據還有店鋪數據,如果在一體化的架構中,你只需要從商品庫,評論庫和店鋪庫獲取數據就可以了,不考慮緩存的情況下有三次網絡請求。
但是,如果獨立出商品服務、評論服務和店鋪服務之后,那么就需要分別調用這三個服務,而這三個服務又會分別調用各自的數據庫,這就是六次網絡請求。如果你服務拆分的更細粒度,那么多出的網絡調用就會越多,請求的延遲就會更長,而這就是你為了提升系統的擴展性,在性能上所付出的代價。

如果優化RPC的性能,從而盡量減少網絡調用,對於性能的影響呢?在這里,你首先需要了解一次RPC的調用都經過了哪些步驟,因為這樣,你才可以針對這些步驟中可能存在的性能瓶頸點提出優化方案。步驟如下:
- 在一次RPC調用過程中,客戶端首先會將調用的類名、方法名、參數名、參數值等信息,序列化成二進制流;
- 然后客戶端將二進制流,通過網絡發送給服務端;
- 服務端接收到二進制流之后,將它反序列化,得到需要調用的類名、方法名、參數名和參數值,再通過動態代理的方式,調用對應的方法得到返回值;
- 服務端將返回值序列化,再通過網絡發送給客戶端;
- 客戶端對結果反序列化之后,就可以得到調用的結果了。

從這張圖中你可以看到,有網絡傳輸的過程,也有將請求序列化和反序列化的過程, 所以,如果要提升RPC框架的性能,需要從網絡傳輸和序列化兩方面來優化。
如何提升網絡傳輸性能
在網絡傳輸優化中,你首要做的,是選擇一種高性能的I/O模型。所謂I/O模型,就是我們處理I/O的方式。而一般單次I/O請求會分為兩個階段,每個階段對於I/O的處理方式是不同的。
首先,I/O會經歷一個等待資源的階段,比方說,等待網絡傳輸數據可用,在這個過程中我們對I/O會有兩種處理方式:
阻塞。指的是在數據不可用時,I/O請求一直阻塞,直到數據返回;
非阻塞。指的是數據不可用時,I/O請求立即返回,直到被通知資源可用為止。
然后是使用資源的階段,比如說從網絡上接收到數據,並且拷貝到應用程序的緩沖區里面。在這個階段我們也會有兩種處理方式:
同步處理。指的是I/O請求在讀取或者寫入數據時會阻塞,直到讀取或者寫入數據完成;
異步處理。指的是I/O請求在讀取或者寫入數據時立即返回,當操作系統處理完成I/O請求,並且將數據拷貝到用戶提供的緩沖區后,再通知應用I/O請求執行完成。
將這兩個階段的四種處理方式,做一些排列組合,再做一些補充,就得到了我們常見的五種I/O模型:
- 同步阻塞I/O
- 同步非阻塞I/O
- 同步多路I/O復用
- 信號驅動I/O
- 異步I/O
這五種I/O模型,你需要理解它們的區別和特點,不過在理解上你可能會有些難度,所以我來做個比喻,方便你理解。
我們來把I/O過程比喻成燒水倒水的過程,等待資源(就是燒水的過程),使用資源(就是倒水的過程):
- 如果你站在炤台邊上一直等着(等待資源)水燒開,然后倒水(使用資源),那么就是同步阻塞I/O;
- 如果你偷點兒懶,在燒水的時候躺在沙發上看會兒電視(不再時時刻刻等待資源),但是還是要時不時的去看看水開了沒有,一旦水開了,馬上去倒水(使用資源),那么這就是同步非阻塞I/O;
- 如果你想要洗澡,需要同時燒好多壺水,那你就在看電視的間隙去看看哪壺水開了(等待多個資源),哪一壺開了就先倒哪一壺,這樣就加快了燒水的速度,這就是同步多路I/O復用;
- 不過你發現自己總是跑廚房去看水開了沒,太累了,於是你考慮給你的水壺加一個報警器(信號),只要水開了就馬上去倒水,這就是信號驅動I/O;
- 最后一種就高級了,你發明了一個智能水壺,在水燒好后自動就可以把水倒好,這就是異步I/O。
- 這五種I/O模型中最被廣泛使用的是多路I/O復用,Linux系統中的select、epoll等系統調用都是支持多路I/O復用模型的,Java中的高性能網絡框架Netty默認也是使用這種模型。所以,我們可以選擇它。
總結:
網絡I/O模型和序列化方式的選擇,它們是實現高並發RPC框架的要素,總結起來有三個要點:
- 1.選擇高性能的I/O模型,這里我推薦使用同步多路I/O復用模型;
- 2.調試網絡參數,這里面有一些經驗值的推薦。比如將tcp_nodelay設置為true,也有一些參數需要在運行中來調試,比如接受緩沖區和發送緩沖區的大小,客戶端連接請求緩沖隊列的大小(back log)等等;
- 3.序列化協議依據具體業務來選擇。如果對性能要求不高,可以選擇JSON,否則可以從Thrift和Protobuf中選擇其一。
