当前位置: 首页>后端>正文

Dubbo3.0使用

1.微服务改造成Dubbo项目

改造成Dubbo项目,有几件事情要做:

  • 添加dubbo核心依赖
    dubbo-spring-boot-starter
  • 添加要使用的注册中心依赖
    dubbo-registry-zookeeper
  • 添加要使用的协议的依赖
    dubbo-rpc-dubbo
  • 配置dubbo相关的基本信息
    dubbo.application.name=provider-application
  • 配置注册中心地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
  • 配置所使用的协议
    dubbo.protocol.name=dubbo
    dubbo.protocol.port=20880
  • 改造服务
    抽象出服务接口UserService;
    要把Spring中的@Service注解替换成Dubbo中的@DubboService注解;
    加上@EnableDubbo(scanBasePackages = "com.zhouyu.service"),表示Dubbo会去扫描某个路径下的@DubboService,从而对外提供该Dubbo服务。(有dubbo-spring-boot-starter包,可以不加该注解)
  • 抽出一个common模块,包括
    User类;
    UserService公共接口;
    服务提供者和服务调用者都引入该common模块。
  • 服务提供者改造时,需要兼容原来的调用方式(服务调用者还可以使用原来的方式调用,比如RestTemplate)
    dubbo.protocols.p1.name=dubbo
    dubbo.protocols.p1.port=20880
    dubbo.protocols.p2.name=rest
    dubbo.protocols.p2.port=8082
    引入dubbo-rpc-dubbo包
    在UserServiceImpl上加注解@Path("/user")以及@Produces;
    在方法getUser上加注解
    @GET,@Path("/{uid}")以及@Produces(MediaType.APPLICATION_JSON),入参需要加上@PathParam("uid");
  • 将服务调用者改造为Dubbo方式。
    1)添加依赖:dubbo-spring-boot-starter、dubbo-registry-zookeeper、dubbo-rpc-dubbo、dubbo-rpc-rest;
    2)配置:dubbo.registry.address=zookeeper://127.0.0.1:2181
    3)引入服务@DubboReference UserService;
    4)@EnableDubbo
    5)如果还要支持REST协议,需要将@Path @Produces等注解加到接口模块上。

2.Http1.1、Dubbo和Triple协议对比

Http1.1请求为什么相对于Dubbo慢?

  • 一个HTTP请求有请求行、请求头、请求体,通过字符(ASCII码)发出去。有效数据Invocation放在请求体中,数据的有效使用率比较低。(HTTP1.x协议中,多余无用的字符太多了,比如回车符、换行符,这每一个字符都会占用一个字节,这些字节占用了网络带宽,降低了网络IO的效率)
  • Dubbo协议,相对于HTTP协议,精简了很多(Dubbo用一个bit位event就可以标识序列化方式,而Http需要在请求头里面加入一个键值对来说明,有效性差距很大)。
  • Http1.1不能通过一个Socket连接,连续发送请求,只能发送一个请求,接收到响应后,再发送下一个请求。因为HTTP没有请求ID,如果多发几个,响应回来后没法对应起来。而Dubbo有了ID,所以可以连续发送,这样性能更好。(HTTP1.x协议中,一条Socket连接,一次只能发送一个HTTP请求,因为如果连续发送两个HTTP请求,然后收到了一个响应,那怎么知道这个响应对应的是哪个请求呢,这样导致Socket连接的利用低,并发、吞吐量低。)

有了Dubbo协议,为什么还要引入Triple协议?

  • Dubbo不够通用,dubbo协议一旦涉及到跨RPC框架,比如一个Dubbo服务要调用gPRC服务,就比较麻烦了,因为发一个dubbo协议的请求给一个gPRC服务,gPRC服务只会按照gRPC的格式来解析字节流,最终肯定会解析不成功的。
  • 所以这就出现了Triple协议,Triple协议是基于HTTP2,没有性能问题,另外HTTP协议非常通用,全世界都认它,兼容起来也比较简单,而且还有很多额外的功能,比如流式调用。

