grpc入门

4 篇文章 0 订阅
2 篇文章 1 订阅

因为项目中使用grpc作为分布式框架,因此最近花点时间来学习grpc的原理,对于grpc的使用本文不进行赘述,不了解的同学可以去官网或者博客了解一下使用方法,此次只是针对自己的盲点进行梳理
标位红色的是还未确认,后续会更新或补充文档

后续会进行源码级别的学习和探讨

grpc本质

grpc本质上就是request-response模式,大致上跟一个http请求,虽然表面上很复杂,如:远程调用,序列化等,但本质上就是client通过封装参数向server端发起请求,server端返回数据,client端再解析的过程

client-server交互模式

有四种交互模式

  • Simple RPC 最常见的一种,一个reqeust,一个response
  • Server-Streaming RPC, client一次请求,server端分多次返回
  • Client-Streaming RPC client多条请求数据,server端一次返回
  • Bidirectional-Streamin RPC client多条请求,server端对应多条返回

一般情况下用最简单的Simple RPC模式就行

client to server的请求响应流程

在这里插入图片描述

流程图入上图说是,其实流程很简单,就是client将要调用的信息encode一下,将要调用的方法放在http post的header中,然后发送http请求到server端,server端docode,并且解码后调用具体的方法,再返回给client

其中编码解码,用的是protobuf

参数是放在Body中还是header中呢

protobuf编码

grpc使用protocol buffer将整个消息进行编码,编码后的整个格式如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u1LvIyUh-1619056323004)(/Users/manzhang/Documents/notes/img/image-20210331085518132.png)]

  • 编码之后,第一个字节表示是否是压缩的标志位,这个作用在下文http2中再讲
  • compressed flag之后是4个字节表示后面的消息的总共的大小,4个字节可以表示的数字最大为4GB,也就是表示grpc最大能处理4GB的数据
  • 再之后就是接口定义的message的编码

message编码的结构

接口message的编码如下所示,tag,value作为一个整体表示我们在proto定义的以序号排列的元素信息,比如int, string型,以及嵌套的message等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2o78S01q-1619056323005)(/Users/manzhang/Documents/notes/img/image-20210331085644485.png)]

tag

tag可以看做是index和wiretype的结合,index就是指在proto定义的成员的序号,wireType是指根据规则,将成员的数据类型映射到一个值

最终,tag的计算公式为:

Tag value = (field_index << 3) | wire_type, 也就是后3bit表示wire_type

value

针对不同的wireType对应的数据类型,数据的编码方式也有所差异,具体可以看https://blog.csdn.net/weikangc/article/details/51027818以及proto官网,值得一提的是,如果是string结构,由于长度不固定,那么value中刚开始一个字节表示的是string的长度

所以说,其实真正的message的结构是

Tag | Length(可选)| value的格式

protobuf高性能的原因

上面一小节说到value根据不同的类型会有不同的编码方式,正是这些编码方式,让protof编码比正常的编码更节省空间,另外其编码解码方式效率也高,protobuf性能比json和xml要高,但是json使用更广泛,并且可读性更高

空间占用小

  • 字段占用方面

    • Variant

      Variant在protobuf中用来表示数字,int32,int64等都是用variant来进行编码,在一般的变成语言中,int32占用32位,也就是4个字节,但是Variant可以动态地减少字节数,对于Variant表示的数字,每个字节的最高位表示该字节是否是该数字的最后一个字节,那么比如数字很小,小于等于一个字节,那么这个数字就可以只占用一个字节,最左边的bit用0表示,如果是多个字节,那么最后一个字节的最高位是0,其余字节的最高位是1,解析时通过移动每个字节的位数来重新计算这些字节表示的真正的大小就行

      当然,如果数字比较大,那么可能占用的字节数要比平常要大,但一般情况下,数字不会很大

      因为负数的最高位总是1,因此,protobuf使用Zigzag编码将有符号数转化为无符号数,再使用Variant

    • string, 用一个length表示value的长度

  • 排列更加紧凑,用tag|length|value形式紧密排列,而不需要像xml那样用一些负责的结构来包裹

编码解码

编码解码只需要进行简单的计算,比如位移等,而xml这种,由于其要转成文档结构模型,那么消耗的CPU计算更加复杂

http2

htt2相当于http1有了4个重要的改进

  • 采用二进制分帧进行传输,原来是文本传输,但是文本比二进制浪费空间(表面上看,所有文件最终的底层都是二进制,但是不要混淆了概念,就拿http传输中的一个数字来说,65536, 这个文本的asciii码是36 35 35 33 36,或者说用二进制表示是00000110 00000101 00000101 00000011 00000110,要占用8个字节,但是如果直接用二进制的话,直接用10000000000000000,也就是2个字节就能表示), 一个请求如果数据量太大,可能会被拆成多帧

二进制还是有点疑问,上面举的例子未必就是对的,这个数字未必就是int型?

  • 多路复用,同域名下所有通信都在单个连接上进行,只需要占用一个tcp连接,单个连接上交错的请求和响应互不干扰
  • 服务器推送,服务器可以在发送html时主动推送其它资源
  • 头部压缩,客户端和服务器端的多次请求,每次请求会发送与之前不一样的header的信息,如果一样,不再冗余的发送,在http2中用一个首部表来进行维护

wireshark抓包grpc请求

打开wireshark后,进行一次grpc请求操作,然后发现,只有tcp协议,没有http2协议

这时候需要进行如下操作:

1、设置解析proto文件,可以尽可能看到明文

2、鼠标停在一个下面内容有data的tcp中,右键data,current选中http2

在这里插入图片描述

3、然后就会看到解析除了http2

