go grpc高级用法

文章目录

    • 错误处理
      • 常规用法
      • 进阶用法
      • 原理
    • 多路复用
    • 元数据
    • 负载均衡
    • 压缩数据

错误处理

gRPC 一般不在 message 中定义错误。毕竟每个 gRPC 服务本身就带一个 error 的返回值,这是用来传输错误的专用通道。gRPC 中所有的错误返回都应该是 nil 或者 由 status.Status 产生的一个error。这样error可以直接被调用方Client识别。

常规用法

当遇到一个go错误的时候,直接返回是无法被下游client识别的。

恰当的做法是
调用 status.New 方法,并传入一个适当的错误码,生成一个 status.Status 对象
调用该 status.Err 方法生成一个能被调用方识别的error,然后返回
st := status.New(codes.NotFound, “some description”)
err := st.Err()
传入的错误码是 codes.Code 类型。

此外还有更便捷的办法:使用 status.Error。它避免了手动转换的操作。

err := status.Error(codes.NotFound, "some description")

进阶用法

上面的错误有个问题,就是 code.Code 定义的错误码只有固定的几种,无法详尽地表达业务中遇到的错误场景。

gRPC 提供了在错误中补充信息的机制:status.WithDetails 方法

Client 通过将 error 重新转换位 status.Status ,就可以通过 status.Details 方法直接获取其中的内容。

status.Detials 返回的是个slice, 是interface{}的slice,然而go已经自动做了类型转换,可以通过断言直接使用。

服务端示例

  • 生成一个 status.Status 对象
  • 填充错误的补充信息
// 生成一个 status.Status 
st := status.New(codes.ResourceExhausted, "Request limit exceeded.")
// 填充错误的补充信息 WithDetails
ds, err := st.WithDetails(
    &epb.QuotaFailure{
        Violations: []*epb.QuotaFailure_Violation{{
            Subject:     fmt.Sprintf("name:%s", in.Name),
            Description: "Limit one greeting per person",
        }},
    },
)
if err != nil {
    return nil, st.Err()
}
return nil, ds.Err()

客户端的示例

  • 调用RPC错误后,解析错误信息
  • 通过断言直接获取错误详情
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
// 调用 RPC 如果遇到错误就对错误处理
if err != nil {
    // 转换错误
    s := status.Convert(err)
    // 解析错误信息
    for _, d := range s.Details() {
        // 通过断言直接使用
        switch info := d.(type) {
            case *epb.QuotaFailure:
            log.Printf("Quota failure: %s", info)
            default:
            log.Printf("Unexpected type: %s", info)
        }
    }
}

原理

这个错误是如何传递给调用方Client的呢?

是放到 metadata中的,而metadata是放到HTTP的header中的。

metadata是key:value格式的数据。错误的传递中,key是个固定值:grpc-status-details-bin。

而value,是被proto编码过的,是二进制安全的。

目前大多数语言都实现了这个机制。

多路复用

同一台服务器上的多个RPC服务的多路复用,比如同时保存一个订单的存根、一个欢迎的存根因为多个RPC服务运行在一个服务端上,所以客户端的多个存根之间是可以共享gRPC连接的
服务端代码

func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	grpcServer := grpc.NewServer() 

	// 注册进订单服务
	ordermgt_pb.RegisterOrderManagementServer(grpcServer, &orderMgtServer{}) 
	// 注册进欢迎服务
	hello_pb.RegisterGreeterServer(grpcServer, &helloServer{}) 
}

客户端代码

func main() {
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()

	// 订单服务建立实例连接
	orderManagementClient := pb.NewOrderManagementClient(conn)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	order1 := pb.Order{Id: "101", Items:[]string{"iPhone XS", "Mac Book Pro"}, Destination:"San Jose, CA", Price:2300.00}
	res, addErr := orderManagementClient.AddOrder(ctx, &order1)
  
	// 欢迎服务建立实例连接
	helloClient := hwpb.NewGreeterClient(conn)
	hwcCtx, hwcCancel := context.WithTimeout(context.Background(), time.Second)
	defer hwcCancel()
  
	helloResponse, err := helloClient.SayHello(hwcCtx, &hwpb.HelloRequest{Name: "gRPC Up and Running!"})
	fmt.Println("Greeting: ", helloResponse.Message)
}

