dubbo-环境隔离
大家在平时开发和测试阶段定位一些bug的时候,需要调用本地启动的dubbo服务debug。如果与服务器、其他同事共用一个注册中心的话,就会调用到别人的服务或者自己本地的服务被别人调用到,造成一些调用失败或者其他异常。
解决如上问题有大致几种解决方案:
1、dubbo直连;
2、dubbo group 服务分组;
3、dubbo version 版本过渡;
在开发以及测试环境下,经常需要绕过注册中心,只测试指定服务的提供者,这时候可 以使用点对点直连的方式。点对点直连方式,将以服务接口为单位,忽略注册中心的提供者表。A接口配置点对点,不影响B接口从注册中心获取列表。
消费者应用,在注入的提供者api上添加 @Reference(version = "100", url = "dubbo://ip:port") 即可!
此时,如果提供者不希望本地的服务被别人调用到,设置:dubboregistryregister=false,默认值是true。该属性含义: 是否向此注册中心注册服务,如果设为false,将只订阅,不注册。
通过服务分组实现环境隔离,不用绕过注册中心,大家可以共用一个注册中心。服务注册分组,使跨组的服务不会相互影响,也无法相互调用,适用于环境隔离。
场景:服务A希望调用到本地的服务B(此时,B服务正常的调用远程服务C),而不是远程服务B。
本地服务B的配置设置如下: //应用全局配置 dubboprovidergroup=local-group //设置本地B所提供的dubbo服务均在local-group分组下如图:
//针对某个Api进行配置
也可以针对某个api单独做分组,例如:@Service(version = "100",group = "local-group")服务A:在注入的B dubbo服务的api上加 @Reference(version = "100", group = "local-group") 即可。
服务分组的几个属性解释:
1、dubboregistrygroup=local-group
该值自行定义,确保唯一即可,该属性含义:服务注册分组,跨组的服务不会相互影响,也无法相互调用,适用于环境隔离。 该配置不推荐配置,配置之后服务在dubbo admin上默认无法查看,也调用不到该服 务。不同环境,通过zookeeper做数据隔离。
2、dubboprovidergroup=local-group
该属性含义:服务分组,当一个接口有多个实现,可以用分组区分。该配置使当前服务所有的提供者都在local-group下。也可以只针对某个api做配置@Service(version ="100",group ="local-group");推荐后者!
3、dubboconsumergroup=local-group
该配置使当前服务只消费local-group分组下的提供者,建议只针对某个api进行配置即可,例如: @Reference(version ="100", group ="local-group")
当一个接口的实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。dubboproviderversion=10 //服务版本, 建议使用两位数字版本,如:10 ,通常在接口不兼容时版本号才需要升级。
dubboconsumerversion=10 //消费10版本的提供者
可全局配置,亦可配置到某个api服务上,此时优先级大于全局配置。
提供者服务示例:@Service(version ="your version")
消费者api服务示例: @Reference(version ="your version")
在dubbo服务器上,执行telnet可进入dubbo命令控制行:
点击回车,进入dubbo控制台
ls
显示服务列表。
ls -l
显示服务详细信息列表。
ls XxxService
显示服务的方法列表。
ls -l XxxService
显示服务的方法详细信息列表。
ps
显示服务端口列表。
ps -l
显示服务地址列表。
ps 20880
显示端口上的连接信息。
ps -l 20880
显示端口上的连接详细信息。
trace XxxService
跟踪1次服务任意方法的调用情况。
trace XxxService 10
跟踪10次服务任意方法的调用情况。
trace XxxService xxxMethod
跟踪1次服务方法的调用情况
trace XxxService xxxMethod 10
跟踪10次服务方法的调用情况。
我们都知道Dubbo可以与Spring进行融合,那是怎么进行融合的呢?
我先介绍一下官方文档中是如何实现与Spring融合的,然后再从底层分析一下。
Service注解暴露服务
增加应用配置信息
指定Spring扫描路径
Reference注解引用服务
增加应用配置信息
指定Spring扫描路径
调用服务
上面是整体融合Spring的案例,接下来分析 Service 注解和 Reference 注解是怎么实现的。
当用户使用注解 @DubboComponentScan 时,会激活 DubboComponentScanRegister ,同时生成 ServiceAnnotationBeanPostProcessor 和 ReferenceAnnotationBeanPostProcessor , ServiceAnnotationBeanPostProcessor 处理器实现了 BeanDefinitionRegistryPostProcessor 接口,Spring容器会在所有Bean注册之后回调 postProcessBeanDefinitionRegistry 方法。
在这个方法里先提取用户配置的扫描包名称,然后委托Spring对所有符合包名的class文件做字节码分析,然后扫描Dubbo的注解@Service作为过滤条件,将扫描的服务创建 BeanDefinitionHolder ,用于生成 ServiceBean 定义,最后注册 ServiceBean 的定义并做数据绑定和解析。
这时我们注册了 ServiceBean 的定义,但是还没有实例化。
ServicecBean 的结构如下:
InitializingBean
只包含 afterPropertiesSet() 方法,继承该接口的类,在初始化Bean的时候会执行该方法。在构造方法之后调用。
ApplicationContextAware
Spring容器会检测容器中的所有Bean,如果发现某个Bean实现了 ApplicationContextAware 接口,Spring容器会在创建该Bean之后,自动调用该Bean的 setApplicationContextAware() 方法,调用该方法时,会将容器本身作为参数传给该方法。
ApplicationListener
当Spring容器初始化之后,会发布一个ContextRefreshedEvent事件,实现ApplicationListener接口的类,会调用 onApplicationEvent() 方法。
重要的接口主要是这几个,那么执行的先后顺序是怎样的呢?
如果某个类实现了ApplicationContextAware接口,会在类初始化完成后调用setApplicationContext()方法进行操作
首先会执行 ApplicationContextAware 中的 setApplicationContextAware() 方法。
这里主要是将Spring的上下文引用保存到 SpringExtensionFactory 中,里面有个set集合,保存所有的Spring上下文。这里实现了Dubbo与Spring容器的相连,在SPI机制中利用 ExtensionLoadergetExtension 生成扩展类时,会有一个依赖注入的过程,即调用 injectExtension() 方法,它会通过反射获取类的所有方法,然后遍历以set开头的方法,得到set方法的参数类型,再通过ExtensionFactory寻找参数类型相同的扩展类实例。
如果某个类实现了InitializingBean接口,会在类初始化完成后,并在setApplicationContext()方法执行完毕后,调用afterPropertiesSet()方法进行操作
然后会调用 InitializingBean 的 afterPropertiesSet() 方法。
主要是将Dubbo中的应用信息、注册信息、协议信息等设置到变量中。最后有个方法值得注意的是 isDelay 方法当返回true时,表示无需延迟导出;返回false时,表示需要延迟导出。
最后会调用 ApplicationListene 中的 onApplicationEvent 方法。
此时 ServiceBean 开始暴露。 具体的暴露流程之前已经介绍容量。
在Dubbo中处理 ReferenceBean 是通过 ReferenceAnnotionBeanPostProcessor 处理的,该类继承了 InstantiationAwareBeanPostProcessor ,用来解析@Reference注解并完成依赖注入。
InstatiationAwareBeanPostProcessor
postProcessBeforeInstantiation 方法: 在实例化目标对象执行之前,可以自定义实例化逻辑,如返回一个代理对象。
postProcessAfterInitialization 方法:Bean实例化完成后执行的后处理操作,所有初始化逻辑、装配逻辑之前执行。
postProcessPropertyValues 方法: 完成其他定制的一些依赖注入和依赖检查等,可以增加属性和属性值修改。
新版本出现了改动,采用 AnnotationInjectedBeanPostProcessor 来处理。
AnnotationInjectedBeanPostProcessor 是 ReferenceAnnotationBeanPostProcessor 的父类,它实现InstantiationAwareBeanPostProcessorAdapter的postProcessPropertyValues方法,这个是实例化的后置处理,这个方式是在注入属性时触发,就是要在注入@Reference的接口时候,要将接口封装成动态代理的实例注入到Spring容器中
主要分为两步:
1) 获取类中标注的@Reference注解的字段和方法。
2)反射设置字段或方法对应的引用
最重要的是第二步,通过 inject 方法进行反射绑定。
里面最主要的就是对生成的ReferenceBean设置一个代理对象。
服务引用的触发时机有两个:
一种是ReferenceBean初始化的时候;另一种是ReferenceBean对应的服务被注入到其他类中时引用。
dubbo实现了分布式远程调用框架,多运行节点既能提高可靠性,又能提升负载能力。dubbo配置主要有注册中心(推荐zookeeper或redis)、提供者provider、消费者consumer,注册中心是第三方实现,所以主要配置好服务提供者和消费者就可以了。实际上服务接口和实现都是需要我们自己设计和实现的,dubbo做的事情就是将服务实现发布到注册中心,然后消费者从注册中心订阅服务接口,之后对接口的调用就由dubbo调度提供者去执行并返回结果。以下配置都有源码,见右侧“免费资源”。
提供者provider的配置:提供者是独立运行的节点,可以多实例运行,将服务注册到注册中心
必须要有application name,注册中心配置zookeeper,协议dubbo,超时6秒失败不重试,提供者加载repository和service层bean,然后发布接口service。
<dubbo:application name="ite-provider" />
<dubbo:registry address="zookeeper://127001:2181"/>
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:provider timeout="6000" retries="0"/>
<import resource="classpath:cachexml"/>
<import resource="classpath:ite-repositoryxml"/>
<import resource="classpath:ite-servicexml"/>
<import resource="classpath:ite-providerxml"/>
ite-providerxml,ref引用的bean是ite-servicexml已经定义好的接口实现,dubbo:service就是把接口实现发布到注册中心
<dubbo:service ref="codeListService" interface="comitecheastitedomainserviceCodeListService" />
<dubbo:service ref="idService" interface="comitecheastitedomainserviceIdService" />
<dubbo:service ref="passwordService" interface="comitecheastitedomainservicePasswordService" />
<dubbo:service ref="rolePermissionService" interface="comitecheastitedomainserviceRolePermissionService" />
provider是可以独立运行的,dubbojar里面有assembly目录,运行mvn assembly:directory就可以生成能直接运行的provider目录
assemblyxml内容,可以切换dir或targz两种格式
<assembly>
<id>assembly</id>
<formats>
<!-- <format>targz</format> -->
<format>dir</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>src/main/assembly/bin</directory>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>src/main/assembly/conf</directory>
<outputDirectory>conf</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
<fileSet>
<directory>src/test/resources</directory>
<outputDirectory>conf</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>
dubboproperties,运行startbat或startsh时,将从属性文件读取dubbo配置信息,provider节点可以多处复制并运行。
dubbocontainer=log4j,spring
dubboapplicationname=ite-provider
dubboregistryaddress=zookeeper://127001:2181
dubbomonitorprotocol=registry
dubboprotocolname=dubbo
dubboprotocolport=20880
dubbospringconfig=providerxml
dubbolog4jfile=logs/ite-providerlog
dubbolog4jlevel=WARN
消费者consumer的配置,使用dubbo:reference订阅注册中心里的服务即可,然后就可以@Autowired注入服务接口了。
<dubbo:application name="ite-consumer" />
<dubbo:registry address="zookeeper://127001:2181"/>
<dubbo:reference id="codeListService" interface="comitecheastitedomainserviceCodeListService" />
<dubbo:reference id="idService" interface="comitecheastitedomainserviceIdService" />
<dubbo:reference id="passwordService" interface="comitecheastitedomainservicePasswordService" />
<dubbo:reference id="rolePermissionService" interface="comitecheastitedomainserviceRolePermissionService" />
如果前端项目是一个消费者,就可以在webxml里直接加载consumerxml订阅服务了。
<listener>
<listener-class>orgspringframeworkwebcontextContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:consumerxml,classpath:cachexml,classpath:shiroxml,classpath:frontxml</param-value>
</context-param>
实际上本地调试开发时,可以不必启用分布式配置,只需要更改webxml即可,所有的服务都已经是配置好了的。
<listener>
<listener-class>orgspringframeworkwebcontextContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:ite-repositoryxml,classpath:ite-servicexml,classpath:cachexml,classpath:shiroxml,classpath:frontxml</param-value>
</context-param>
zookeeper的配置很简单,
wget http://toolxlongweicom/softwares/zookeeper-346targz
tar -zxvf zookeeper-346targz
cd zookeeper-346/conf
cp zoo_samplecfg zoocfg
vi zoocfg #配置zookeeper参数
单机配置(集群配置待研究)
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/home/dubbo/zookeeper-333/data
clientPort=2181
运行或停止zookeeper
sh zkServersh start | stop
dubbo的版本是253,接口调用后,provider方接收不到请求,超时后报错:Waiting server-side response timeout by scan timer详细日志如下:
通过dubbo admin查看providers和consumers,可以找到服务,双方状态正常,dubbo版本也一致。 其实如果接口的provider方异常,调用方直接会收到类似于No provider available 或Forbid consumer access service from registry use dubbo version 253, Please check registry access list (whitelist/blacklist)的错误提示。
然后,查看接口请求参数,发现其实现了java序列化接口,和错误日志中的序列化方式一致。
后来测试时发现,竟然存在部分请求成功,着重分析后发现请求对象数据存在差异,请求对象的类定义中部分自定义域没有实现序列化接口。之前调用成功的请求中,其请求参数中未实现序列化的字段值为null。后来测试当整个请求参数不实现序列化时,发现接收方也可以收到请求。
跨服务调用时,传参必须实现序列化,其内部引用的对象也必须序列化。
1方法的参数没有实现序列化,或其内部引用的对象没有实现序列化。
2方法返回值没有序列化。(根据错误日志就能看明确发现)
3调用双方形参版本不一致。(根据错误日志就能看明确发现)
4注册中心配置错误导致的异常。(根据错误日志或dubbo admin可以明确发现)
5网络异常导致的超时。
0条评论