初学grpc | 青训营

97 阅读6分钟

gRPC是一个高性能、通用的开源RPC框架,它由Google开发,基于HTTP/2协议和Protocol Buffers序列化协议,支持多种编程语言。gRPC可以让我们方便地实现跨平台、跨语言的服务调用,提高开发效率和性能。

在本文中,我将介绍如何在go中使用gRPC,包括以下几个方面:

  • 安装相关工具和包
  • 定义服务接口和消息类型
  • 生成客户端和服务端代码
  • 实现服务端逻辑
  • 实现客户端调用
  • 运行和测试

安装相关工具和包

要使用gRPC,我们需要安装以下几个工具和包:

  • protoc:Protocol Buffers编译器,用于将.proto文件转换为不同语言的代码。
  • protoc-gen-go:protoc的go插件,用于生成go语言的代码。
  • grpc-go:gRPC的go实现,提供了gRPC的核心库和API。

我们可以使用以下命令来安装这些工具和包:

# 安装protoc
# 可以从https://github.com/protocolbuffers/protobuf/releases下载对应平台的二进制文件,并放到PATH中
# 也可以使用包管理器安装,如apt-get install protobuf-compiler

# 安装protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

# 安装grpc-go
go get -u google.golang.org/grpc

定义服务接口和消息类型

要使用gRPC,我们首先需要定义我们要提供的服务接口和消息类型,这些定义都写在.proto文件中,使用Protocol Buffers语法。例如,我们想要实现一个简单的计算器服务,它可以提供加、减、乘、除四种操作,那么我们可以定义如下的.proto文件:

// calculator.proto

// 指定proto版本
syntax = "proto3";

// 指定包名
package calculator;

// 定义请求消息类型
message CalculatorRequest {
  // 第一个操作数
  int32 num1 = 1;
  // 第二个操作数
  int32 num2 = 2;
}

// 定义响应消息类型
message CalculatorResponse {
  // 计算结果
  int32 result = 1;
}

// 定义服务接口
service Calculator {
  // 加法操作,接收CalculatorRequest,返回CalculatorResponse
  rpc Add(CalculatorRequest) returns (CalculatorResponse) {}
  // 减法操作,接收CalculatorRequest,返回CalculatorResponse
  rpc Subtract(CalculatorRequest) returns (CalculatorResponse) {}
  // 乘法操作,接收CalculatorRequest,返回CalculatorResponse
  rpc Multiply(CalculatorRequest) returns (CalculatorResponse) {}
  // 除法操作,接收CalculatorRequest,返回CalculatorResponse
  rpc Divide(CalculatorRequest) returns (CalculatorResponse) {}
}

生成客户端和服务端代码

有了.proto文件后,我们就可以使用protoc编译器来生成客户端和服务端的代码。我们需要指定以下参数:

  • –go_out:指定生成go代码的输出目录。
  • –go_opt:指定生成go代码的选项,如包名等。
  • –go-grpc_out:指定生成go gRPC代码的输出目录。
  • –go-grpc_opt:指定生成go gRPC代码的选项,如包名等。
  • *.proto:指定要编译的.proto文件。

例如,我们可以使用以下命令来编译calculator.proto文件:

protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       calculator.proto

这样就会在当前目录下生成两个文件:

  • calculator.pb.go:包含了消息类型的定义和序列化方法。
  • calculator_grpc.pb.go:包含了服务接口的定义和客户端和服务端的桩代码。

实现服务端逻辑

有了服务接口的定义后,我们就可以实现服务端的逻辑。我们需要做以下几件事:

  • 创建一个gRPC服务器实例。
  • 实现服务接口中定义的方法。
  • 注册服务实例到gRPC服务器中。
  • 监听一个端口,接受客户端的连接请求。
  • 启动gRPC服务器,处理客户端的调用。

例如,我们可以实现一个calculator_server.go文件,如下:

// calculator_server.go

package main

import (
	"context"
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	pb "calculator/calculator"
)

// CalculatorServer 是Calculator服务的实现
type CalculatorServer struct {
	pb.UnimplementedCalculatorServer
}

// Add 实现了CalculatorServer接口中的Add方法
func (s *CalculatorServer) Add(ctx context.Context, in *pb.CalculatorRequest) (*pb.CalculatorResponse, error) {
	// 从请求中获取两个操作数
	num1 := in.GetNum1()
	num2 := in.GetNum2()
	// 计算加法结果
	result := num1 + num2
	// 返回响应
	return &pb.CalculatorResponse{Result: result}, nil
}

// Subtract 实现了CalculatorServer接口中的Subtract方法
func (s *CalculatorServer) Subtract(ctx context.Context, in *pb.CalculatorRequest) (*pb.CalculatorResponse, error) {
	// 从请求中获取两个操作数
	num1 := in.GetNum1()
	num2 := in.GetNum2()
	// 计算减法结果
	result := num1 - num2
	// 返回响应
	return &pb.CalculatorResponse{Result: result}, nil
}