大概对比一下triple、dubbo、rest这三个协议

  • triple协议基于的是HTTP2,rest协议目前基于的是HTTP1,都可以做到跨语言。
  • triple协议兼容了gPRC(Triple服务可以直接调用gRPC服务,反过来也可以),rest协议不行
  • triple协议支持流式调用,rest协议不行
  • rest协议更方便浏览器、客户端直接调用,triple协议不行(原理上支持,当得对triple协议的底层实现比较熟悉才行,得知道具体的请求头、请求体是怎么生成的)
  • dubbo协议是Dubbo3.0之前的默认协议,triple协议是Dubbo3.0之后的默认协议,优先用Triple协议
  • dubbo协议不是基于的HTTP,不够通用,triple协议底层基于HTTP所以更通用(比如跨语言、跨异构系统实现起来比较方便)
    dubbo协议不支持流式调用

Http1 vs Http2

  • Http1的缺点
    1)额外占用了很多字节,比如众多的回车符、换行符,它们都是字符,都需要一个字节;
    2)大头儿子,通常一个HTTP1的请求,都会携带各种请求头;
    3)Request-Repsonse模式,一次只能发送一个HTTP请求,接收到响应后才能发送下一个请求;(浏览器通过创建多个Socket连接来提高并发量。)
  • Http2的改进
    1)设计了帧,通过这种设计,就可以来压缩请求头了,比如如果帧的类型是HEADERS ,那就进行压缩,当然压缩算法是固定的HPACK算法,不能更换;
    2)支持Stream。每个帧里有一个流标识符,表示Stream ID,这是HTTP2的新特性,表示一个“虚拟流”,达到的效果是,我们可以在一个TCP连接中,同时维护多个Stream,每一个帧都是属于某一个Stream。极大的提高了并发。

HTTP1协议:


Dubbo3.0使用,第1张

HTTP2协议

Dubbo3.0使用,第2张
  • 帧长度,用三个字节来存一个数字,这个数字表示当前帧的实际传输的数据的大小,3个字节表示的最大数字是2的24次方(16M),所以一个帧最大为9字节+16M。
  • 帧类型,占一个字节,可以分为数据帧和控制帧
    ?数据帧又分为:HEADERS 帧和 DATA 帧,用来传输请求头、请求体的
    ?控制帧又分为:SETTINGS、PING、PRIORITY,用来进行管理的
  • 标志位,占一个字节,可以用来表示当前帧是整个请求里的最后一帧,方便服务端解析
  • 流标识符,占4个字节,在Java中也就是一个int,不过最高位保留不用,表示Stream ID,这也是HTTP2的一个重要设计
  • 实际传输的数据Payload,如果帧类型是HEADERS,那么这里存的就是请求头,如果帧类型是DATA ,那么这里存的就是请求体

在利用HTTP2发送一个请求时,首先:

  • 1)新建一个TCP连接(三次握手)
  • 2)新建一个Stream,生成一个新的StreamID,生成一个控制帧,帧里记录了前面生成出来的StreamID,通过TCP连接发送出去
  • 3)生成一个要发送的请求对应的HEADERS 帧,用来发送请求头,也是key:value的格式,先利用ascii进行编码,然后利用HPACK算法进行压缩,最终把压缩之后的字节存在帧中的Payload区域,记录好StreamID,最后通过TCP连接把这个HEADERS 帧发送出去
  • 4)最后把要发送的请求体数据按指定的压缩算法(请求中所指定的压缩算法,比如gzip)进行压缩,把压缩之后的字节生成DATA 帧,记录好StreamID,通过TCP连接把DATA 帧发送出去。

对于服务端而言:

  • 1)会不断的从TCP连接接收到某些帧
  • 2)当接收到一个控制帧时,表示客户端要和服务端新建一个Stream,在服务端记录一下StreamID,比如在Dubbo3.0的源码中会生成一个ServerStreamObserver的对象
  • 3)当接收到一个HEADERS 帧,取出StreamID,找到对应的ServerStreamObserver对象,并解压得到请求头,把请求头信息保存在ServerStreamObserver对象中
  • 4)当接收到一个DATA 帧时,取出StreamID,找到对应的ServerStreamObserver对象,根据请求头的信息看如何解压请求体,解压之后就得到了原生了请求体数据,然后按业务逻辑处理请求体
  • 5)处理完了之后,就把结果也生成HEADERS 帧和DATA 帧时发送客户端,客户端此时就变成了服务端,来处理响应结果。
  • 6)客户端接收到响应结果的HEADERS 帧,是也先解压得到响应头,记录响应体的解压方式
  • 7)然后继续接收到响应结果的DATA 帧,解压响应体,得到原生的响应体,处理响应体