元数据

在多个微服务的调用当中,信息交换常常是使用方法之间的参数传递的方式,但是在有些场景下,一些信息可能和 RPC 方法的业务参数没有直接的关联,所以不能作为参数的一部分,在 gRPC 中,可以使用元数据来存储这类信息。

元数据创建

// 方法1
md := metadata.Pairs(
		"1", "v1",
    "1", "v2",	// 方法1会把相同的键的字段合并,[ ]string{"v1","v2"}
		"2", "v3",
	)
// 方法2
md := metadata.New(map[string]string{"1":"v1","2":"v2"})

客户端收发

在context中设置的元数据会转换成线路层的gRPC头信息和 trailer

客户端发送这些头信息,收件方会以头信息的形式接收他们

	// 创建元数据
	md := metadata.Pairs(
		"timestamp", time.Now().Format(time.StampNano),
		"kn", "vn",
	)
	// 创建新元数据的上下文,这种方法会替换掉已有的上下文
	mdCtx := metadata.NewOutgoingContext(context.Background(), md)
	// 这种方法是将元数据附加到已有的上下文
	ctxA := metadata.AppendToOutgoingContext(mdCtx, "k1", "v1", "k1", "v2", "k2", "v3")

	// 定义头信息和 trailer,可以用来接收元数据
	var header, trailer metadata.MD

	order1 := pb.Order{Id: "101", Items: []string{"iPhone XS", "Mac Book Pro"}, Destination: "San Jose, CA", Price: 2300.00}
	res, _ := client.AddOrder(ctxA, &order1, grpc.Header(&header), grpc.Trailer(&trailer))

	log.Print("AddOrder Response -> ", res.Value)
	// 获取头信息
	head, err := res.Header()
	// 获取trailer
	trail, err := res.Trailer()

服务端收发

// 从上下文中获取元数据列表
md, metadataAvailable := metadata.FromIncomingContext(ctx)
	if !metadataAvailable {
		return nil, status.Errorf(codes.DataLoss, "UnaryEcho: failed to get metadata")
	}
// 操作元数据逻辑
	if t, ok := md["timestamp"]; ok {
		fmt.Printf("timestamp from metadata:\n")
		for i, e := range t {
			fmt.Printf("====> Metadata %d. %s\n", i, e)
		}
	}

// 创建元数据
header := metadata.New(map[string]string{"location": "San Jose", "timestamp": time.Now().Format(time.StampNano)})
// 发送头信息
grpc.SendHeader(ctx, header)
trailer := metadata.Pairs("status","ok")
// 设置trailer
grpc.SetTrailer(ctx,trailer)

负载均衡

负载均衡器代理

也就是说后端的结构对gRPC客户端是不透明的,客户端只需要知道均衡器的断点就可以了,比如NGINX代理、Envoy代理

客户端负载均衡

func main(){
  roundrobinConn, err := grpc.Dial(
		address,
		grpc.WithBalancerName("round_robin"), 	// 指定负载均衡的算法
    // 默认是"pick_first",也就是从服务器列表中第一个服务端开始尝试发送请求,成功则后续所有RPC都发往这个服务器
    // "round_robin"轮询调度算法,连接所有地址,每次向后端发送一个RPC
		grpc.WithInsecure(),
	)
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer roundrobinConn.Close()
	// 起10个RPC调度任务
	makeRPCs(roundrobinConn, 10)
}

func makeRPCs(cc *grpc.ClientConn, n int) {
	hwc := ecpb.NewEchoClient(cc)
	for i := 0; i < n; i++ {
		callUnary(hwc)
	}
}

func callUnary(c ecpb.EchoClient) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
  
}

在这里插入图片描述

压缩数据

在服务端会对已注册的压缩器自动解码,响应时自动编码
始终从客户端获取指定的压缩方法,如果没被注册就会返回Unimplemented

