10

go-gRPC 初体验

 4 years ago
source link: https://studygolang.com/articles/26541
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

微服务想必大家都不陌生了。刚接触到 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
}

期待一起交流

emmuEnE.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK