go微服務框架go-micro深度學習(二) 入門例子
上一篇帖子簡單介紹了go-micro的整體框架結構,這一篇主要寫go-micro使用方式的例子,中間會穿插一些go-micro的原始碼,和呼叫流程圖,幫大家更好的理解go-micro的底層。更詳細更具體的呼叫流程和細節,會在以後的帖子裡詳細講解。
例子的github地址: ofollow,noindex"> gomicrorpc 跑一遍例子,也就會明白個大概。
安裝所需要的環境
go-micro服務發現預設使用的是 consul ,
brew install consul consul agent -dev
或者直接使用使用docker跑
docker run -p 8300:8300 -p 8301:8301 -p 8301:8301/udp -p 8302:8302/udp -p 8302:8302 -p 8400:8400 -p 8500:8500 -p 53:53/udp consul
我個人更喜歡 etcdv3 原因我上一篇也有提到過,gomicro服務發現不支援consul叢集,我之前也寫過 etcdv3 叢集的搭建和使用 帖子,有時間大家可以看一下
安裝go-micro框架
go get github.com/micro/go-micro
安裝protobuf和依賴 prtobuf的基礎知識我這裡就不講了,如果不瞭解的可以看一下 官方文件 ,就是一個跨平臺,跨語言的資料序列化庫,簡單易學。
是go-micro用於幫助我們生成服務介面和一系列的呼叫程式碼
brew install protobuf go get -u -v github.com/golang/protobuf/{proto,protoc-gen-go} go get -u -v github.com/micro/protoc-gen-micro
protobuf也可以直接從原始碼安裝
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-all-3.6.1.tar.gz tar zxvf protobuf-all-3.6.1.tar.gz cd protobuf-3.6.1/ ./autogen.sh ./configure make make install protoc -h
安裝micro工具包,這個安裝是可選項,micro提供了一系列的工具來幫助我們更好的使用go-micro。
go get github.com/micro/micro
例子1
建立proto檔案common.proto,這個檔案包含了傳入和返回的引數,引數包含了常用的基礎型別、陣列、map等。還有一個Say 服務,這個服務裡有一個rpc方法。
syntax = "proto3"; package model; message SayParam { string msg = 1; } message Pair { int32 key = 1; string values = 2; } message SayResponse { string msg = 1; // 陣列 repeated string values = 2; // map map<string, Pair> header = 3; RespType type = 4; } enum RespType { NONE = 0; ASCEND = 1; DESCEND = 2; } // 服務介面 service Say { rpc Hello(SayParam) returns (SayResponse) {} }
在根目錄下執行,生成兩個模板檔案
protoc --proto_path=$GOPATH/src:. --micro_out=. --go_out=. example1/proto/*.proto
一個檔案是proto的go 結構檔案,還有一個go-micro rpc的介面檔案。
server 端:
type Say struct {} func (s *Say) Hello(ctx context.Context, req *model.SayParam, rsp *model.SayResponse) error { fmt.Println("received", req.Msg) rsp.Header = make(map[string]*model.Pair) rsp.Header["name"] = &model.Pair{Key: 1, Values: "abc"} rsp.Msg = "hello world" rsp.Values = append(rsp.Values, "a", "b") rsp.Type = model.RespType_DESCEND return nil } func main() { // 我這裡用的etcd 做為服務發現,如果使用consul可以去掉 reg := etcdv3.NewRegistry(func(op *registry.Options){ op.Addrs = []string{ "http://192.168.3.34:2379", "http://192.168.3.18:2379", "http://192.168.3.110:2379", } }) // 初始化服務 service := micro.NewService( micro.Name("lp.srv.eg1"), micro.Registry(reg), ) // 註冊 Handler model.RegisterSayHandler(service.Server(), new(Say)) // run server if err := service.Run(); err != nil { panic(err) } }
服務發現我使用的是etcdv3 替換了預設的consul
micro.NewService 初始化服務,然後返回一個Service介面的例項,newService()方法的大概流程如下,
先是給各個介面初始化預設值,再使用傳入的值替換預設值,這也是go-micro可替換外掛的地方。
service有一個Init()可選方法,這是一個單例方法,
func (s *service) Init(opts ...Option) { // process options for _, o := range opts { o(&s.opts) } s.once.Do(func() { // save user action action := s.opts.Cmd.App().Action // set service action s.opts.Cmd.App().Action = func(c *cli.Context) { .........//這裡就不把程式碼全顯示出來了 ......... } }
官方的例子中者有顯示呼叫,其實是不必的,因為在替換預設值的時候都會呼叫Init方法
比如micro.Name()方法,已經呼叫了Init()方法了
// Name of the service func Name(n string) Option { return func(o *Options) { o.Server.Init(server.Name(n)) } }
service.Run()方法 呼叫流程
因為在初始化的時候沒有指定埠,系統會自動分配一個埠號分給Server,並把這個server的資訊註冊到Register。
BeferStart和AfterStart也都是可以自定義的
client 端:
func main() { // 我這裡用的etcd 做為服務發現,如果使用consul可以去掉 reg := etcdv3.NewRegistry(func(op *registry.Options){ op.Addrs = []string{ "http://192.168.3.34:2379", "http://192.168.3.18:2379", "http://192.168.3.110:2379", } }) // 初始化服務 service := micro.NewService( micro.Registry(reg), ) sayClent := model.NewSayService("lp.srv.eg1", service.Client()) rsp, err := sayClent.Hello(context.Background(), &model.SayParam{Msg: "hello server"}) if err != nil { panic(err) } fmt.Println(rsp) }
上面根據proto檔案的生成的兩個檔案中有一個是rpc的介面檔案,介面檔案已經幫我們把呼叫方法的整個流程封裝好了。
只需要給出服務名稱和licent就可以。然後呼叫Hello方法
原始碼:
func (c *sayService) Hello(ctx context.Context, in *SayParam, opts ...client.CallOption) (*SayResponse, error) { req := c.c.NewRequest(c.name, "Say.Hello", in) out := new(SayResponse) err := c.c.Call(ctx, req, out, opts...) if err != nil { return nil, err } return out, nil }
主要的流程裡都在c.c.Call方法裡。簡單來說流程如下
就是得到節點資訊address,根據address去查詢 pool裡是否有連線,如果有則取出來,如果沒有則建立,然後進行資料傳輸,傳輸完成後把client放回到pool內。pool的大小也是可以控制的,這部分的程式碼讀起來特別爽,具體的細節和處理流程會在以後的帖子裡詳細講解
例子2
例子1,做了一個簡單的服務,已經不能再簡單了,只是為了能讓大家熟悉一下go-micro。看完例子1後應該會有更多的想法,想使用更多的go-micro的功能,比如protobuf生成的類都在一起,如果想model和api分開怎麼處理,怎麼使用go-micro的雙向流,怎麼使用訊息推送,等等。所以我就雙做了一個小例子,這個例子裡包含了一些東西。
這個例子我就只說一下組織結構,也沒有多少程式碼,大家有時間看一下就ok了。
proto下的兩個資料夾,一個model一個rpcapi,是把資料和api分開,api引用了model
看一下rpcapi
syntax = "proto3"; package rpcapi; import "github.com/lpxxn/gomicrorpc/example2/proto/model/common.proto"; // 服務介面 service Say { rpc Hello(model.SayParam) returns (model.SayResponse) {} rpc Stream(model.SRequest) returns (stream model.SResponse) {} }
import了model裡的common.proto
在生成的時候一個只要go_out另一個只要micro_out就好了
protoc --proto_path=$GOPATH/src:. --go_out=. example2/proto/model/*.proto protoc --proto_path=$GOPATH/src:. --micro_out=. example2/proto/rpcapi/*.proto
訂閱一個資訊
// Register Subscribers if err := server.Subscribe(server.NewSubscriber(common.Topic1, subscriber.Handler)); err != nil { panic(err) }
當有資訊傳送時,所有訂閱了lp.srv.eg2.topic1這個資訊的服務都會收到資訊
客戶端傳送資訊
p := micro.NewPublisher(common.Topic1, service.Client()) p.Publish(context.TODO(), &model.SayParam{Msg: lib.RandomStr(lib.Random(3, 10))})
如果是生產環境一定不要用go-micro預設的資訊釋出和訂閱處理方式,micro的外掛plugin裡是有很多成熟的外掛。
使用雙向流的小功能
這個方法只是每次向客戶端傳送一些資料,每次只發送一部分。比如我們給客戶端推送的資料很大時,一次性全都推過去,是不太正確的做法,分批推送還是比較好的。
func (s *Say) Stream(ctx context.Context, req *model.SRequest, stream rpcapi.Say_StreamStream) error { for i := 0; i < int(req.Count); i++ { rsp := &model.SResponse{} for j := lib.Random(3, 5); j < 10; j++ { rsp.Value = append(rsp.Value, lib.RandomStr(lib.Random(3, 10))) } if err := stream.Send(rsp); err != nil { return err } // 模擬處理過程 time.Sleep(time.Microsecond * 50) } return nil return nil }
希望這個小例子能讓大家入門go-micro.