对于微服务的架构来说,整体API需要一个API网关统一的管理,API网关有多种实现方式,使用Nginx或者OpenResty结合Lua脚本可以做到,在Spring Cloud体系中,有个组件Zuul就是干这个

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

API网关是用来管理API的,但是API的实现一般在一个叫做Controller的地方,对外提供API,让陌生人访问,业界主流的是RESTful的API,面向大规模的互联网应用

图片

整体的架构如上

在Controller之内,就是业务相关的逻辑,然后在底层,去维护了资源的状态,比如分布式数据库和ElasticSearch.

这些服务端的状态,例如订单,库存,商品等,都是重中之重,很重要,但是因为持久化到了硬盘,所以,吞吐量不大,如果是简单的读取应用,我们会走前面的缓存层,使用Reids和memchached将请求拦截一道

缓存和持久化层上面是基础服务层,里面提供一些原子化的接口,例如对于数据库 用户,商品,订单,库存的增删改查,将缓存和数据库对再上层的业务逻辑屏蔽

在往上就是组合层,因为基础服务层只是提供简单的接口,实现简单的业务逻辑,复杂的业务逻辑,但比如下单,扣优惠券等操作,在组合服务层实现

这样,这几层彼此调用,彼此使用RPC机制来实现

内部服务比较多,就意味着需要一个单独的注册中心来做服务发现,服务提供方会将自己提供的服务注册到注册中心,服务消费方订阅这个服务,从而进行调用

调用的时候需要思考,这里的RPC调用,走二进制还是文本类,文本类占用字节数目多,比如123,二进制8位就够,但是文本类,123需要3个字节,同样的消息,花费的空间大多了

因而,对于数据中心内部的互相调用,可以采用更加节省的二进制的方案

比如Dubbo的二进制RPC的调用方式

图片

整体的流程如上

Dubbo会在客户端的本地启动一个Proxy,就是客户端的Stub,对于远程的调用,会利用这个Stub进行封装

然后Dubbo,会从注册中心获取服务端,然后根据路由规则和负载均衡规则,从多个服务端中选择一个最合适的服务端来调用

调用服务端的时候,首先要对编码进行序列化,形成Dubbo头和序列化的方法和参数,将编好的数据,交给网络客户端进行发送,网络服务端收到了消息后,进行解码,然后讲给某个线程去做逻辑处理,在线程之中,会调用服务端的代码逻辑,然后返回结果

那么就是交互双方的协议约定问题

对于交互双方如何进行彼此的约定问题,Dubbo默认的RPC的协议是Hessian2,为了保证传输的效率,Hessian2将远程调用序列化为二进制进行传输,然后进行一定的压缩,这个二进制和一般的简单二进制压缩有所区别的

Hessian2解决了一些问题,比如,原本的调用过程中,交互双方需要有一个协议文件,然后通过这个文件生成客户端和服务端的Stub,进行相互调用,这样修改就会不方便,Hessian2做了一些特殊处理,进行了自描述处理

所谓自描述,就是关于调用哪个函数,参数是什么,另一方不需要拿到某个协议文件,而是可以看到二进制文件,再加上Hessian2的规则,就能解析出来

原本的协议文件,就好比两个人约定一件事,0为add,然后一方发送 012,另一方就知道是将1和2叫起来,但是如果不能有协议文件的,收到了012,也是一脸懵逼的

自描述,改变了这样,每个人说话都有着前因后果,比如说,

传递的是函数 :add 第一个参数是1 第二个参数是2,无论谁拿到这个表述,都能解析出什么意思

只不过是以二进制来编码的,就好比XML的二进制压缩版

Hessian2的语法描述文件

图片

H开头,表示使用的协议是Hession,H的二进制是0x48

C开头,表示这是一个RPC调用

0x03,表示方法名为三个字符

0x92,表示有两个参数,前面加的0x9-,是为了防歧义

第一个参数是2,编码是0x92,第二个参数是3,编码为0x93

这就是自描述

而且,Hessian2是面向对象的,可以传输一个对象

class Car {

String color;

String model;

}

out.writeObject(new Car(“red”, “corvette”));

out.writeObject(new Car(“green”, “civic”));

C            # object definition (#0)

x0b example.Car    # type is example.Car

x92          # two fields

x05 color       # color field name

x05 model       # model field name

O            # object def (long form)

x90          # object definition #0

x03 red        # color field value

x08 corvette      # model field value

x60           # object def #0 (short form)

