上一篇帖子簡單介紹了go-micro的總體框架結構,這一篇主要寫go-micro使用方式的例子,中間會穿插一些go-micro的源碼,和調用流程圖,幫你們更好的理解go-micro的底層。更詳細更具體的調用流程和細節,會在之後的帖子裏詳細講解。html
例子的github地址: gomicrorpc 跑一遍例子,也就會明白個大概。git
go-micro服務發現默認使用的是consul,github
brew install consul
consul agent -dev複製代碼
或者直接使用使用docker跑golang
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 集羣的搭建和使用帖子,有時間你們能夠看一下docker
安裝go-micro框架api
go get github.com/micro/go-micro複製代碼
安裝protobuf和依賴 prtobuf的基礎知識我這裏就不講了,若是不瞭解的能夠看一下官方文檔,就是一個跨平臺,跨語言的數據序列化庫,簡單易學。數組
是go-micro用於幫助咱們生成服務接口和一系列的調用代碼bash
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也能夠直接從源碼安裝app
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複製代碼
建立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),
)
service.Init()
// 註冊 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) {
.........//這裏就不把代碼全顯示出來了
.........
}
}複製代碼
用於始化cmd的一些信息
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),
)
service.Init()
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的大小也是能夠控制的,這部分的代碼讀起來特別爽,具體的細節和處理流程會在之後的帖子裏詳細講解
例子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.