3.Triple协议的流式调用(分批发送、处理)

几种调用方式:

  • UNARY,发送一次,返回一次。就是最普通的,服务端只有在接收到完请求包括的所有的HEADERS帧和DATA帧之后(通过调用onCompleted()发送最后一个DATA帧),才会处理数据,客户端也只有接收完响应包括的所有的HEADERS帧和DATA帧之后,才会处理响应结果。
  • SERVER_STREAM,发送一次请求,返回多次响应。服务端流,特殊的地方在于,服务端在接收完请求包括的所有的DATA帧之后,才会处理数据,不过在处理数据的过程中,可以多次发送响应DATA帧(第一个DATA帧发送之前会发送一个HEADERS帧),客户端每接收到一个响应DATA帧就可以直接处理该响应DATA 帧,这个模式下,客户端只能发一次数据,但能多次处理响应DATA帧。(目前有Bug,gRPC的效果是正确的,Dubbo3.0需要异步进行发送)。
  • CLIENT_STREAM / BI_STREAM,客户端可以发送多次数据,也可以接收多次响应。双端流,或者客户端流,特殊的地方在于,客户端可以控制发送多个请求DATA帧(第一个DATA帧发送之前会发送一个HEADERS帧),服务端会不断的接收到请求DATA帧并进行处理,并且及时的把处理结果作为响应DATA帧发送给客户端(第一个DATA帧发送之前会发送一个HEADERS帧),而客户端每接收到一个响应结果DATA帧也会直接处理,这种模式下,客户端和服务端都在不断的接收和发送DATA帧并进行处理,注意请求HEADER帧和响应HEADERS帧都只发了一个。
public interface UserService {

    // UNARY
    String sayHello(String name);

    // SERVER_STREAM
    default void sayHelloServerStream(String name, StreamObserver<String> response) {
    }

    // CLIENT_STREAM / BI_STREAM
    default StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
        return response;
    }

}
Dubbo3.0使用,第3张

中间Thread.sleep()后,服务调用者还是一下子收到了所有数据,这是个Bug。但是gRPC是可以实现睡眠接收到,而不是一下子接收到所有数据。

4.Dubbo3.0跨语言调用(Triple协议)

在工作中,我们用Java语言通过Dubbo提供了一个服务,另外一个应用(也就是消费者)想要使用这个服务,如果消费者应用也是用Java语言开发的,那没什么好说的,直接在消费者应用引入Dubbo和服务接口相关的依赖即可。

但是,如果消费者应用不是用Java语言写的呢,比如是通过python或者go语言实现的,那就至少需要满足两个条件才能调用Java实现的Dubbo服务:

  • Dubbo一开始是用Java语言实现的,那现在就需要一个go语言实现的Dubbo框架,也就是现在的dubbo-go,然后在go项目中引入dubbo-go,从而可以在go项目中使用dubbo,比如使用go语言去暴露和使用Dubbo服务。
  • 我们在使用Java语言开发一个Dubbo服务时,会把服务接口和相关类,单独抽象成为一个Maven项目,实际上就相当于一个单独的jar包,这个jar能被Java项目所使用,但不能被go项目所使用,所以go项目中该如何使用Java语言所定义的接口呢?直接用是不太可能的,只能通过间接的方式来解决这个问题,除开Java语言之外,那有没有其他技术也能定义接口呢?并且该技术也是Java和go都支持,这就是protobuf。

5.Triple与gRPC互通

Triple与gRPC互通之所以能够互通,是因为tri兼容了grpc,兼容的意思是,tri协议在发送请求和发送响应时,都是按照grpc的格式来发送的,比如在请求头和响应头中设置grpc能识别的信息。

6.Dubbo3.0与Spring Cloud互通

目前Dubbo3.0和Spring Cloud之间的互通,还没做到特别方便,比如我们知道SpringCloud在使用的过程中,需要程序员知道某个服务的controller访问路径,就算用openFeign也避免不了。

需要调用一个SpringCloud的微服务时,得在消费端应用中自己去确定要调用的应用名,以及具体的controller路径。

要调用Spring Cloud的服务,得用http协议,那tri协议行不行呢?原理上行,但是我们在用tri协议去调用另外一个服务时,并不能去指定controller地址,得用rest协议,底层也是http协议。


https://www.xamrdz.com/backend/3fv1937615.html

相关文章: