首页 > 其他分享 >gRPC重试与接口幂等性

gRPC重试与接口幂等性

时间:2024-03-18 15:56:16浏览次数:21  
标签:retry 请求 gRPC 接口 重试 grpc token 主键

目录

一、gRPC超时重试

[image-20220612005951908](http://photo.liuqingzheng.top/20220612-image-20220612005951908 .png)

1.1 客户端

package main

import (
	"context"
	"fmt"
	retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
	"go_test_learn/grpc_retry/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials/insecure"
	"time"
)

func main() {
	// 创建客户端拦截器
	interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error{
		start := time.Now()
		fmt.Println("客户端拦截器")
		err := invoker(ctx, method, req, reply, cc, opts...)
		fmt.Printf("耗时:%s\n", time.Since(start))
		return err
	}
	opt := grpc.WithUnaryInterceptor(interceptor)

	//********方式二;在此处配置******
	retryOpt:=[]retry.CallOption{
		retry.WithMax(3), //重试3次
		retry.WithPerRetryTimeout(1*time.Second), // 超过1s就要重试
		retry.WithCodes(codes.Unknown,codes.DeadlineExceeded,codes.Unavailable), // 哪些状态码重试
	}
	conn, err := grpc.Dial("127.0.0.1:50052",
		opt,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		// 客户端拦截器中加入retry--》多长时间超时?重试几次?
		// 方式一:配合下面
		//grpc.WithUnaryInterceptor(retry.UnaryClientInterceptor()),

		// 方式二:配置上面
		grpc.WithUnaryInterceptor(retry.UnaryClientInterceptor(retryOpt...)),
		)
	if err != nil {
		panic("连接服务异常")
	}
	defer conn.Close()
	client := proto.NewHelloClient(conn)
	request := proto.HelloRequest{Name: "lqz",}
	// **********方式一:在此处配置重试超时时间和重试次数和错误状态码********
	//res, err := client.Hello(context.Background(),
	//	&request,
	//	retry.WithMax(3), //重试3次
	//	retry.WithPerRetryTimeout(3*time.Second), // 超过1s就要重试
	//	retry.WithCodes(codes.Unknown,codes.DeadlineExceeded,codes.Unavailable), // 哪些状态码重试
	//)

	//*****方式二;
	res, err := client.Hello(context.Background(),
		&request,
	)
	if err != nil {
		panic("调用方法异常")
	}

	fmt.Println(res.Reply)
}

1.2 服务端

package main

import (
	"context"
	"fmt"
	"github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
	"go_test_learn/grpc_retry/proto"
	"google.golang.org/grpc"
	"net"
	"time"
)

type HelloServer struct {
}

func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	fmt.Println(request.Name)
	time.Sleep(2*time.Second)
	return &proto.HelloResponse{
		Reply: "收到客户端的数据:" + request.Name,
	}, nil
}

func main() {
	// 使用第三方拦截器,使用了grpc_auth和grpc_recovery
	myAuthFunction := func(ctx context.Context) (context.Context, error) {
		fmt.Println("走了认证")
		return ctx, nil
	}
	opt := grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
		grpc_auth.UnaryServerInterceptor(myAuthFunction),
		grpc_recovery.UnaryServerInterceptor(),
	))
	g := grpc.NewServer(opt)
	// 使用拦截器结束
	s := HelloServer{}
	proto.RegisterHelloServer(g, &s)
	lis, error := net.Listen("tcp", "0.0.0.0:50052")
	if error != nil {
		panic("启动服务异常")
	}
	g.Serve(lis)

}

1.3 proto

syntax = "proto3";
option go_package = ".;proto";


service Hello{
  rpc Hello(HelloRequest) returns(HelloResponse);
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string reply = 1;
}

二、接口幂等性

2.1 什么是幂等性

幂等性是系统服务对外一种承诺,承诺只要调用接口成功,外部多次调用对系统的影响是一致的。声明为幂等的服务会认为外部调用失败是常态,并且失败之后必然会有重试。

2.2 什么情况下需要幂等

  • 以SQL为例:
    • SELECT col1 FROM tab1 WHER col2=2,无论执行多少次都不会改变状态,是天然的幂等。
    • UPDATE tab1 SET col1=1 WHERE col2=2,无论执行成功多少次状态都是一致的,因此也是幂等操作。
    • UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,这种不是幂等的。
    • insert into user(userid,name) values(1,’a’) 如userid为唯一主键,即重复操作上面的业务,只会插入一条用户数据,具备幂等性。
    • 如userid不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性。
    • delete from user where userid=1,多次操作,结果一样,具备幂等性

2.3 如何保证幂等

(1)token机制

1、服务端提供了发送token的接口。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务前,先去获取token,服务器会把token保存到redis中。

2、然后调用业务接口请求时,把token携带过去,一般放在请求头部。

3、服务器判断token是否存在redis中,存在表示第一次请求,然后删除token,继续执行业务。

4、如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

(2)关键点 先删除token,还是后删除token

  • 后删除token:如果进行业务处理成功后,删除redis中的token失败了,这样就导致了有可能会发生重复请求,因为token没有被删除。这个问题其实是数据库和缓存redis数据不一致问题,后续会写文章进行讲解。

  • 先删除token:如果系统出现问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果,然后进行重试,但token已经删除掉了,服务端判断token不存在,认为是重复请求,就直接返回了,无法进行业务处理了。

  • 先删除token可以保证不会因为重复请求,业务数据出现问题。出现业务异常,可以让调用方配合处理一下,重新获取新的token,再次由业务调用方发起重试请求就ok了。

