RPC 的變革 —— ARPC 專案自薦

lesismal發表於2020-12-09

RPC 的變革 —— ARPC 專案自薦

一、ARPC 示例

echo server

package main

import (
    "log"

    "github.com/lesismal/arpc"
)

func onEcho(ctx *arpc.Context) {
    str := ""
    err := ctx.Bind(&str)
    if err != nil {
        log.Printf("/echo error: %v", err)
        return
    }
    ctx.Write(str)
    log.Printf("/echo: %v", str)
}

func main() {
    svr := arpc.NewServer()

    // register handler
    svr.Handler.Handle("/echo", onEcho)

    svr.Run(":8888")
}

echo client

package main

import (
    "context"
    "log"
    "net"
    "time"

    "github.com/lesismal/arpc"
)

func dialer() (net.Conn, error) {
    return net.DialTimeout("tcp", "localhost:8888", time.Second*3)
}

func main() {
    client, err := arpc.NewClient(dialer)
    if err != nil {
        log.Fatalf("NewClient failed: %v", err)
    }
    defer client.Stop()

    request := "hello"
    response := ""
    // err = client.Call("/echo", &request, &response, time.Second*5)
    err = client.CallWith(context.Background(), "/echo", &request, &response)
    if err != nil {
        log.Fatalf("Call /echo failed: %v", err)
    } else {
        log.Printf("Call /echo Response: \"%v\"", response)
    }
}

二、傳統主流的 RPC 框架的侷限/不爽

1. 網路互動模式單一,無法支援更豐富的場景

傳統主流 RPC 的網路互動主要是 [客戶端到服務端,請求 - 應答] 的模式,比較單一。按照這個模式以及顧名思義 “遠端過程呼叫”,其實 HTTP 也算是 RPC 的一種,只是由於其短連線和 HTTP 協議的文字編碼格式等原因導致效能和資源的浪費,所以很少有直接把 HTTP 稱為 RPC

網路通訊的本質是資料收發,客戶端和服務端都可以隨時主動傳送訊息給對方,比如推送服務IM遊戲等;而且一方傳送資料給另一方,有時是不需要回復的,比如VOIP 電話,其他不要求強一致的訊息廣播、推送等。

這裡把需要應答的通訊定義為 Call ,不需要應答的通訊定義為 Notify,則網路互動按照發起方、是否需要應答,可以分為以下四種基本模式:

序號 互動方式 是否需要應答 互動流程
1 Call 需要 客戶端發起請求,服務端應答
2 Call 需要 服務端發起請求,客戶端應答
3 Notify 不需要 客戶端向服務端發出通知/推送
4 Notify 不需要 客戶端向服務端發出通知/推送

如此看來,傳統主流 RPC 就像是個大內男公務員,因為它只支援了第一種基本模式,只覆蓋了 25%——甚至說它的網路通訊模式有點不完整,都算是一種褒獎,因為 25% 的模式支援那是相當不完整。

而只支援請求 - 應答的模式也限制了很多業務場景,其他更廣泛的業務場景比如推送服務IM遊戲,我們還需要自定義各種協議。

2. Server 端函式呼叫的寫法,函式返回即是呼叫結束,不夠靈活

傳統主流 RPC 服務端的寫法通常是一個函式,函式返回後框架層把返回值打包傳送給客戶端作為應答,不支援在該函式中進行非同步響應,尤其是 golang 的 RPC ,有的框架為了程式碼簡單,沒有寫協程,傳送資料時直接寫到 Conn ,高併發寫時競爭比較明顯會增加時延;有的框架預設採用讀協程收到資料 one-by-one 的方式處理,存線上頭阻塞的問題,有的框架採用每個訊息新開一個 go 協程處理,高併發時協程數量可能暴增、比較浪費,不支援按單個 Method/Router 定製同步或者非同步處理,也不支援在 Method/Router handler 內由業務層自主選擇同步處理、新開 go 協程非同步或者協程池等非同步等方式的處理。

三、關於 ARPC

1. 高效能

想說 ARPC 比其他流行的 golang RPC 效能都好,但是自吹最強好像沒有說服力,感謝 rpcx 有做一些主流 RPC 框架的效能對比,老倉庫 已經廢棄並且那時 ARPC 還沒有出生,有興趣的同學可以到 新倉庫 跑下程式碼進行對比,測試時請注意排除其他程式的干擾。

目前最為主流的 gRPC 因為官方綜合考量使用了 HTTP2 ,詳情參考 gRPC 的動機和設計原則,註定了不能很高效能。而很多 RPC 的業務場景,是基於內部服務叢集, HTTP2 的加密流程等顯得有些效能浪費。而 ARPC 更注重效能和靈活性,通訊協議部分交由業務層決定,通常建議使用 TCP 作為基礎通訊協議,如有需要,業務層也可以使用 TLSWebsocket 或者 KCP 等 。