// Multiply 实现了CalculatorServer接口中的Multiply方法
func (s *CalculatorServer) Multiply(ctx context.Context, in *pb.CalculatorRequest) (*pb.CalculatorResponse, error) {
	// 从请求中获取两个操作数
	num1 := in.GetNum1()
	num2 := in.GetNum2()
	// 计算乘法结果
	result := num1 * num2
	// 返回响应
	return &pb.CalculatorResponse{Result: result}, nil
}

// Divide 实现了CalculatorServer接口中的Divide方法
func (s *CalculatorServer) Divide(ctx context.Context, in *pb.CalculatorRequest) (*pb.CalculatorResponse, error) {
	// 从请求中获取两个操作数
	num1 := in.GetNum1()
	num2 := in.GetNum2()
	// 判断除数是否为0,如果是,返回错误
	if num2 == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "cannot divide by zero")
	}
	// 计算除法结果,这里使用整数除法,忽略余数
	result := num1 / num2
	// 返回响应
	return &pb.CalculatorResponse{Result: result}, nil
}

func main() {
	// 监听一个端口,例如50051
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	// 创建一个gRPC服务器实例
	s := grpc.NewServer()
	// 创建一个CalculatorServer实例
	calcServer := &CalculatorServer{}
	// 注册CalculatorServer到gRPC服务器中
	pb.RegisterCalculatorServer(s, calcServer)
	fmt.Println("Calculator server is running on port 50051...")
	// 启动gRPC服务器,处理客户端的调用
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}

}

实现客户端调用

有了服务端的逻辑后,我们就可以实现客户端的调用。我们需要做以下几件事:

  • 创建一个gRPC客户端连接,指定服务端的地址和端口。
  • 从连接中获取一个CalculatorClient实例。
  • 调用CalculatorClient的方法,传入请求参数,获取响应结果。
  • 处理响应结果或错误。

例如,我们可以实现一个calculator_client.go文件,如下:

// calculator_client.go

package main

import (
	"context"
	"fmt"
	"log"

	"google.golang.org/grpc"

	pb "calculator/calculator"
)

func main() {
	// 创建一个gRPC客户端连接,指定服务端的地址和端口
	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("failed to dial: %v", err)
	}
	defer conn.Close()
	// 从连接中获取一个CalculatorClient实例
	c := pb.NewCalculatorClient(conn)
	// 调用CalculatorClient的Add方法,传入两个操作数,获取响应结果
	addRes, err := c.Add(context.Background(), &pb.CalculatorRequest{Num1: 3, Num2: 5})
	if err != nil {
		log.Fatalf("failed to call Add: %v", err)
	}
	// 打印响应结果
	fmt.Printf("3 + 5 = %d\n", addRes.GetResult())
	// 调用CalculatorClient的Subtract方法,传入两个操作数,获取响应结果
	subRes, err := c.Subtract(context.Background(), &pb.CalculatorRequest{Num1: 10, Num2: 4})
	if err != nil {
		log.Fatalf("failed to call Subtract: %v", err)
	}
	// 打印响应结果
	fmt.Printf("10 - 4 = %d\n", subRes.GetResult())
	// 调用CalculatorClient的Multiply方法,传入两个操作数,获取响应结果
	mulRes, err := c.Multiply(context.Background(), &pb.CalculatorRequest{Num1: 6, Num2: 7})
	if err != nil {
		log.Fatalf("failed to call Multiply: %v", err)
	}
	// 打印响应结果
	fmt.Printf("6 * 7 = %d\n", mulRes.GetResult())
	// 调用CalculatorClient的Divide方法,传入两个操作数,获取响应结果
	divRes, err := c.Divide(context.Background(), &pb.CalculatorRequest{Num1: 20, Num2: 5})
	if err != nil {
		log.Fatalf("failed to call Divide: %v", err)
	}
	// 打印响应结果
	fmt.Printf("20 / 5 = %d\n", divRes.GetResult())
}

运行和测试

有了服务端和客户端的代码后,我们就可以运行和测试我们的gRPC服务了。我们需要做以下几件事:

  • 编译服务端和客户端的代码。
  • 启动服务端程序。
  • 启动客户端程序。
  • 观察输出结果。

例如,我们可以使用以下命令来编译和运行我们的代码:

# 编译服务端代码
go build -o calculator_server calculator_server.go

# 编译客户端代码
go build -o calculator_client calculator_client.go

# 启动服务端程序
./calculator_server

# 启动客户端程序(另开一个终端)
./calculator_client

# 观察输出结果
3 + 5 = 8
10 - 4 = 6
6 * 7 = 42
20 / 5 = 4

这样就完成了一个简单的gRPC服务在go中的使用。当然,gRPC还有很多的特性和功能,例如流式传输、拦截器、元数据、错误处理等,这些都可以在官方文档中找到更详细的介绍和示例。