func main() {
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	defer conn.Close()
	client := pb.NewOrderManagementClient(conn)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5)
	defer cancel()

	order1 := pb.Order{Id: "101", Items:[]string{"iPhone XS", "Mac Book Pro"}, Destination:"San Jose, CA", Price:2300.00}
  // 通过 grpc.UseCompressor(gzip.Name) 就可以轻松压缩数据
	res, _ := client.AddOrder(ctx, &order1, grpc.UseCompressor(gzip.Name))
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/230440.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nodejs+vue+微信小程序+python+PHP的游戏测评网站设计与实现-计算机毕业设计推荐

通过软件的需求分析已经获得了系统的基本功能需求&#xff0c;根据需求&#xff0c;将游戏测评网站功能模块主要分为管理员模块。管理员添加个人中心、管理员管理、基础数据管理、公告管理、用户管理、游戏管理、游戏测评管理、游戏攻略管理、轮播图信息等操作。  随着时代的…

轻量封装WebGPU渲染系统示例<43>- PBR材质与阴影实(源码)

原理简介: 1. 基于rendering pass graph实现。 2. WGSL Shader 基于文件系统和宏机制动态组装。 当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/PBRShadowTest.ts 当前示例运行效果: 此示例基于此渲染系统实现&a…

学习记录---kubernetes动态卷使用---storageClass及驱动安装(nfs驱动)

一、简介 kubernetes中&#xff0c;在存储层面&#xff0c;我们常用到的两个资源是pv和pvc&#xff0c;其中pv是实际创建出来的一致性卷&#xff0c;我们可以通过pv将容器中的数据进行持久化保存&#xff0c;而pvc则可以理解为pod使用pv的中间控制器&#xff0c;通过pvc将pv绑…

node14升级node16之后无法启动处理

node从14升级到16之后&#xff0c;项目就无法启动了&#xff0c;研究了webpack3升级5&#xff0c;研究好几个小时都无法启动&#xff0c;最后发现&#xff0c;微微升级几个版本就可以了。webpack还是3 新版的依赖 "scripts": {"dev": "webpack-dev-se…

Child Mind Institute - Detect Sleep States(2023年第一次Kaggle拿到了银牌总结)

感谢 感谢艾兄&#xff08;大佬带队&#xff09;、rich师弟&#xff08;师弟通过这次比赛机械转码成功、耐心学习&#xff09;、张同学&#xff08;也很有耐心的在学习&#xff09;&#xff0c;感谢开源方案&#xff08;开源就是银牌&#xff09;&#xff0c;在此基础上一个月…

汽车电子智能保险丝解决方案

一、背景知识 在过去的几十年里&#xff0c;电子在汽车系统创新中发挥了关键作用。新型半导体器件具有新颖的功能&#xff0c;增强了车辆机械系统提供的功能。 虽然半导体解决方案和电子产品将继续在汽车电子产品中发挥关键作用&#xff0c;但展望未来&#xff0c;汽车创新将…

SQL Server 2017数据库window server服务器改名操作

在window服务器修改机器名重新加域后&#xff0c;需要执行下面的SQL语句修改数据库里面记录的机器名字&#xff0c;才能在修改后通过新名字连接数据库。 if serverproperty(servername) <> servername begin declare server sysname set server ser…

【hugging face】bitsandbytes中8 bit量化的理解

8 位量化使数十亿参数规模的模型能够适应更小的硬件&#xff0c;而不会降低性能。 8 位量化的工作原理如下&#xff1a; 1.从输入隐藏状态中按列提取较大值&#xff08;离群值&#xff09;。 2.对 FP16 中的离群值和 int8 中的非离群值执行矩阵乘法。 3.改变非异常值结果以将值…

一文讲解如何从 Clickhouse 迁移数据至 DolphinDB

ClickHouse 是 Yandex 公司于2016年开源的 OLAP 列式数据库管理系统&#xff0c;主要用于 WEB 流量分析。凭借面向列式存储、支持数据压缩、完备的 DBMS 功能、多核心并行处理的特点&#xff0c;ClickHouse 被广泛应用于广告流量、移动分析、网站分析等领域。 DolphinDB 是一款…

Java--1v1双向通信-控制台版

文章目录 前言客户端服务器端输出线程端End 前言 TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的网络传输协议&#xff0c;它提供了端到端的数据传输和可靠性保证。 本程序就是基于tcp协议编写而成的。 利用 TCP 协议进行通信的两个应用…

