Dubbo基础专题——第三章(Dubbo整合SpringBoot分析细节点)

前言:刚完成的Spring基础专题本想更新源码的,但是发现分布式非常火,所以今年我希望把我的知识可以分享给正在奋斗中的互联网开发人员,以及未来想往架构师上走的道友们我们一起进步,从一个互联网职场小白到一个沪漂湿人,一路让我知道分享是一件多么重要的事情,总之不对的地方,多多指出,我们一起徜徉代码的海洋!我这里做每个章节去说的前提,不是一定很标准的套用一些官方名词,目的是为了让大家可以理解更加清楚,如果形容的不恰当,可以留言出来,万分感激!

1、Dubbo整合SpringBoot

在上一节我们用传统的Spring案例,结合官网,简单介绍了Dubbo对应不同的服务怎么进行发布,以及注册中心Zookeeper的使用,当然我这里是为了演示效果,根据官网的极力推荐,使用的注册中心是Zookeeper,实际上Zookeeper的功能远远不止这些,而在

市面上的注册中心有很多种,比如Zookeeper,Nacos,Simple,Multicast和Redis等等,你没想过Redis可以做注册中心吧,当然市面上用的比较少,还有Netflix系的SpringCloud Eureka等等,其实这些都是为了做服务治理的第一步。

那么本章开始,带你走进SpringBoot微服务化的Dubbo案例,里面很多干货,都是在实战中会用到的,里面不会涉及到很多业务代码,微服务的本质是运维!!说三遍,运维,所以应该更注重服务之间的关系,和遇到各种问题应该具备的解决方案!在实战中

都值得一试!

准备环境:

我们设定两个服务:

  • user-service-provider
  • order-service-consumer

一个api接口层

  • api-service:这个用上一节的项目

新建一个Springboot项目,我就很简单过两个图,不会的要百度下了。

然后

下一步后

好了,我把上个项目中的代码都拷过来,创建两个服务的最终效果是这样的

两个服务的pom文件别忘记加上这个api-service,之前一定要install到本地!!

<dependency>
    <groupId>com.chenxin.dubbo</groupId>
    <artifactId>api-service</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

那么此时我们两个服务就创建好了,那么我们要改造下,既然是Springboot项目,就需要有Controller请求层,模拟一个请求去请求user-service-provider的业务方法。

新建一个OrderController,负责调用用户服务,返回用户id对应的地址。

是不是发现少了什么,对,pom文件里的依赖没进来,上节我们的依赖是纯Dubbo,是这样的

但是我们已经转成了Springboot项目了,场景驱动器会帮我们做自动装配的,所以我们只需要引入dubbo和springboot的starter就可以

注意下自己的Springboot版本,我的是2.x版本的,对照下图看看

    <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

其实你导入这个依赖你会发现一个东西,这个starter已经把上节我们需要的zk依赖,监控等依赖都带进来了。

这样是不是就可以启动了呢?当然不是,我们上一节的配置文件,怎么办,怎么整合到Springboot项目中来呢?

当然有办法,看官网:

取而代之的是用application.properties里面的key,value来替代xml的标签,这个我在讲Spring的时候经常提到

所以我们在user-service-provider中的properties文件重要这么配置,我特地做了对比,可以看看其实和配置文件没什么区别。

dubbo.application.name=user-service-provider
    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="gmall-user"  />

dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper
    <!-- 使用zookeeper注册中心暴露服务地址 -->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />


dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />

那么对于暴露的服务,我们xml中是这么写的

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.chenxin.dubbo.service.UserService" ref="userService" />

如果很多接口需要暴露的话,这个量就上来了,所以dubbo为我们提供了注解,叫做@Service

不再是Spring的注解@Service

import org.springframework.stereotype.Service;

而是

import com.alibaba.dubbo.config.annotation.Service;

这里要注意!

所以暴露的接口上需要加上@Service注解

