GO-GRPC實踐(一) 完成第一個GRPC介面並使用etcd作為服務註冊和發現
阿新 • • 發佈:2021-06-12
https://me1onrind.github.io/2021/06/06/grpc_one/
demo程式碼地址
https://github.com/Me1onRind/go-demo
環境搭建
go
go 1.13 以上
需安裝的二進位制檔案
可執行檔名 | 安裝方式 | 作用 |
---|---|---|
protoc | https://github.com/protocolbuffers/protobuf/releases 下載安裝 | 將.proto檔案編譯為具體程式語言的檔案 |
protoc-gen-go | go get github.com/golang/protobuf/[email protected] | 將.proto檔案編譯為go檔案時需要的外掛 |
使用etcd作為服務註冊中心
https://github.com/etcd-io/etcd
非生產環境在本地單機部署或者使用docker執行即可
docker-compose.yml
https://github.com/Me1onRind/my_docker/blob/master/etcd/docker-compose.yml
編寫go服務端程式碼
專案目錄
./ ├── cmd │ └── grpc │ ├── client_test.go # 測試檔案 │ └── main.go # main檔案 ├── go.mod ├── go.sum ├── internal │ ├── controller # grpc介面實現 │ │ └── foo_controller.go │ ├── core │ │ └── register # 服務註冊實現 │ │ └── etcd.go ├── protobuf # proto原型檔案和編譯後的檔案 │ ├── build.sh │ ├── foo.proto │ └── pb │ └── foo.pb.go └── README.md
程式碼
greet.proto
syntax = "proto3";
package pb;
service Foo {
rpc Greet(GreetReq) returns (GreetResp);
}
message GreetReq {
string my_name = 1;
string msg = 2;
}
message GreetResp {
string msg = 1;
}
編譯proto檔案生成go程式碼
[root@debian go-demo]# protoc --proto_path=./: --go_out=plugins=grpc:./pb/. ./*.proto
為了避免每次都要輸一串命令(還有其他用處), 將編譯命令寫在shell腳本里
build.sh
#!/bin/bash
set -ue
cd `dirname $0`
protoc --proto_path=./: --go_out=plugins=grpc:./pb/. ./*.proto
之後更新proto檔案後只需執行
[root@debian go-demo]# sh protobuf/build.sh
foo_controller.go
實現定義的Foo介面
package controller
import (
"context"
"fmt"
"github.com/Me1onRind/go-demo/protobuf/pb"
)
type FooController struct {
}
func NewFooController() *FooController {
f := &FooController{}
return f
}
func (f *FooController) Greet(ctx context.Context, in *pb.GreetReq) (*pb.GreetResp, error) {
reply := fmt.Sprintf("Hello %s, I got your msg:%s", in.GetMyName(), in.GetMsg())
out := &pb.GreetResp{}
out.Msg = reply
return out, nil
}
etcd.go
服務註冊功能
package register
import (
"context"
"fmt"
"log"
"time"
uuid "github.com/satori/go.uuid"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/naming/endpoints"
)
var client *clientv3.Client
const (
prefix = "service"
)
func init() {
var err error
client, err = clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379"},
DialTimeout: 5 * time.Second,
})
if err != nil {
panic(err)
}
}
func Register(ctx context.Context, serviceName, addr string) error {
lease := clientv3.NewLease(client)
cancelCtx, cancel := context.WithTimeout(ctx, time.Second*3 )
defer cancel()
leaseResp, err := lease.Grant(cancelCtx, 3)
if err != nil {
return err
}
leaseChannel, err := lease.KeepAlive(context.Background(), leaseResp.ID) // 長連結, 不用設定超時時間
if err != nil {
return err
}
em, err := endpoints.NewManager(client, prefix)
if err != nil {
return err
}
cancelCtx, cancel = context.WithTimeout(ctx, time.Second*3)
defer cancel()
if err := em.AddEndpoint(cancelCtx, fmt.Sprintf("%s/%s/%s", prefix, serviceName, uuid.NewV4().String()), endpoints.Endpoint{
Addr: addr,
}, clientv3.WithLease(leaseResp.ID)); err != nil {
return err
}
go func() {
for {
select {
case resp := <-leaseChannel:
if resp != nil {
//log.Println("keep alive success.")
} else {
time.Sleep(time.Second)
log.Println("keep alive failed.")
}
case <-ctx.Done():
log.Println("close service register")
cancelCtx, cancel = context.WithTimeout(ctx, time.Second*3)
defer cancel()
em.DeleteEndpoint(cancelCtx, serviceName)
lease.Close()
client.Close()
return
}
}
}()
return nil
}
main.go
package main
import (
"context"
"fmt"
"log"
"net"
"github.com/Me1onRind/go-demo/internal/controller"
"github.com/Me1onRind/go-demo/internal/core/register"
"github.com/Me1onRind/go-demo/protobuf/pb"
"google.golang.org/grpc"
)
func registerService(s *grpc.Server) {
pb.RegisterFooServer(s, controller.NewFooController())
}
func main() {
addr := "127.0.0.1:8080"
ctx := context.Background()
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
registerService(s)
if err := register.Register(ctx, "go-demo", addr); err != nil { // 服務註冊名: go-demo
log.Fatalf("register %s failed:%v", "go-demo", err)
}
fmt.Printf("start grpc server:%s", addr)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
通過服務發現呼叫Foo.Greet方法
client_test.go
package main
import (
"context"
"testing"
"time"
"github.com/Me1onRind/go-demo/protobuf/pb"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/naming/resolver"
"google.golang.org/grpc"
)
func Test_Greet(t *testing.T) {
cli, err := clientv3.NewFromURL("http://localhost:2379")
if err != nil {
t.Fatal(err)
}
builder, err := resolver.NewBuilder(cli)
if err != nil {
t.Fatal(err)
}
conn, err := grpc.Dial("etcd:///service/go-demo",
grpc.WithResolvers(builder),
grpc.WithBalancerName("round_robin"),
grpc.WithInsecure(), grpc.WithTimeout(time.Second))
if err != nil {
t.Fatal(err)
}
fooClient := pb.NewFooClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
resp, err := fooClient.Greet(ctx, &pb.GreetReq{
MyName: "Bar",
Msg: "Hello, World",
})
if err != nil {
t.Fatal(err)
}
t.Log(resp.Msg)
}
驗證
啟動server
[root@debian go-demo]# go run cmd/grpc/main.go
start grpc server:127.0.0.1:8080
可以使用etcd命令列客戶端/UI客戶端看到, 服務已經註冊上去
客戶端呼叫
=== RUN Test_Greet
client_test.go:43: Hello Bar, I got your msg:Hello, World
-- PASS: Test_Greet (0.00s)
PASS
ok github.com/Me1onRind/go-demo/cmd/grpc 0.010s