持续集成交付CICD:CentOS 7 安装 Nexus 3.63

目录 一、实验 1.CentOS 7 安装Nexus3.63 二、问题 1.安装Nexus报错 2.Nexus启动停止相关命令 一、实验 1.CentOS 7 安装Nexus3.63 &#xff08;1&#xff09;当前操作系统版本&JDK版本 cat /etc/redhat-releasejava -version&#xff08;2&#xff09;下载Nexus新…

Linux--学习记录(2)

解压命令&#xff1a; gzip命令&#xff1a; 参数&#xff1a; -k&#xff1a;待压缩的文件会保留下来&#xff0c;生成一个新的压缩文件-d&#xff1a;解压压缩文件语法&#xff1a; gzip -k pathname(待压缩的文件夹名)gzip -kd name.gz&#xff08;待解压的压缩包名&#x…

腾讯地图系列(二):微信小程序添加插件(三种方法)以及插件AppId获取

目录 第一章 前言 第二章 添加插件 2.1 微信小程序添加插件方法一&#xff08;微信公众平台添加插件&#xff09; 2.2 微信小程序添加插件方法二&#xff08;通过项目配置添加插件&#xff09; 2.3 微信小程序添加插件方法三&#xff08;微信公众平台服务市场添加插件&…

降维技术——PCA、LCA 和 SVD

一、说明 降维在数据分析和机器学习中发挥着关键作用&#xff0c;为高维数据集带来的挑战提供了战略解决方案。随着数据集规模和复杂性的增长&#xff0c;特征或维度的数量通常变得难以处理&#xff0c;导致计算需求增加、潜在的过度拟合和模型可解释性降低。降维技术通过捕获数…

Android 等待view 加载布局完成 (包括动态生成View)

前言 在实际开发中&#xff0c;有很多组件需要 根据数据&#xff0c;动态生成&#xff0c;或者 追加 / 减少 子view&#xff0c;由于View布局需要时间&#xff0c;此时想要获取父View的最新宽高值&#xff0c;要么手动测量&#xff0c;要么等待布局完成后再获取&#xff1b; …

actitivi自定义属性(二)

声明&#xff1a;此处activiti版本为6.0 此文章介绍后端自定义属性解析&#xff0c;前端添加自定义属性方法连接&#xff1a;activiti自定义属性&#xff08;一&#xff09;_ruoyi activiti自定义标题-CSDN博客 1、涉及到的类如下&#xff1a; 简介&#xff1a;DefaultXmlPar…

【数据结构】C语言实现:栈(Stack)与队列(Queue)

栈与队列 栈栈的概念及其结构栈的顺序结构&#xff08;杯子&#xff09;栈的相关接口 栈的实现顺序栈定义栈结构&#xff1a;头文件 (Stack.h)初始化 (StackInit)销毁 (StackDestroy)压栈 (StackPush)出栈(StackPop)读取栈顶元素 (StackTop)判断空栈(StackEmpty)栈元素个数(Sta…

Github入门教程之高效搜索和查看需要的项目

对咱们新入门的小白来说&#xff0c;前两天手把手注册 Github 账号的任务已经完成&#xff0c;接下来&#xff0c;学习如何高效搜索和查看自己感兴趣的内容。 下面是之前教程传送门 超详细GitHub注册和登录教程-CSDN博客 一. 搜索 可以在页面左上角「Search or jump to ...」…

C语言WFC实现绘制Lagrange插值多项式曲线的函数

前言&#xff08;引用&#xff09;&#xff1a; 拉格朗日多项式插值 插值方法有许多&#xff0c;常用的、基本的有&#xff1a;拉格朗日多项式插值、牛顿插值、分段线插值、Hermite插值和三次样条插值。这里只将一下拉格朗日多项式插值法&#xff1a; 方法应用 通缩点说&…

思维链(CoT)提出者 Jason Wei:关于大语言模型的六个直觉

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 Jason Wei 的主页&#xff1a;https://www.jasonwei.net/ Jason Wei&#xff0c;一位于 2020 年从达特茅斯学院毕业的杰出青年&#xff0c;随后加盟了…