然后我们要启动前,在主启动类上加上一个@EnableDubbo这个注解,表示开启基于注解的Dubbo

记得要本地启动zk和dubbo-admin,不然你看不到效果,本地访问localhost:7001就可以了。此时,生产者我们已经成功注册到zk上了,接下来消费者order-service也要注册上去并且去订阅生产者

 消费者也是一样,这么几步:

1、依赖引进来

    <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

2、配置properties文件,用户服务默认走8080,所以你这里要声明下端口别冲突了

server.port=8081
dubbo.application.name=order-service-consumer
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper

dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

3、引用的地方从@Autowired改成@Reference,@Service改成Dubbo的包

4、主启动类上加上注解@EnableDubbo

来测试下接口localhost:8081/initOrder/1,很明显已经调用成功了。

到这里,其实整合Springboot已经完成,我这边要详细的介绍一些注意事项:

2、对于覆盖策略

dubbo官网给出了启动时候的覆盖策略:

启动时候的参数  覆盖  外部配置的参数,这个外部配置,你理解为目的是实现配置的集中式管理:比如目前有很多主流的配置中心,

这部分业界已经有很多成熟的专业配置系统如 Apollo, Nacos 等,Dubbo 所做的主要是保证能配合这些系统正常工作。

外部化配置和其他本地配置在内容和格式上并无区别,可以简单理解为 dubbo.properties 的外部化存储,配置中心更适合将一些公共配置如注册中心、元数据中心配置等抽取以便做集中管理。

而外部配置参数  覆盖  代码的api设定的参数

代码的api设定参数 覆盖  本地的properties配置文件,这个是很重要的,优先级是从上到下

这个场景会经常使用到,尤其是上配置中心的时候,是非常有用的。

3、启动时检查

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"

比如你生产者在启动了很久一段时间,然后启动消费者,这个时候偶发的会爆出消费者找不到服务,或者生产者没有启动,直接启动消费者,也会报错,我演示下只启动order-service-consumer这个服务

为什么要说这个呢,因为目前我演示的是消费者调用生产者,是单向的,但是在实际项目中,双向调用是很正常的事情,那就涉及到循环依赖,比如我用户服务要调用订单服务的接口,而订单服务也要调用用户服务的接口

这样就产生了双向依赖关系,在任何一方启动都会先检查另一方有没有正常启动。所以都会抛出异常,这样的场景,为了不想这些异常信息不断发生影响服务的判断,我可以设定启动的时候关闭检查,设定check的值为false;

这样启动的时候就不会报错,而在调用具体的服务的时候,才会去检查是否有相关接口被暴露在注册中心上,从而再抛出异常。

还有一点,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,也就是设定check=false,启动的时候不检查,因为Spring容器都已经懒加载了,不会在创建工厂的时候初始化bean,创建bean对象;

如果你设定为true,会导致本服务临时不可用时,因为启动的时候检查,检查啥呢,Spring容器都没初始化对象出来,所以你的服务会抛出异常,拿到 null 引用;

如果 check="false",总是会返回引用,不为空,当服务恢复时,能自动连上。

所以对于某个接口来说的话,我们基于配置文件的方式,对于消费者方,在启动的时候先不检查userService是否可用,可以这么设置

<dubbo:reference id="userService" check="false" interface="com.chenxin.dubbo.service.UserService"/>

那么注解版的话,直接在@Reference注解里有个属性叫做check,置位false即可!!

如果我想全局设置统一规则,只需要在你的properties设置这个

dubbo.consumer.check=false 表示消费者在启动的时候,都不去检查生产者是否可用,防止循环依赖的影响。

此外,我们只是说了启动的时候不检查,那么如果消费者启动的时候,检查注册中心是否存在呢?消费者一定也是要订阅注册中心的,先存在注册中心,进而去检查服务是否存在,所以对于注册中心也是一样

如果不希望在服务启动时候,因为注册中心没启动而报错,我们可以设置这个属性