如下所示,标红的就是context中的metadata,果然出现在了header中

在这里插入图片描述

grpc中的request/response

request

grpc请求中request信息包含三个部分如上图所示,request headers, length-prefixed-message, end of stream flag

三个部分的请求是按照顺序的

1、首先,发送request headers, 初始化一个请求,内容如下:

2、length-prefixed-message,也就是上文中所说的编码的数据信息,如果一帧传不完,会分多帧进行传说

3、end of stream flag,标识request数据已经传输完毕

response

response信息入上图所示,也是分成三个部分,前两个部分类似,最后一个部分trailers,不仅标识数据传输结束,还返回了grpc status

有些场景下,比如快速失败,或者业务失败,那么只需要返回trailer就行,trailer信息中加上response headers中的一些信息,比如http status, content-type等

交互模式对应的数据流

前面提到过,client-server有多种交互模式,simple, client-server stream等,它们的request-response消息流是什么样的呢?

前三种模式基本差不多,当client端发送完,end of stream flag之后,client端会half-close connection,也就是client端无法再向server端发送数据,server端开始传输数据

只有最后一个bi-directional-stream模式不同,client端会传输多个请求,而server端也会同时返回数据,而不会等到client端完毕后才开是response

grpc整体架构

grcp整体架构如上图所示,最底层是封装了http/ssh网络层的处理逻辑,中间层是核心层,抽象了通用的方法,比如序列化,网络请求等,那么最上层可以选择不同的语言,c/c++/java等生成不同的上层接口调用方式

grpc特性

Interceptors

grpc有client端和server端的interceptor接口供开发者实现进行动态扩展

client端的interceptor接口如:

type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error

apc项目中monitor_interceptor.go中就实现了该接口,用于计算调用其他服务的接口耗时以及上报prometheus

server端的interceptor接口如:

type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

Deadline

通过调用context.WithDeadline方法或者context.WithTimeout方法可以设置超时时间,并且这个context具有传递性,比如在server端续用了这个context调用了另一个服务,那么如果这个context超时了调用该服务也会失效

根据我的理解,context就是上下文的意思,也携带一些信息,grpc获取这个context时会解析超时时间,最终进行http请求的超时设置

Cancel

grpc提供cancel功能,终止grpc调用

用法如下:

ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
这个cancel是个方法,通过调用Cancel可以取消

需要了解下context的机制,为什么调用context的cancel能够取消

错误码

grpc已经有默认的错误码,开发者可以自定用,并通过调用status.Err等方法返回给client端

Metadata

matadata可以看做是一个map,存放键值对,并通过调用metadata.NewOutgoingContext或者AppendToOutgoingContext将md放在context中,context中key为mdOutgoingKey struct,value为md,通过调用metadata.FromIncomingContext可以取出

在grpc的请求中,metadata是存放在http请求中的header中

Load Balancing

微服务中,一个服务一般会有多个实例部署,那么其他服务(或中间件)会请求到不同的实例中,这时候就需要进行负载均衡,让client端能够通过不同的算法进行请求到不同实例中,因此grpc提供了两种负载均衡模式

Load Balancer Proxy

负载均衡代理,有一个专门的负载均衡进程,所有微服务不需要自己维护其他服务地址,只需要向proxy注册自己的信息,那么由proxy来维护服务地址列表,当client向微服务发起请求时,实则是向proxy发起请求,由proxy根据自己的负责均衡算法转发请求到指定的服务实例。

这种方式下,client端不用开发和维护其他微服务地址列表,但是如果调用量很大,proxy就是瓶颈,LB发生故障,将影响整个系统

Client-Side Load Balancing

这种模式下,负载均衡算法被内置在每个微服务中,每个微服务启动时向注册中心注册自己的信息,同时监听/定时获取注册中心中其他服务的地址列表,当请求其他微服务时通过内置的负载均衡算法从内存中的服务地址列表中选取合适的地址进行请求

grpc生态

有很多grpc的插件能够满足日常的需求,虽然这些插件不属于grpc,但是属于grpc的生态,可以看做是一个个插件。比较有用的有:

grpc gateway, http/json transcoding, grpc server reflection, grpc middleware, healthcheck等,具体的功能一看名字就一目了然,具体可以自行按需查询下

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,让我来回答你的问题:Java如何调用远程gRPC服务? 首先,你需要定义一个.proto文件来描述你的服务和消息。proto文件是gRPC的核心文件之一,它定义了服务的接口和消息的格式。 接下来,你需要使用gRPC的代码生成工具来生成客户端和服务端的代码。你可以选择使用不同的编程语言,例如Java、C++或Python等。 在Java中,你需要使用gRPC的Java库来编写客户端代码。你可以使用Maven或Gradle来管理你的依赖项。在你的Java代码中,你需要实现一个Stub来调用远程服务。Stub是一个自动生成的类,它提供了一个简单的API来调用远程服务。 下面是一个简单的Java gRPC客户端的示例代码: ``` ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); HelloWorldGrpc.HelloWorldBlockingStub stub = HelloWorldGrpc.newBlockingStub(channel); HelloRequest request = HelloRequest.newBuilder().setName("World").build(); HelloResponse response = stub.sayHello(request); System.out.println(response.getMessage()); channel.shutdown(); ``` 在这个例子中,我们创建了一个ManagedChannel来连接到远程服务。然后,我们创建了一个HelloWorldBlockingStub来调用远程服务。最后,我们使用这个Stub来发送一个HelloRequest消息并接收一个HelloResponse消息。 最后,你需要启动你的gRPC服务,并在客户端代码中使用正确的地址和端口来连接到它。 希望这个简单的指南能够帮助你入门gRPC

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值