1. 簡單重構 RPC 框架
將之前寫好的 RPC 框架進行簡單重構,主要重構的工作
- 將各個類進行分類規劃到各個特定的包中,這些包有 client、server、service、rpc、protocol、transport等
- 客戶端與服務端都引入 dispatchor 獲取目標服務,因爲有時候服務既是服務端又是客戶端。客戶端在調用目標服務時,根據 dispatchor 獲取服務,獲取到說明服務存在本地,否則調用服務端。
2. 影響併發的因素
影響併發的因素主要有:
- reactor 模型,os 多路複用器 epoll
- eventloop(selector)佔用一個 CPU
- IO 的讀取是線性的(搬運,從內核到 app)
- 讀取到的內容,可以在當前線程(阻塞後續 IO 讀取),也可以在其他線程
- 考量:IO 上的消耗,尤其是在讀取時間和資源的佔比上
- 儘量是小的數據包(好的壓縮,1. 協議以上的減輕;2. 好的壓縮算法(消耗 CPU 時間,但是 CPU 一定比 IO 快))
3. 自定義HTTP協議解析和HTTPServer調用實現
- httpclientCoder 實現
- 通過 netty 實現支持自定義協議和 http 協議的 rpc 調用功能
- 通過啓動 jetty 實現 servlet 功能
- 切換客戶端與服務端的連接
客戶端編寫 http 傳輸協議代碼
服務器端編寫 http 傳輸協議代碼
客戶端編寫基於 nett 的傳輸協議代碼
服務器端編寫基於 jetty 的服務端代碼
注意事項:
- 在 netty 中使用 http1.1 協議時,需要在請求頭中添加 Host 屬性,指向目標 ip
- 在 netty 客戶端中發送 http 消息時,請求參數 content-length 屬性
- 在 netty 服務端使用 http 編解碼處理器,同時使用 SimpleChannelInboundHandler 類實現自定義處理邏輯時,需要注意重寫自定義處理器的構造器,參數
boolean autoRelease
要爲 false 時,才能在channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg)
方法中使用異步線程處理,因爲它的調用方法會執行根據 autoRelease 參數 執行ReferenceCountUtil.release(msg)
方法釋放資源操作。 - nett 只是一個 IO 框架,它可以支持自定義的協議、http 協議,兩者的區別是是否有狀態。http 協議是無狀態協議,即一個請求對應一個連接。當我們藉助 netty 利用 http 協議實現有狀態的請求,在發送數據時如果使用連接池,就一定要給請求添加請求頭唯一參數,這樣當數據返回時,程序才能根據連接中數據的唯一標識用來進行處理。
- 瀏覽器發送請求是與服務器端建立三次握手
4. 總結回顧
rpc 是有客戶端與服務端兩端,APP 調用代理,代理的對象是面向接口的,目標接口可能會存在多個節點,所以我們需要一個註冊發現服務,服務註冊有很多現成的服務,比如 zookeeper、eureka、etcd、nocos 等等。
接着就要使用負載均衡算法選擇一個服務節點,再來通過選擇一個協議來來封裝我們的數據,負載均衡的算法有很多,比如輪詢、隨機、哈希、權重等等。
這個協議有很多:自定義 header、http、rmi 等等,這些協議是要狀態的。什麼是有狀態和無狀態?無狀態是說在連接池取連接,並鎖定連接的發送和返回的生命週期裏;有狀態是說 consumer + provider 端同步實現有狀態協議(requestID),發送和接收可以異步,連接可以共享使用。
http 本身是無狀態連接,那麼它也是可以實現有狀態的連接,但是通過在客戶端和服務端的適當調整也是可以做成有狀態的連接,比如在請求時增加 requestID,服務端增加一些 session 來維護客戶端的連接等等,客戶端 + 服務端遵從 http 約束,對報文的封裝,是否斷開連接,保障發送 + 返回爲一個原子操作。
客戶端與服務端的連接可以複用,串行使用,也有池化的概念
我們需要遵從現實,有可能 provider 端不可控。
接下來就是發送數據,通過 IO 框架 netty 來發送數據,注意還有很多框架比如 mina2 等 IO 框架。