dubbo.registry.check=false

说明启动的时候不检查注册中心,等注册中心服务启动的时候,再去订阅。

4、超时策略

超时策略指的是服务的消费方在引用服务的提供方时,可能因为网络的原因,或者服务的提供者执行一个方法要很长的时间,很长时间没有返回,会导致大量的线程被阻塞在调用服务方上,会出现一些性能异常。

为了防止这个问题的不断发生,我可以指定调用这个方法的超时时间,还是以上面的为例子,订单服务要调用用户服务,假设UserService数据返回的时间是3s,那么就需要在调用的时候,配置上这个属性time=xxxxx,单位是毫秒

超时是有个默认值,这个缺省值是1000ms,有人说你怎么知道,还是看官网:

默认使用的是dubbo consumer的timeout,再看看dubbo consumer这个标签

很明显,的确是1000ms

所以可以试验下,我在提供方线程休眠个4s,而调用方给个3s后超时,看看会不会有异常。

很明显我在生产者线程休眠了4s后,消费者3s超时,结果报错超时,现象在生产中,会经常出现这个问题,合理选择一个好的超时时间是有效的。

但是在实际生产中,我会发现有很多的老项目用dubbo的时候,更多会采用配置文件的方式,那么可能这里设置了一个超时,那边又设置了一个方法级别的超时,或者全局超时,哪里的会被覆盖,或者是优先生效呢?

后面的例子我以配置文件的方式演示,不以Springboot的方式,原因很简单,xml你懂了,注解的方式你自然就很明确!

比如说,我在消费端设置了UserService的调用超时时间,为5s,而在提供方我设置线程休眠4s,很明显这个是调用成功的对吧。

但是dubbo是可以支持接口方法级别的超时时间:

<dubbo:reference id="userService" check="false" timeout="5000" interface="com.chenxin.dubbo.service.UserService">
    <dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method>
</dubbo:reference>

此时我设置方法getUserAddressList的超时时间为1s,那么是外层5s生效呢,还是内层方法1s的生效呢?

很明显报错了,思考下也知道,肯定是方法级别优先,接口次之,因为都已经精确到方法了,你接口生效的话,方法设了还有啥用吗,对吧。

看官网的解释:

  • 方法级别优先,接口次之,全局再次之
  • 级别一样的话,消费者优先,提供者次之

那级别一样,消费者优先是什么意思呢?

举个例子,提供者提供接口,设定超时时间是2s,而同级别的消费者消费这个接口,超时设定5s,是哪边生效?

运行看下,很明显是消费者优先生效!!!

那上面两种情况我都已经做了解释,下面我设定一种情况,你们看看是什么优先。

假设我提供者设定了方法级别的超时时间,而消费者设定了接口级别的超时时间

这是提供方暴露接口,方法级别的超时时间为2s

这是消费方,接口级别的超时,5s

运行看下,结果其实是提供者方法级别的超时时间优先。

因为此时级别不一样,级别一样的时候,消费者才优先,级别不一样,那么还是生产者优先

所以我再总结下:

  • 方法级别优先,接口次之,全局再次之
  • 级别一样的话,消费者优先,提供者次之
  • 级别不一样,提供者优先,消费者次之

感兴趣下去可以验证下!!

5、重试次数

当我们在开发中某个服务由于各种原因,比如网络不佳,或者运行缓慢,导致了超时,消费者远程调用方法失败,我们可以通过调整重试次数,让消费者多试几次

这个重试次数,不包含第一次,比如我基于上述的代码,在消费者这边写了个重试次数retries=3,那么会额外重试3次,直到成功,所以最多会试4次!!!

提供方代码是:因为提供方超时设置优先,所以这个一定会超时,我们看下重试的验证

结果发现,日志里面重试了4次!!!

在实际的生产环境中,如果你的提供方有多台服务在不同的机子上,那么会重试轮询不同的机器,我模拟下:

1、改提供方的端口20881,并且添加实现类的日志,表明模拟调用不同的服务,然后启动main函数

2、改提供方的端口20882,并且添加实现类的日志,表明模拟调用不同的服务,然后启动main函数

然后idea启动三个main函数,等于模拟了三个服务提供方,看看监控中心,已经启动三个服务。

启动消费端试试

所以一共是4次,多个服务会以轮询的方式进行重试。

先是UserServiceImpl.....1....发现超时,然后开始轮巡UserServiceImpl....2....发现也是超时,于是轮巡UserServiceImpl...3....也超时,于是最后一次又回来从UserServiceImpl....1....开始,以此下去,所以解释为什么UserServiceImpl...1....会执行2次了吧。

这里要注意下,我们设置重试次数一定要在接口幂等的情况下设置重试次数

什么是接口幂等性,意思就是方法无论执行多少次,结果都是一样的,比如查询,我带个条件查,无论查多少次,结果都是一样的,比如删除,我删除一次了,删除成功,删除第二次也是一样返回删除成功。因为第一次已经删除过了。

比如修改,修改一次,和修改多次,结果都是一样的,因为你每次都是执行同一个方法,带同样的参数。

但是非幂等性的话,就不能设置重试次数了,比如插入数据,每次插入都会产生新的效果。如果第一次超时了 ,虽然是超时,但是数据库已经拿到这个新增的请求了,那么就会去新增数据,而消费方等不及了,开始重试,又插入一条数据;

所以以后在设计分布式系统的时候,这接口的幂等性要注意。

所以对于新增,我们不想做重试,就应该设置retries=0!!!

6、多版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

什么意思呢,就是你某个接口实现修改了,也就是某些业务修改了,那么生怕上线的时候,万一这程序员写的bug太多,影响了整体的功能,这就很操蛋,于是我们可以让用户使用一部分先上线的版本功能,老的不变,具体来说

可以按照以下的步骤进行版本迁移:

  1. 在低压力时间段,先升级一半的提供者为新版本,让一半的新版本先试用看看有没有问题
  2. 然后过段时间,再将所有消费者升级为新版本,开始全部调用新的实现类,新的功能
  3. 最后将剩下的一半提供者升级为新版本

这样就有效的预防级联的问题。

现在我把所有的服务都停掉,我多写一个UserServiceImpl2,其实和UserServiceImpl1一样,只是输出不一样

提供方端口恢复成20880,并且暴露服务添加版本号

这时候消费者指定版本号为1.0.0

然后启动提供者和消费者后,日志打印

说明此时消费者调用的是老版本。同样你version设定新版本,那么就会调用新版本的接口。

其实这就是灰度发布!!!

7、本地存根

用官网的图:因为接口的实现都在服务端,有时候不一定所有的代码都一定要通过远程调用去执行,有时候也希望本地可以先做点事情,再选择去调不调用远程的服务,比如我要参数验证通过了,再去调用远程服务。于是本地存根就有用武之地了。

我们要在UserService接口写个消费端的实现类叫做UserServiceStub,实现了UserService,与此同时,Dubbo会在消费端会生产一个代理的实例对象,这个代理对象我们是看不到的(基于动态代理创建的),假设叫做UserServiceProxy,把这个代理对象,通

过UserServiceStub的构造方法传入进来,进行你的参数校验等等操作,验证是否成功了后,你再选择去不去调用远程的服务UserServiceImpl。

所以我们在消费端写个类,比如我要先本地判断下参数是否合法!

然后写完后记得要配置消费端,先走下本地存根。

然后启动消费者看看本地存根有没有被调用成功!很明显已经调用了。

那么本节就整理了通过xml的方式,进行Dubbo的很多基础点的配置,下一节我们继续分享Dubbo是如何暴露本地服务以及高可用的过程的!!!敬请期待。。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风清扬逍遥子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值