2. 網路互動模式全面

上面在 不爽 的部分提到了傳統主流 RPC 的不完整, ARPC 當然要有比較全面的支援:

序號 互動方式 是否需要應答 互動流程 傳統主流 RPC ARPC
1 Call 需要 客戶端發起請求,服務端應答
2 Call 需要 服務端發起請求,客戶端應答
3 Notify 不需要 客戶端向服務端發出通知/推送
4 Notify 不需要 客戶端向服務端發出通知/推送

3. 豐富的業務場景支援

由於網路互動模式相對全面,ARPC 可以用於處理多種常規業務場景而不受類似 HTTP 短連結、單向請求 - 應答方式的限制。比如:

1)推送服務

2)遊戲服務

3)聊天服務

4)其他需要長連線、雙向、廣播等靈活互動方式的業務

4. 寫法簡單

示例所示, Handler 不採用函式返回即呼叫結束的形式,寫法簡單、更像 HTTP Handler 。由於也不強制使用編解碼器,甚至不必生成結構化訊息或者服務如 Protobuf 的 Message、Service 等,這樣也帶來一些額外的好處,比如熱點的結構化資料,業務層可以在資料更新時序列化一次並快取起來,有需要時直接傳送序列化之後的資料給需求方,避免每次傳送給每個連線都需要進行一次序列化的浪費,在高線上量的廣播類業務中這點尤為明顯。

其他一些 RPC 框架喜歡註冊物件的方式,由框架層通過反射去解析符合 Handler 格式的方法進行隱式註冊,由於早年被 C++ 的各種語言標準、機制等背後動作玩弄得辛苦,golang 專案中希望框架層和業務層都儘量不讓使用者增加沒有必要的心智負擔(比如通過物件隱式註冊的方式:沒有帶來效能提升,沒有架構設計模組設計的解耦或者其他優化), ARPC 的設計遵循簡單、透明的原則,所以像 HTTP 一樣進行顯式註冊,如果有的同行喜歡玩弄語法糖技巧或者被語法糖技巧玩弄,可以自行定製。

5. 更靈活的同步非同步

支援單個 Method/Router Handler 級別設定同步或者非同步處理,也支援 Handler 內由業務層自主控制同步或非同步回包、從而針對性定製快/慢介面的協程數量控制與線頭阻塞問題處理。

6. 最少依賴

目前如果只使用 ARPC 預設引數,則只使用了 golang 標準庫,不需要依賴其他第三方 package。

7. 易擴充套件

1)網路協議支援:由使用者自主決定,服務端實現 net.Listener、客戶端實現 net.Conn 即可做為 ARPC 的網路載體,arpc/examples/protocols 已經提供了 KCPQUICTLSUnixSocketUTPWebsocket 等示例,歡迎參考。

2)非結構化的訊息體編解碼支援:可以直接用 string 、 *string 、 [] byte 、 *[] byte 、 error 、 *error 等作為訊息體引數。

3)結構化的訊息體編解碼支援:為了最少依賴, ARPC 預設使用了 encoding/json 作為結構化訊息體的編解碼器,效能不夠強,但是業務層可以很方便地設定使用 json-iter 、 Protobuf 等作為結構化訊息地編解碼器。

4)訊息體編解碼中介軟體支援: ARPC 提供了訊息體編解碼中介軟體機制, arpc/middleware/coder 子包實現了 GzipTracer 作為預設示例,有需要的使用者可以參考實現自行定製,使用示例在 arpc/examples/middleware/coder

5)Method/Router 的中介軟體支援: ARPC 提供了類似流行的 golang HTTP 框架的中介軟體,方便業務層自行擴充套件, arpc/middleware/router 子包實現了 LoggerRecoverGraceful 作為預設示例,有需要的使用者可以參考並實現自行定製,使用示例在 arpc/examples/middleware/router

6)Web JS Client 支援: ARPC 提供了 JS Client 及示例:API 示例聊天示例。有了 JS Client , 不需要類似其他 RPC 框架那樣部署 HTTP 轉換 RPC 的閘道器,前端可以直接通過 WebsocketARPC 服務進行互動,而且因為 ARPC 已經包括了訊息的編解碼、Method/Router Handler,比 melody 等只封裝了收發資料的基礎 websocket 框架更方便。

其他擴充套件不一一列舉了,歡迎有興趣的同學檢視程式碼或者 New issue 。

8. 更多示例

arpc/examples提供了較為豐富的示例,如 通知廣播優雅退出服務註冊與發現連線池kcp/quic/tls/websocket 等協議支援釋出訂閱JS Web Chat 等,請見 arpc/examples

9. 其他

個人精力有限,並且 golang 是世界上第二好的程式語言,所以暫時不考慮對其他語言的支援,歡迎 pr、issue 。

更多原創文章乾貨分享,請關注公眾號
  • RPC 的變革 —— ARPC 專案自薦
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章