趣谈网络协议---二进制类RPC协议:还是叫NBA吧,总说全称多费劲

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/u012319493/article/details/82933135

接入层,对于静态资源或动态资源静态化的部分可以做缓存,但对于下单、支付等交易场景,还是需要调用 API。

对于微服务架构,API 需要一个 API 网关统一的处理。API 网关实现由多种方式,Nginx 或 OpenResty 结合 Lua 脚本是常用的方式,也可以用 Spring Cloud 中的组件 Zuul。

数据中心内部是如何相互调用的?

API 网关用来管理 API,而 API 的实现一般在 Controller 层,基本上都是基于 RESTful 的,面向大规模的互联网应用。
在这里插入图片描述

  • Controller 为互联网应用的业务逻辑实现,业务逻辑的实现应该是无状态的,从而可以横向扩展。所以,资源状态的维护在最底层的持久化层,一般会是分布式数据库和 ElasticSearch。

  • 因为硬盘读写性能差,持久化层往往吞吐量较小,因此前面需要由一层缓存层,Redis 或 memcached。

  • 缓存和持久化层之上一般是基础服务层,提供一些原子化的接口,如对用户、商品、订单、库存的增删改查。有了这层,上层业务逻辑看到的都是接口,而不会调用数据库和缓存,有利于对缓存和数据库的运维。

  • 再往上是组合层,实现负责的业务逻辑,如下单、扣优惠券等。

Controller 层、组合服务层、基础服务层会相互调用,这个调用在数据中心内部,使用 RPC 机制实现。

由于服务较多,需要一个单独的注册中心来做服务发现。服务提供方会将自己提供的服务注册到注册中心,服务消费订阅该服务,从而对该服务调用。

RPC 调用,如果使用文本类,则相对于二进制,同样的信息占用更多空间,传输起来更加占带宽,时延也高。因而,多数公司更愿意使用二进制方案。

Dubbo 服务框架二进制的 RPC 方式
在这里插入图片描述

  • Dubbo 在客户端的本地启动一个 Proxy,即客户端 Stub。

  • Dubbo 从注册中心获取服务端的列表,根据路由规则和负责均衡规则,从多个服务端中选择一个进行调用。

  • 调用服务端时,进行编码和序列化,形成 Dubbo 头和序列化的方法和参数,交给网络客户端发送。

  • 网络服务端收到消息,解码,将任务发给某个线程处理,线程调用服务端代码逻辑,然后返回结果。

如何解决协议约定问题?

Dubbo 中默认的 RPC 协议是 Hessian2。Hessian2 将远程调用序列化为二进制进行传输,并且可以进行一定的压缩。

Hessian2 与原先的二进制的 RPC 的区别:

1、综合了 XML 和二进制共同的优势。

原先要定义一个协议文件,然后通过该文件生成客户端和服务端 Stub,才能相互调用。Hessian2 不需要定义协议文件,而是自描述的。关于调用哪个函数,参数是什么,另一方不需要拿到某个协议文件,而是拿到二进制后,靠 Hessian2 的规则解析。

Hessian2 的序列化语法描述文件:
在这里插入图片描述
从 Top 起,下一层是 value,直到形成一棵树。为防止歧义,每个类型的起始数字都是唯一的。

例如,“add(2, 3)” 被序列化后:

H x02 x00     # H 开头,表示使用的协议是 Hessian
C          # C 开头,表示是一个 RPC 调用
 x03 add     # 0x03,表示方法名是 3 个字符
 x92        # 0x92,表示有 2 个参数,加上 0x90 是为了防止歧义,表示这一定是个 int
 x92        # 第一个参数是 2
 x93        # 第二个参数是 3

2、Hessian2 是面向对象的,可传输一个对象。

class Car {
 String color;
 String model;
}
out.writeObject(new Car("red", "corvette"));
out.writeObject(new Car("green", "civic"));
---
C            # 定义类,定义在位置 0
 x0b example.Car    # 类名为 example.Car,11 个字符
 x92          # 2 个成员变量
 x05 color       # color 成员变量,5 个字符
 x05 model       # model 成员变量,5 个字符

O            # 传输的对象引用这个类
 x90          #  因为类定义在位置 0,所以对象会指向这个位置 0
 x03 red        # color 的值为 red,3 个字符
 x08 corvette      # model 的值为 corvette,8 个字符

x60           # 传输一个属于相同类的对象,不保存对类的引用,只保存 0x60,表示同上
 x05 green       # color 的值为 green,5 个字符
 x05 civic       # model 的值为 civic,5 个字符

如何解决 RPC 传输问题?

Dubbo 中,使用了 Netty 的网络传输框架。

Netty 是一个非阻塞的基于事件的网络传输框架,在服务端启动时,会监听一个端口,并注册以下事件。

  • 连接事件:收到客户端的连接事件时,调用 void connected(Channel channel) 方法。
  • 可写事件触发时,调用 void sent(Channel channel, Object message),服务端向客户端返回响应数据。
  • 可读事件触发时,调用 void received(Channel channel, Object message),服务端接收客户端的请求数据。
  • 发生异常时,调用 void caught(Channel channel, Throwable exception)。

事件触发后,服务端可直接在函数中操作,也可将请求分发到线程池处理。

上面的架构中,如果使用二进制的方式进行序列化,虽然不用协议文件生成 Stub,但对于接口的定义,及传递的对象 DTO,还是需要共享 JAR。客户端和服务端都有 JAR,才能成功地序列化和反序列化。

关系复杂时,JAR 的依赖也会复杂,在 DTO 中增加一个字段,双方 JAR 没有匹配好,也会导致序列化失败,还可能循环依赖,这时,一般有两种选择:

1、建立严格的项目管理流程。

  • 不允许循环调用,不讯息跨层调用。
  • 接口保持兼容性。
  • 升级时,先升级服务提供端,再升级服务消费端。

2、改用 RESTful 的方式。

  • 使用 Sprint Cloud,消费端和提供端不共享 JAR,各声明各的,只要能变成 JSON 即可。
  • 使用 RESTful 方式,性能会降低,所以需要通过横向扩展来地下单机的性能损耗。

猜你喜欢

转载自blog.csdn.net/u012319493/article/details/82933135