go grpc简单使用

臭大佬 2022-09-20 20:14:26 1726
Go 
简介 go grpc简单使用

简介

微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题.

gRPC可以实现将大的项目拆分为多个小且独立的业务模块,也就是服务。各服务间使用高效的protobuf协议进行RPC调用,gRPC默认使用protocol buffers,这是google开源的一套成熟的结构数据序列化机制。

安装

下载grpc通用编译器

在如下地址获取编译器

https://github.com/protocolbuffers/protobuf/releases

解压出来因平台而异会是一个protoc或者protoc.exe

把下载的二进制文件路径添加到环境变量中(为了能全局访问protoc)

win下

win下载protoc-xx.x-win64.zip,把解压出来了的protoc.exe文件放到GOPATH下的bin目录中。

linux下

以centos 为例,把解压的protoc文件,添加到环境变量的文件中,步骤大致如下:下载protoc-xxx.x-linux-x86_64.zip

// 解压
unzip protoc-21.6-linux-x86_64.zip
// 移动到go目录下
whereis go
// go: /usr/local/go /usr/local/go/bin/go
mv ./bin/protoc /usr/local/go/bin/

查看版本

protoc --version

安装协议编译器插件

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

linux还需要执行如下命令,更新PATH,以便协议编译器可以找到插件

export PATH="$PATH:$(go env GOPATH)/bin"

使用

初始化

新建一个grpc目录,

go mod init grpc
//go mod tidy
go get -u -v google.golang.org/grpc

编写proto文件

在当前目录下建一个helloworld目录,然后在里面创建helloworld.proto文件

// 指定的当前proto语法的版本
syntax = "proto3";
package hello;
// 生成 go 类的包名 hello
option go_package = "./;hello";
// // 生成 java 类的包名
// option java_package = "";

// 定义request
message HelloRequest {
  string name = 1;// 1代表顺序
}
// 定义response
message HelloReply {
  string message = 1;// 1代表顺序
}
// 定义服务主体
service Greeter {
  // 定义方法
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

grpc目录执行如下命令:

win
protoc --go_out=./ --go_opt=paths=source_relative 
    --go-grpc_out=./ --go-grpc_opt=paths=source_relative  ./helloworld/helloworld.proto
linux
protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto

目录helloworld下生成了helloworld.pb.go文件和hello.pb.go文件。

protoc 介绍

  • 在gRPC跟目录下使用如下命令进行编译
  • protoc —go_out=. —go-grpc_out=. ./helloworld.proto,生成对应的.pb.go和_grpc.pb.go两个文件,前者主要是对message生成对应的结构体和方法,后者生成gRPC,主要是对service生成对应的interface和方法

  • go_out=. 指定生成的pb.go文件所在目录(如果没有该目录,需要手动提前创建),.代表当前protoc执行目录,结合.proto文件中的option go_package,其最终的生成文件目录为go_out指定目录/go_package指定目录

  • go-grpc_out针对_grpc.pb.go文件,作用同上
  • 另外官网文档里还有一个—go_opt=paths=source_relative,其含义代表生成的.pb.go文件路径不依赖于.proto文件中的option go_package配置项,直接在go_out指定的目录下生成.pb.go文件(.pb.go文件的package名还是由option go_package决定)
  • —go-grpc_opt=paths=source_relative,针对_grpc.pb.go文件,作用同上。

所以,根据.proto文件中的option go_package配置项生成文件如下(需要自己根据go_package先创建目录):

protoc --go_out=. --go_opt=paths=import \
    --go-grpc_out=. --go-grpc_opt=paths=import \
    helloworld/helloworld.proto

编写RPC的server和client

在根目录下,创建如下两个文件
server服务端代码:

// server.go
package main

import (
    "context"
    "flag"
    "fmt"
    "google.golang.org/grpc"
    pb "grpc/helloworld"
    "log"
    "net"
)

// go run server.go -port=500 可以传值
var (
    port = flag.Int("port", 50051, "The server port")
)

// 服务器用于实现 helloworld.GreeterServer。
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello 实现 helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    // 把用户传递的命令行参数解析为对应变量的值
    flag.Parse()
    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

client客户端代码:

// client.go
package main

import (
    "context"
    "flag"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    pb "grpc/helloworld"
    "log"
    "time"
)

const (
    defaultName = "world"
)

var (
    addr = flag.String("addr", "localhost:50051", "the address to connect to")
    name = flag.String("name", defaultName, "Name to greet")
)

func main() {
    flag.Parse()
    // 连接rpc服务
    conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}

先将服务端启动,然后运行客户端.

go run server.go
//2022/09/20 22:42:51 server listening at //[::]:50051
//2022/09/20 22:44:12 Received: world
go run client.go
//2022/09/20 22:44:12 Greeting: Hello world

Nginx 转发

与HTTP请求一样,gRPC请求也可以通过Nginx进行负载均衡和路由转发。

在使用Nginx转发gRPC请求时,需要使用Nginx的gRPC插件。Nginx的gRPC插件可以通过源码编译的方式进行安装,也可以使用第三方的二进制包安装。安装完成后,需要在Nginx配置文件中添加gRPC服务的转发规则。

http {
    upstream grpc_servers {
        # 配置gRPC服务的后端服务器地址和端口
        server 127.0.0.1:50051;
        server 127.0.0.1:50052;
    }

    server {
        listen 80;
        server_name grpc.example.com;

        location / {
            # 使用gRPC插件进行转发
            grpc_pass grpc://grpc_servers;
        }
    }
}

我们定义了一个名为grpc_servers的upstream块,其中配置了两个gRPC服务的后端服务器地址和端口。然后,在server块中定义了一个名为grpc.example.com的虚拟主机,并配置了一个location块,使用gRPC插件进行转发。

需要注意的是,在使用Nginx转发gRPC请求时,需要使用gRPC插件进行转发,而不是HTTP插件。此外,还需要确保gRPC服务端和Nginx服务器之间的通信是安全的,可以使用TLS协议进行加密。