go-micro是一個知名的golang微服務框架,最新版本是v4,這篇文章將介紹go-micro v4開發RPC服務的方法及其運作原理。
基本概念
go-micro有幾個重要的概念,後邊開發RPC服務和介紹其執行原理的時候會用到,這裡先熟悉下:
- Service:代表一個go-micro應用程式,Service中包括:Server、Client、Broker、Transport、Registry、Config、Store、Cache等程式執行所需的各個模組。
- Server:代表一個go-micro伺服器,主要函式包括:Start、Stop、Handle、Subscribe。預設建立的Server是 rpcServer。
- Broker:用於處理非同步訊息,主要的函式包括:Connect、Publish、Subscribe。預設的Broker是httpBroker。
- Router:用於訊息處理的路由,內部包括兩種路由方式:RPC服務對映serviceMap和訊息訂閱器subscribers。
- Codec:用於訊息的編解碼,主要函式包括:Marshal、Unmarshal預設的Codec是json.Marshaler,是基於jsonpb的。RPC服務是根據請求頭中的Content-Type自動建立的。
- Registry:用於服務發現,主要函式包括:Register、Deregister、GetService、ListServices、Watch。預設的Registry是mdns。
- Selector: 用於從同一個服務的多個例項之中選擇一個,支援快取,有隨機和輪詢兩種策略。
- Transport:用於同步通訊,主要函式包括:Dial、Listen。它的底層基於Socket的send、recv語義,有多種實現,包括http、grpc、quic等。預設的Transport是httpTransport。
開發RPC服務
RPC全稱是Remote Procedure Call,翻譯過來是就是:遠端過程呼叫,中心思想是:像呼叫本地函式一樣呼叫遠端函式。常見的Dubbo、Spring Cloud都可以稱為RPC框架,還有最近很流行的gRPC。
使用go-micro建立一個RPC服務很簡單,共分三步走:
1、編寫proto協議檔案
這個服務提供的功能很簡單,名字為Hello,提供一個方法名字為Say,需要傳入一個字串Name,然後返回一個字串Message。這個檔案我命名為 hello.proto,放到了專案中的 proto 資料夾中。
syntax = "proto3";
option go_package="/proto";
package Business;
service Hello {
rpc Say (SayRequest) returns (SayResponse);
}
message SayResponse {
string Message = 1;
}
message SayRequest {
string Name = 1;
}
2、生成go-micro服務端代理
需要首先安裝protoc和兩個程式碼生成外掛。
protoc下載地址:https://github.com/protocolbuffers/protobuf/releases,儲存到 GOPATH/bin目錄中。同時建議將 GOPATH/bin 新增到環境變數 PATH 中,方便直接執行相關命令。
兩個外掛直接通過命令即可安裝:
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install go-micro.dev/v4/cmd/protoc-gen-micro@v4
然後在專案的目錄下執行命令:
protoc --go_out=. --go_opt=paths=source_relative --micro_out=. --micro_opt=paths=source_relative proto/hello.proto
然後會在proto資料夾中生成兩個檔案:hello.pb.go 和 hello.pb.micro.go 。
下個步驟中就要使用它們來建立RPC服務。
3、編寫go-micro服務
這裡先把程式碼貼出來,然後再做一個簡要說明:
package main
import (
"context"
"fmt"
"log"
"rpchello/proto"
"go-micro.dev/v4"
"go-micro.dev/v4/server"
)
type Hello struct{}
func (s *Hello) Say(ctx context.Context, req *proto.SayRequest, rsp *proto.SayResponse) error {
fmt.Println("request:", req.Name)
rsp.Message = "Hello " + req.Name
return nil
}
func main() {
rpcServer := server.NewServer(
server.Name("rpchello.service"),
server.Address("0.0.0.0:8001"),
)
proto.RegisterHelloHandler(rpcServer, &Hello{})
service := micro.NewService(
micro.Server(rpcServer),
)
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
上邊我們建立了一個 Hello 型別,然後給它繫結了一個名為Say的函式。這個是和proto協議對應的,其實是實現了生成程式碼 hello.pb.micro.go 中的HelloHandler介面:
type HelloHandler interface {
Say(context.Context, *SayRequest, *SayResponse) error
}
然後main函式中是我們的重頭戲:先建立一個Server,預設情況下就是rpc Server,設定它的名字、監聽地址等引數;然後建立一個Service,並繫結剛剛建立的Server;然後使用生成的服務端代理函式將我們編寫的Hello服務註冊到Server中;最後開啟執行Service。
當然只有一個服務端沒有什麼意義,還得有客戶端來訪問它。這裡也給一個例子:
package main
import (
"bufio"
"context"
"fmt"
"os"
"rpchello/proto"
"go-micro.dev/v4"
"go-micro.dev/v4/client"
)
func main() {
service := micro.NewService(
micro.Client(client.NewClient()),
)
service.Init()
client := proto.NewHelloService("rpchello.service", service.Client())
rsp, err := client.Say(context.TODO(), &proto.SayRequest{Name: "BOSSMA"})
if err != nil {
fmt.Println(err)
}
fmt.Println(rsp)
fmt.Println("Press Enter key to exit the program...")
in := bufio.NewReader(os.Stdin)
_, _, _ = in.ReadLine()
}
這裡呼叫服務的時候沒有指定服務的地址和埠,因為內部走了服務發現,服務端會自動註冊服務,客戶端會根據服務名稱查詢到對應的地址和埠。預設的服務發現機制使用的是mdns。
RPC服務的執行原理
這裡從服務端的角度進行介紹,先來看一張圖:
請大家參考程式碼從上往下看。
NewServer 時建立一個rpcServer,這個rpcServer還會建立一個httpTransport用於程式間網路通訊,並繫結到當前rpcServer。
RegisterXXXHandler 時使用我們編寫的Handler建立一個內部的service例項,然後註冊這個service例項到rpcServer內部的router中,客戶端請求時會用到它。這裡其實可以註冊任意一個帶方法的型別,並不一定要定義proto協議,定義它只是為了協作更方便。
Service.Run 時會呼叫rpcServer的Start方法,這個方法內部會呼叫其繫結的httpTransport的Listen方法,然後在其建立的Listener上接收客戶端連線,接收方法Accept傳入了當前rpcServer的連線處理方法:rpcServer.ServeConn,有連線到來時會呼叫它。
當客戶端請求來臨時,客戶端連線被交給rpcServer的ServeConn方法,然後又呼叫到HandleEvent方法。
然後進入rpcServer內部的router的函式ServeRequest中,通過分析請求訊息,找到請求的服務名字和方法名字,在router中找到前面註冊過的service,通過servcie.call,再進入function.call,最終通過反射呼叫到我們編寫的Handler的業務方法。
有的同學可能會想,反射不是效能很低嗎?!反射效能低主要是查詢方法和欄位的時候,呼叫方法的效能並不低,而查詢方法和欄位等的操作已經在RegisterXXXHandler的步驟中做了,並且快取到了router中,所以效能並不受影響。
以上就是本文的主要內容了,如有問題,歡迎交流。演示程式碼已釋出到Github:https://github.com/bosima/go-demo/tree/main/go-micro-rpc-hello
收穫更多架構知識,請關注微信公眾號 螢火架構。原創內容,轉載請註明出處。