gRPC 中已经内置了 retry 功能,可以直接使用,不需要手动来实现,本文主要记录使用 gRPC 实现自动重试功能。

gRPC重试机制

示例代码基于 grpc-testing v0.0.1

服务端

为了模拟操作失败,在服务端增加一些处理逻辑

package main

import (
	"app/grpc-testing-server/api/echo"
	"context"
	"fmt"
	"log"
	"net"

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

type EchoServer struct {
    // 增加计数
	count int
	echo.UnimplementedEchoServer
}

func (e *EchoServer) Echo(ctx context.Context, par *echo.EchoRequest) (*echo.EchoResponse, error) {
	fmt.Println("echo")
	e.count++
    // 模拟失败
	if e.count%3 != 0 {
		return nil, status.Error(codes.Unavailable, "Unavailable")
	}
	result := &echo.EchoResponse{
		Msg: par.GetMsg(),
	}
	return result, nil
}

func main() {
	log.Println("Go")
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	echo.RegisterEchoServer(s, &EchoServer{})
	if err := s.Serve(listen); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

客户端

package main

import (
	"app/grpc-testing-client/api/echo"
	"context"
	"log"

	"google.golang.org/grpc"
)

func main() {
    // 通过 serviceConfig 配置服务 retry 
	scsource := `{
		"methodConfig": [{
		  "name": [{"service": "echo.echo","method":"Echo"}],
		  "retryPolicy": {
			  "MaxAttempts": 4,
			  "InitialBackoff": ".01s",
			  "MaxBackoff": ".1s",
			  "BackoffMultiplier": 1.0,
			  "RetryableStatusCodes": [ "UNAVAILABLE" ]
		  }
		}]}`

	conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure(), grpc.WithBlock(), grpc.WithDefaultServiceConfig(scsource))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()

	for i := 0; i < 10; i++ {
		c := echo.NewEchoClient(conn)
		ctx, cancle := context.WithCancel(context.Background())
		defer cancle()
		r, err := c.Echo(ctx, &echo.EchoRequest{Msg: "hello"})
		if err != nil {
			log.Fatalf("echo failed :%#v", err)
		}
		log.Print(r.GetMsg())
	}
}

ServiceConfig 的配置说明

name 指定下面的配置信息作用的 RPC 服务或方法

  • service : 通过服务名匹配,语法为 package.servicepackage 就是 proto 文件中指定的 packageservice 是 proto 文件中指定的 service
  • method : 匹配具体方法,值为 proto 文件中定义的 RPC 名称。

gRPC 的重试机制使用了退避算法,即失败一次,等待一定时间后再次尝试,如果还是失败,则等待更久的时间再次尝试,直到达到最大尝试次数或者最大尝试时间。重试策略配置如下:

  • MaxAttempts : 最大尝试次数
  • InitialBackoff : 默认退避时间
  • MaxBackoff : 最大退避时间
  • BackoffMultiplier : 退避时间增加倍率
  • RetryableStatusCodes : 服务端返回什么错误代码才重试

gRPC 内置错误代码

gRPC 内置的错误代码定义在 google.golang.org/grpc/codes 包中

  • OK Code = 0 成功返回

  • Canceled Code = 1 客户端取消,gRPC 框架生成此错误代码

  • Unknown Code = 2 未知的错误,场景: 一个 RPC 调用返回的错误信息是本系统不知道的错误,或者返回的错误信息不足。 以上两上场景 gRPC 框架自动生成此错误代码

  • InvalidArgument Code = 3 无效的参数,此错误代码不由 gRPC 框架生成

  • DeadlineExceeded Code = 4 RPC 调用超时,此错误代码由 gRPC框架生成

  • NotFound Code = 5 实体未找到,此错误代码不由 gRPC 框架生成

  • AlreadyExists Code = 6 创建实体已存在,此错误代码不由 gRPC 框架生成

  • PermissionDenied Code = 7 调用者没有操作权限,如果是用于认证失败,请使用 Unauthenticated ,此错误代码不由 gRPC 框架生成,但是 authentication 中间件使用了它

  • ResourceExhausted Code = 8 资源耗尽,此错误代码在 out of memory , 服务器超载,或者 RPC 消息大于配置的最大尺寸时生成。

  • FailedPrecondition Code = 9 表示操作请求因为系统不处于执行操作所需的状态而被拒绝。此错误代码不由 gRPC 框架生成

  • Aborted Code = 10 操作中止。此错误代码不由 gRPC 框架生成

  • OutOfRange Code = 11 操作尝试超过有效范围。此错误代码不由 gRPC 框架生成

  • Unimplemented Code = 12 此服务未实现或不支持/未启用。此错误代码将由 gRPC 框架生成。最常见的情况是,当服务器上缺少方法实现时。

  • Internal Code = 13 内部错误,此错误代码由 gRPC 框架生成

  • Unavailable Code = 14 服务当前不可用,此错误代码将在服务器过程或网络连接突然关闭期间由 gRPC 框架生成。

  • DataLoss Code = 15 无法恢复的数据丢失或损坏。此错误代码不由 gRPC 框架生成

  • Unauthenticated Code = 16 请求没有有效的操作身份验证凭据。