Dubbo基础专题——第三章(Dubbo整合SpringBoot分析细节点)
Posted 皮卡皮卡程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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已经完成,我这边要详细的介绍一些注意事项:
1、对于覆盖策略
dubbo官网给出了启动时候的覆盖策略:
启动时候的参数 覆盖 外部配置的参数,这个外部配置,你理解为目的是实现配置的集中式管理:比如目前有很多主流的配置中心,
这部分业界已经有很多成熟的专业配置系统如 Apollo, Nacos 等,Dubbo 所做的主要是保证能配合这些系统正常工作。
外部化配置和其他本地配置在内容和格式上并无区别,可以简单理解为
dubbo.properties
的外部化存储,配置中心更适合将一些公共配置如注册中心、元数据中心配置等抽取以便做集中管理。而外部配置参数 覆盖 代码的api设定的参数
代码的api设定参数 覆盖 本地的properties配置文件,这个是很重要的,优先级是从上到下
这个场景会经常使用到,尤其是上配置中心的时候,是非常有用的。
2、启动时检查
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说明启动的时候不检查注册中心,等注册中心服务启动的时候,再去订阅。
3、超时策略
超时策略指的是服务的消费方在引用服务的提供方时,可能因为网络的原因,或者服务的提供者执行一个方法要很长的时间,很长时间没有返回,会导致大量的线程被阻塞在调用服务方上,会出现一些性能异常。
为了防止这个问题的不断发生,我可以指定调用这个方法的超时时间,还是以上面的为例子,订单服务要调用用户服务,假设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
运行看下,结果其实是提供者方法级别的超时时间优先。
因为此时级别不一样,级别一样的时候,消费者才优先,级别不一样,那么还是生产者优先
所以我再总结下:
- 方法级别优先,接口次之,全局再次之
- 级别一样的话,消费者优先,提供者次之
- 级别不一样,提供者优先,消费者次之
感兴趣下去可以验证下!!
4、重试次数
当我们在开发中某个服务由于各种原因,比如网络不佳,或者运行缓慢,导致了超时,消费者远程调用方法失败,我们可以通过调整重试次数,让消费者多试几次
这个重试次数,不包含第一次,比如我基于上述的代码,在消费者这边写了个重试次数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!!!
5、多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
什么意思呢,就是你某个接口实现修改了,也就是某些业务修改了,那么生怕上线的时候,万一这程序员写的bug太多,影响了整体的功能,这就很操蛋,于是我们可以让用户使用一部分先上线的版本功能,老的不变,具体来说
可以按照以下的步骤进行版本迁移:
- 在低压力时间段,先升级一半的提供者为新版本,让一半的新版本先试用看看有没有问题
- 然后过段时间,再将所有消费者升级为新版本,开始全部调用新的实现类,新的功能
- 最后将剩下的一半提供者升级为新版本
这样就有效的预防级联的问题。
现在我把所有的服务都停掉,我多写一个UserServiceImpl2,其实和UserServiceImpl1一样,只是输出不一样
提供方端口恢复成20880,并且暴露服务添加版本号
这时候消费者指定版本号为1.0.0
然后启动提供者和消费者后,日志打印
说明此时消费者调用的是老版本。同样你version设定新版本,那么就会调用新版本的接口。
其实这就是灰度发布!!!
6、本地存根
用官网的图:因为接口的实现都在服务端,有时候不一定所有的代码都一定要通过远程调用去执行,有时候也希望本地可以先做点事情,再选择去调不调用远程的服务,比如我要参数验证通过了,再去调用远程服务。于是本地存根就有用武之地了。
我们要在UserService接口写个消费端的实现类叫做UserServiceStub,实现了UserService,与此同时,Dubbo会在消费端会生产一个代理的实例对象,这个代理对象我们是看不到的(基于动态代理创建的),假设叫做UserServiceProxy,把这个代理对象,通
过UserServiceStub的构造方法传入进来,进行你的参数校验等等操作,验证是否成功了后,你再选择去不去调用远程的服务UserServiceImpl。
‘
所以我们在消费端写个类,比如我要先本地判断下参数是否合法!
然后写完后记得要配置消费端,先走下本地存根。
然后启动消费者看看本地存根有没有被调用成功!很明显已经调用了。
那么本节就整理了通过xml的方式,进行Dubbo的很多基础点的配置,下一节我们继续分享Dubbo是如何暴露本地服务以及高可用的过程的!!!敬请期待。。
以上是关于Dubbo基础专题——第三章(Dubbo整合SpringBoot分析细节点)的主要内容,如果未能解决你的问题,请参考以下文章