go grpc简单使用
简介
微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题.
gRPC
可以实现将大的项目拆分为多个小且独立的业务模块,也就是服务。各服务间使用高效的protobuf
协议进行RPC
调用,gRPC
默认使用protocol buffers
,这是google
开源的一套成熟的结构数据序列化机制。
安装
下载grpc通用编译器
在如下地址获取编译器
解压出来因平台而异会是一个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协议进行加密。