
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
識訓更多架構知識,請關注微信公眾號 螢火架構,原創內容,轉載請注明出處,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/462982.html
標籤:其他

