上一篇帖子go微服務框架go-micro深度學習(三) Registry服務的註冊和發現詳細解釋了go-micro是如何做服務註冊和發現在,服務端註冊server資訊,client獲取server的地址資訊,就可以和服務建立連線,然後就可以進行通訊了。這篇帖子詳細說一下,go-micro的通訊協議、編碼,和具體服務方法的呼叫過程是如何實現的,文中的程式碼還是我github上的例子: gomicrorpc
go-micro 支援很多通訊協議:http、tcp、grpc等,支援的編碼方式也很多有json、protobuf、bytes、jsonrpc等。也可以根據自己的需要實現通訊協議和編碼方式。go-micro 預設的通訊協議是http,預設的編碼方式是protobuf,我就以預設的方式來分解他的具體實現。
服務的啟動
go-micro在啟動的時候會選擇預設通訊協議http和protobuf編碼方式,但他是如何路由到具體方法的?在go-micro服務端啟動的時候我們需要註冊Handler,也就是我們具體實現結構體 ,如例子中註冊方法時,我們呼叫的RegisterSayHandler方法
// 註冊 Handler
rpcapi.RegisterSayHandler(service.Server(), new(handler.Say))複製程式碼
這個方法內部的體實現主要是利用了反射的力量,註冊的物件是實現了rpc介面的方法,如我們的Say實現了SayHandler。go-micro預設的router會利用反射把Say物件的資訊完全提取出來,解析出結構體內的方法及方法的引數,儲存到一個map內-> map[結構體名稱][方法資訊集合]
具體的實現在rpc_router.go裡router的Handle(Handler)方法,組織完成後map的是下圖這樣,儲存了很多反射資訊,用以將來呼叫。
下面是這個方法的主要程式碼,刪除了一些,很希望大家讀一下rpc_router.go裡面的程式碼,prepareMethod方法是具體利用反射提取資訊的方法。
func (router *router) Handle(h Handler) error {
router.mu.Lock()
defer router.mu.Unlock()
// ....
rcvr := h.Handler()
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
// check name
// ....
s.name = h.Name()
s.method = make(map[string]*methodType)
// Install the methods
for m := 0; m < s.typ.NumMethod(); m++ {
method := s.typ.Method(m)
// prepareMethod會把所有解析的資訊返回來
if mt := prepareMethod(method); mt != nil {
s.method[method.Name] = mt
}
}
// .....
// save handler
router.serviceMap[s.name] = s
return nil
}複製程式碼
serviceMap裡儲存的就是反射後的資訊,下圖是我用goland的debug得到的儲存資訊
路由資訊處理完後,主要的工作就已經完成了,然後註冊服務並啟動服務,啟動的服務是一個http的服務,我們可以看一下http_transport.go裡的程式碼
服務的簡單流程圖如下 ,選擇通訊協議和編碼方式->註冊服務方法->啟動服務並註冊服務資訊
客戶端呼叫服務方法
客戶端在啟動的時候也要選擇預設的通訊協議http,和protobuf編碼。客戶端在呼叫rpc方法的時候如:
rsp, err := client.Hello(context.Background(), &model.SayParam{Msg: "hello server"})複製程式碼
go-micro為我們自動生成的rpcapi.micro.go裡我們可以看一上Hello的具體實現,沒有幾行程式碼,但內部還是做了很多工作
func (c *sayService) Hello(ctx context.Context, in *model.SayParam, opts ...client.CallOption) (*model.SayResponse, error) {
req := c.c.NewRequest(c.name, "Say.Hello", in)
out := new(model.SayResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}複製程式碼
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
var opts RequestOptions
for _, o := range reqOpts {
o(&opts)
}
// set the content-type specified
if len(opts.ContentType) > 0 {
contentType = opts.ContentType
}
return &rpcRequest{
service: service,
method: endpoint,
endpoint: endpoint,
body: request,
contentType: contentType,
opts: opts,
}
}複製程式碼
簡單流程圖如下
client:封裝引數-> 編碼資料->連線服務->傳送資料->接收返回資料,並解碼。
service: 接收資料->解碼資料,找到相應的例項和方法,利用反射呼叫具體方法->編碼返資料->傳送給客戶端。
服務端處理請求
當服務端監接收到資料後,從http的Request裡的Header中讀取到相應的資訊:編碼方式,endpoint,請求的資料,由路由器進行對比和匹配找到儲存的反射資訊,利用反射把請求的資料根據相應的編碼方式進行解碼,再利用反射呼叫具體的方法,處理完把返回資料進行編碼,組織一個http.Response傳輸給使用者,客戶端接收到資料後進行解碼讀取資料。 rpc_router.go裡的call方法就是具體的呼叫過程方法,有時間大家可以讀一下。