(3)token机制缺点

  • 业务请求每次请求,都会有额外的请求(一次获取token请求、判断token是否存在的业务)。其实真实的生产环境中,1万请求也许只会存在10个左右的请求会发生重试,为了这10个请求,我们让9990个请求都发生了额外的请求。

(4)乐观锁机制

  • 这种方法适合在更新的场景中,update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1
  • 根据version版本,也就是在操作库存前先获取当前商品的version版本号,然后操作的时候带上此version号。我们梳理下,我们第一次操作库存时,得到version为1,调用库存服务version变成了2;但返回给订单服务出现了问题,订单服务又一次发起调用库存服务,当订单服务传如的version还是1,再执行上面的sql语句时,就不会执行;因为version已经变为2了,where条件就不成立。这样就保证了不管调用几次,只会真正的处理一次。
  • 乐观锁主要使用于处理读多写少的问题

(5)唯一主键

  • 这个机制是利用了数据库的主键唯一约束的特性,解决了在insert场景时幂等问题。但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键。

  • 如果是分库分表场景下,路由规则要保证相同请求下,落地在同一个数据库和同一表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关。

(6)防重表

  • 使用订单号orderNo做为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且他们在同一个事务中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。

(7)唯一ID

  • 调用接口时,生成一个唯一id,redis将数据保存到集合中(去重),存在即处理过。

(8)唯一ID机制

  • 调用者生成一个唯一ID

标签:retry,请求,gRPC,接口,重试,grpc,token,主键
From: https://www.cnblogs.com/Mcoming/p/18080574

相关文章

  • 网络实名制接口-GO语言身份核验接口代码-身份证实名认证
    互联网时代,人工识别身份证信息的方式已不适用于当下社会的发展需求,更需要高效精准的科技程序来支持,在线身份证实名认证接口必不可少。翔云身份证实名认证接口,可助力线上平台与消费者信用相关联,建立完善的客户资源与网络用户的管理。翔云身份证实名认证接口,实时联网核验用......
  • C++实名认证接口教程-好集成的身份证实名认证接口-三要素认证
    现如今,随着实名制的实施,各行各业都将进行人员身份的核查,如家政、保洁、物流、金融、电商等,身份证实名认证接口主要是验证个人用户提交的姓名、人像和身份证号码信息,和公安数据库内对应的数据是否匹配一致,可以验证个人身份证信息的真伪。以下是C++语言调用翔云身份证实名......
  • C++实名认证接口教程-好集成的身份证实名认证接口-三要素认证
    现如今,随着实名制的实施,各行各业都将进行人员身份的核查,如家政、保洁、物流、金融、电商等,身份证实名认证接口主要是验证个人用户提交的姓名、人像和身份证号码信息,和公安数据库内对应的数据是否匹配一致,可以验证个人身份证信息的真伪。以下是C++语言调用翔云身份证实名认......
  • 除gRPC之外的另一个选择,IceRPC-支持QUIC
    作者引言自从19年开始接触到RPC,当时完全没有相关概念,接触到的都是http,tcp等,当时公司用的是zeroc出品的ice框架,对应rpc非常强大,跨平台,跨语言。可惜的国内并不是主流,主流是gRPC,万物诸途同归,最终的目地是一样的。主要上看谁简单,方便,好理解。就在去年重新出一个新的RPC框架IceRP......
  • Java基础——抽象类和接口详细解读
    文章目录前言一、抽象类1、什么是抽象类?2、抽象类的定义规范和要求2.1、抽象类不能被实例化2.2、抽象类内的属性和方法定义2.2、抽象类的修饰符要求2.4、继承类要求3、抽象类的应用3.1、实现共有特性特征和行为3.2、代码复用4、抽象类总结二、接口1、什么是接口?2、接......
  • 接口
    1接口的介绍(规则)接口体现的是对规则的声明,java中的接口更多体现的是对行为的抽象。使用时机:如果你发现一个类中所有的方法,都是抽象方法,那么这个类的唯一价值,就是在声明规则,对于这种类,通常会改写为Java中的接口。2语法和细节定义格式:publicinterface接口名{}接口......
  • 抽象类和接口的区别
    1抽象类和接口的区别1.1成员变量​抽象类:可以定义变量,也可以定义常量​ 接口:只能定义常量1.2成员方法​ 抽象类:可以是定义具体方法,也可以定义抽象方法​ 接口:只能定义抽象方法1.3构造方法​ 抽象类:有​ 接口:没有1.4应用场景1.4.1抽象类描述事务*......
  • Serializable是什么,为什么要实现Serializable接口?
    什么是Serializable接口什么是序列化?为什么要序列化对象什么情况下需要序列化?为什么要定义serialversionUID变量序列化的使用关于serialVersionUID定义实体类的时候会先定义一个BaseDomain类用来实现Serializable接口什么是Serializable接口一个对象序列化的接口,一个类......
  • InstantiationAwareBeanPostProcessor 接口实现
    BeanPostProcessor结构图1code如下:packagecom.gientech.resolveBeforeInstantiation;publicclassBeforeInstantiation{publicvoiddoSomething(){System.out.println("dosomething......");}}packagecom.gientech.resolveBefor......
  • go语言请求http接口示例 并解析json
    本例请求了天气api接口对接流程注册一个账号,对接免费实况天气接口阅读接口文档http://tianqiapi.com/index/doc?version=day请求接口解析json开发流程创建一个json.go文件需要引入的包import( "encoding/json" "fmt" "io/ioutil" "net/http")定义Wea......