x05 green       # color field value

x05 civic       # model field value

上面就是传输了一个类,对于类型的定义也会传过去,也是自描述的,类名为example.Car,字符长11位,前面长度为0x0b,有两个成员变量,一个是Color,一个是model,字符长5位,前面长度0x05

然后,传输的对象引用这个类,由于类的定义在位置0,对象会指向这个位置0,编码为0x90,后面red和corvette是两个成员变量的值,字符分别为3和8

接下里,传输一个属于同一个类的对象,就不再保存对类的引用了,只保存一个0x60,表示同上就可以了

这就是Hessian2的压缩能力

如何解决RPC的传输问题

Dubbo的RPC传输问题,基于Socket实现一个高性能的服务端,很复杂,在Dubbo中使用了Netty的网络框架

Netty会存在着很多的事件,诸如如下

连接事件,收到客户端的连接事件的时候,调用void connected(Channel channel)

可写事件,可写事件触发的时候,会调用服务端向客户端返回相应数据

可读事件,可读事件触发的时候,服务端会收到客户端的请求数据

发生异常,会调用void caught(Channel channel,Throwable exception)

事件触发了之后,服务端会执行对应的函数,可以选择直接在这个函数中执行,也可以选择分发到队形的线程池去实行,一般异步的数据读写都需要另外的线程池来参与,在线程池中调用真正的服务端业务代码逻辑,返回结果

Hessian2是Dubbo默认的RPC的序列化内模式,当然Dubbo除了Hessian2之外,还实现了Spark的Kryo,实现了高性能的序列化

说到底这,我们说了数据中心之间的互相调用,为了高性能,大家支持了二进制,但是,后来的Spring Cloud 的兴起,其主要原因,是因为并发量的提高,微服务更加细粒度

上面的架构,使用二进制进行序列化和反序列化,虽然不用协议文件来生成Stub,但是接口的定义,以及传得DTO对象,还是需要共享JAR,因为只有客户端和服务端都有这个JAR,才能成功序列化和反序列化

但当关系复杂的时候,JAR的复杂,难以维护,而且如果在DTO里面加一个字段,如果双方的JAR没有匹配好,导致序列化不成功,导致循环依赖,这时候

引入了RESTful

或者使用严格的项目管理流程

就这两种方式

1.不允许循环调用,不允许跨层调用,只准上层调用下层,不能下层调用下层

接口保证兼容性,不兼容的接口新添加而非改原本的,接口通过监控,发现不用的时候,再下掉

升级的时候,先升级服务提供端,再升级服务消费端

2.改为RESTful方式

使用SpringCloud,消费端和提供端不同共享这个JAR,各个调用利用JSON

但是性能比起二进制的会降低,需要通过横向扩展来抵消单机的性能消耗

本章小结

RESTful API对于接入层和Controller层之外的调用,已经形成了事实,但是内部服务之间的调用越来愈多,Dubbo的RPC框架有了用武之地

Dubbo通过注册中心解决服务发现问题,通过Hessian2序列化解决协议约定问题,通过Netty解决网络传输问题

在更加复杂的微服务场景下,Spring Cloud更加的合适,不需要JAR包的依赖

课后思考

1.对于微服务RPC框架的选择,Dubbo和Spring Cloud各有优缺点,对比呢?

2.我们讲过RPC,还没有跨语言的调用的场景,跨语言怎么办呢?

dubbo是一个rpc的框架,只包括服务发现,服务均衡负载,接口层面监控,对于RPC中的扩展点多,可以选择的传输协议多

spring cloud是一个完整的微服务的框架,包括rpc框架,整体链路监控,熔断降级,网关,配置中心,安全验证,用于http协议传输

对于跨语言的协议,首先和编程语言无关的协议,比如http.然后对不同的语言进行相对应的实现吧

2.Dubbo使用RPC通信协议

Spring Cloud使用HTTP协议REST API

Dubbo通信性能略微优于Spring Cloud

Dubbo通过接口进行互相依赖,需要严格的版本的控制

而Spring Cloud的RESTful基于json,双方定义DTO或者Map即可,使用Dubbo,会出现共享jar的问题

2.对于跨语言的调用

可以使用Thrift和Protocol Buffers

Thrift是Facebook提供的跨语言的轻量级的RPC消息和数据交换框架

Protocol Buffers是Google提供的一个开源的序列化框架,类似XML,JSON这样的数据表示语言

发表评论

邮箱地址不会被公开。 必填项已用*标注