go-gRPC 初体验

语言: CN / TW / HK

微服务想必大家都不陌生了。刚接触到 golang ,那么在 golang 中怎么使用微服务呢。这里使用 gRRC 框架写了一个简单的例子。

环境要求

示例代码 iris-grpc-example

项目结构

iris-grpc-example
│  .gitignore
│  go.mod
│  go.sum
│  README.md
│
├─proto
│      README.md
│      servers.pb.go
│      servers.proto
│
└─servers
        main.go
        services.go

通过目录可以看到这里使用了 go mod ,当前 golang 版本 1.13

proto

使用 Protobuf 定义了通信的IDL,可以理解为 rpc 中接口定义。

  • protocol buf

可扩展的序列化数据结构,在通信协议中使用的比较广泛。比 json 更快,更小。比 xml 更简洁。

servers.proto

syntax = "proto3";
package proto;
message Id {
    int32 id=1;
}
message Name {
    string name=1;
}
message Age {
    int32 age=1;
}
// 用户变量
message User {
    int32 id=1;
    string name=2;
    int32 age=3;
}
// 用户参数
message UserParams{
    Name name=1;
    Age age=2;
}
// 声明那些方法可以使用rpc
service ServiceSearch{
    rpc SaveUser(UserParams) returns (Id){}
    rpc UserInfo(Id) returns (User){}
}

简单说明

syntax = "proto3"; 声明了 proto 语法版本。

package 声明包名

message Id {
    int32 id=1;
}

接口中使用的变量声明 变量名称: Id ,类型: int32 ,等号后面表示字段编号为 1

service ServiceSearch{
    rpc SaveUser(UserParams) returns (Id){}
    rpc UserInfo(Id) returns (User){}
}

声明了两个函数 SaveUser(),UserInfo() 是使用 RPC 协议,接收的参数与返回参数分别为什么。

servers.go

该文件是使用 servers.proto 编译生成的

。在完成 servers.proto 之后 在 proto 目录下执行

protoc --go_out=plugins=grpc:. *.proto

Protoc plugin-go
grpc

编译命令执行完之后,就会生成 servers.go 。而我们在 go 的模块中实际使用的代码也就是这个文件。

如果有兴趣的同学可以看看里面的代码,主要就是一些参数定义【 我们在proto中所定义的 】,还有一个接口的声明。

......
// ServiceSearchServer is the server API for ServiceSearch service.
type ServiceSearchServer interface {
    SaveUser(context.Context, *UserParams) (*Id, error)
    UserInfo(context.Context, *Id) (*User, error)
}

// UnimplementedServiceSearchServer can be embedded to have forward compatible implementations.
type UnimplementedServiceSearchServer struct {
}

func (*UnimplementedServiceSearchServer) SaveUser(ctx context.Context, req *UserParams) (*Id, error) {
    return nil, status.Errorf(codes.Unimplemented, "method SaveUser not implemented")
}
func (*UnimplementedServiceSearchServer) UserInfo(ctx context.Context, req *Id) (*User, error) {
    return nil, status.Errorf(codes.Unimplemented, "method UserInfo not implemented")
}

func RegisterServiceSearchServer(s *grpc.Server, srv ServiceSearchServer) {
    s.RegisterService(&_ServiceSearch_serviceDesc, srv)
}
......

可以看到最后有一个 RegisterServiceSearchServer 注册服务的方法,接受一个 grpc.Server 与一个 ServiceSearchServer 的接口。而我们在 servers/services.go 中,主要就是实现 ServiceSearchServer 这个接口,并通过 RegisterServiceSearchServer 将接口实现的函数注册 rpc 服务中。

servers

rpc 接口的实现与调用

services.go 接口的实现

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "iris-grpc-example/proto"
    "log"
    "math/rand"
    "net"
)

type ServiceSearch struct{}

func main() {
    listen, err := net.Listen("tcp", "127.0.0.1:9527")
    if err != nil {
        log.Fatalf("tcp listen failed:%v", err)
    }
    server := grpc.NewServer()
    fmt.Println("services start success")
    proto.RegisterServiceSearchServer(server, &ServiceSearch{})
    server.Serve(listen)

}

//保存用户
func (Service *ServiceSearch) SaveUser(ctx context.Context, params *proto.UserParams) (*proto.Id, error) {
    id := rand.Int31n(10) //随机生成id 模式保存成功
    res := &proto.Id{Id: id}
    fmt.Printf("username:%s,age:%d\r\n", params.Name, params.Age)
    return res, nil
}

func (Service *ServiceSearch) UserInfo(ctx context.Context, id *proto.Id) (*proto.User, error) {
    res := &proto.User{Id:id.GetId(),Name:"test",Age:31}
    return res, nil
}
  • 实现 ServiceSearchServer 接口,在代码中声明了一个 ServiceSearch 来实现了 ServiceSearchServer 接口。
  • SaveUser ,实现了 proto 中定义的 SaveUser 方法,需要注意的是这里需要返回两个参,数第一个是我们预先定好的参数,第二个为定义的错误信息。
  • main 函数声明当前服务的ip以及端口,并创建了一个 grpc server 然后通过 proto.RegisterServiceSearchServer(server, &ServiceSearch{})ServiceSearch 注册到 grpc

main.go 接口的调用

package main

import (
    "context"
    "github.com/kataras/iris/v12"
    "google.golang.org/grpc"
    "iris-grpc-example/proto"
    "log"
)

var client proto.ServiceSearchClient

func main() {
    app := iris.New()
    app.Logger().SetLevel("debug") //debug
    app.Handle("GET", "/testSaveUser", saveUser)
    app.Handle("GET", "/testUserInfo", userInfo)
    app.Run(iris.Addr("127.0.0.1:8080"))
}

func saveUser(ctx iris.Context) {
    params := proto.UserParams{}
    params.Age = &proto.Age{Age: 31}
    params.Name = &proto.Name{Name: "test"}
    res, err := client.SaveUser(context.Background(), &params)
    if err != nil {
        log.Fatalf("client.SaveUser err: %v", err)
    }
    ctx.JSON(res)
}
func userInfo(ctx iris.Context) {
    res, err := client.UserInfo(context.Background(), &proto.Id{Id: 1})
    if err != nil {
        log.Fatalf("client.userInfo err: %v", err)
    }
    ctx.JSON(res)
}

func init() {
    connect, err := grpc.Dial("127.0.0.1:9527", grpc.WithInsecure())
    if err != nil {
        log.Fatalln(err)
    }
    client = proto.NewServiceSearchClient(connect)

}

这里使用了 iris 作为了一个 client 。与传统 http 的区别主要是在

func init() {
    connect, err := grpc.Dial("127.0.0.1:9527", grpc.WithInsecure())
    if err != nil {
        log.Fatalln(err)
    }
    client = proto.NewServiceSearchClient(connect)

}

这里创建了一个 rpcclient 。在使用的时候我们只需要调用 services 里面已经写好的函数即可。

测试

\iris-grpc-example>cd servers

开启服务: go run services.go

开启Client: go run main.go

浏览器访问

http://127.0.0.1:8080/testSaveUser
{
"id": 1
}
http://127.0.0.1:8080/testUserInfo
{
"id": 1,
"name": "test",
"age": 31
}

期待一起交流

分享到: