主要按照Apache Dubbo主页上的demo进行编写,涉及到了具体操作和一些问题。
官方环境要求(Maven其实可选):
-
JDK: version 6 or higher
-
Maven: version 3 or higher
基于现在的使用习惯,具体实验使用idea+gradle(4.0)来构建demo
使用IntelliJ IDEA 新建项目(New Project),选择Gradle
定义自己项目的GroupId,ArtifactId,Version(GAV三元组),和maven的GAV三元组概念是一样的。
剩下的新建项目页面不再赘述,一般采用默认即可。
新建完以后等一会,IDEA会进行一些gradle初始化的操作。初始化完成后项目的目录结构为标准的gradle项目目录结构。
首先在build.gradle脚本中添加dubbo的依赖,和maven类似。
在这里贴一下最终版的项目根目录下的build.gradle。里面用到了shadowJar这个gradle的插件,会自动识别的,不需要手动安装。shadowJar是为了将开发的类连同依赖一起打包成一个Jar包(FatJar),方便部署或执行。shadowJar中的配置项是为了解决一些Bug,这个放在后面说。
group'com.miexample'
version'1.0-SNAPSHOT'
buildscript{
repositories{
jcenter()
}
dependencies{
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
}
}
allprojects{
apply plugin : 'com.github.johnrengelman.shadow'
apply plugin : 'java'
sourceCompatibility=1.8
repositories{
mavenCentral()
}
dependencies{
compile "com.alibaba:dubbo:2.6.1"
testCompile group: 'junit', name:' junit', version: '4.12'
}
shadowJar{
transform(com.github.jengelman.gradle.plugins.shadow.transformers
.AppendingTransformer){
resource='META-INF/spring.schemas'
}
transform(com.github.jengelman.gradle.plugins.shadow.transformers
.AppendingTransformer){
resource='META-INF/spring.handlers'
}
transform(com.github.jengelman.gradle.plugins.shadow.transformers
.AppendingTransformer){
resource='META-INF/spring.factories'
}
}
}
官网建议分成RPC接口,服务提供者(Provider),服务调用者(Consumer)这些不同模块,这样符合分成单个服务的开发情景,Provider和Consumer可以共用公共的接口模块。
使用IDEA 的New -> Module就可以自动帮组创建子模块,本实验过程中创建的子模块也是Gradle模块,既符合IDEA本身管理模块特点也符合Gradle的模块架构。
创建了三个模块 demo-common, demo-provider和demo-consumer。
在子模块demo-consumer的build.gradle中增加如下内容(注意主类的包名根据自己的项目目录进行更改)
dependencies{
compile project(":demo-common")
}
processResources{
exclude "**"
}
shadowJar{
from('src/main/resources'){
into("META-INF/")
}
manifest{
attributes "Main-Class":"com.miexample.dubbo.consumer.Consumer"
}
}
在子模块demo-provider的build.gradle中增加如下内容(注意主类的包名根据自己的项目目录进行更改)
dependencies{
compile project(":demo-common")
}
processResources{
exclude"**"
}
shadowJar{
from('src/main/resources'){
into("META-INF/")
}
manifest{
attributes "Main-Class":"com.miexample.dubbo.provider.Provider"
}
}
在子模块的build.gradle中定义了打包的jar的主类,并且将资源文件统一打包到META-INF目录中。
接下来就看定义的接口类和实现类吧!
demo-common模块中定义接口:
DemoService.java 核心部分(除去包名,导入等语句):
Public interface DemoService{
String sayHello(Stringname);
}
demo-provider模块中定义服务实现并定义启动类及相关配置文件:
DemoServiceImpl.java核心部分:
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
}
Provider.java核心部分:
public class Provider {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"META-INF/spring/dubbo-demo-provider.xml"});
context.start();
// press any key to exit
System.in.read();
}
}
dubbo-demo-provider.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="demo-provider"/>
<dubbo:registry address="multicast://224.5.6.7:1234"/>
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:service interface="com.miexample.dubbo.common.DemoService" ref="demoService"/>
<bean id="demoService" class="com.miexample.dubbo.provider.DemoServiceImpl"/>
</beans>
demo-consumer模块中定义服务调用者:
Consumer.java核心部分:
public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
context.start();
// obtain proxy object for remote invocation
DemoService demoService = (DemoService) context.getBean("demoService");
// execute remote invocation
String hello = demoService.sayHello("world");
// show the result
System.out.println(hello);
}
}
dubbo-demo-consumer.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="demo-consumer"/>
<dubbo:registry address="multicast://224.5.6.7:1234"/>
<dubbo:reference id="demoService" interface="com.miexample.dubbo.common.DemoService"/>
</beans>
接下来就是build了,在IDEA的右侧可以方便的使用gradle:
双击相应的task就可以执行,双击shadowJar来进行统一打包。这里出现了一个问题,因为打好的包中META-INF下的spring.schemas同时出现在dubbo依赖包和sping依赖包中,打包过程会导致文件覆盖(这也是依赖冲突的一种情况),虽然一般不会用什么问题,但是会影响xml文件中的的文件校验,xml引用的阿里巴巴地址已经失效,有可能出现校验失败的问题。为了解决这个冲突,利用了shadow插件的合并文件的功能,对应了在文章前面项目根目录下的build.gradle中的配置。
build完了就可以在每个模块的build/libs/下找到打包好的jar,可以直接运行。先运行provider的包,然后运行consumer的包。
运行过程中的问题:
运行过程中发现provider运行正常,但是consumer报错,错误日志如下:
这是一个常见错误,Consumer在找不到Provider时都会报这个错误。
经过改变网络环境和跟踪源码最后发现是由于在同一台机器上运行provider和consumer并且采用组播的registry,并且有虚拟网卡会导致此问题(这种实验方式和网络环境在写demo时会碰到)。
根本原因是源码中会判断发送给consumer的服务消息是采用单播还是组播,因为虚拟网卡对ip地址的影响导致判断逻辑出错,误认为consumer可以接收到单播,但其实consumer在收到单播消息之前provider自己就把这个消息给抢着处理了(因为provider和consumer在同一台机器上,相当于provider自发自收),导致consumer无法获取服务消息。此问题在linux系统上实验时又无法复现,因为linux和windows的接口对待是否回环是不同的,linux下的consumer可以在provider之先获取到单播消息。以下是源码判断逻辑
解决方式有多种:
1、禁用虚拟网卡。
2、在两台机器上分别部署
3、采用其它registry
4、将服务提供者的spring配置文件中改为(注意url后面的参数配置)
<dubbo:registry address="multicast://224.5.6.7:1234?unicast=false"/>
第四个解决方式在官方文档上有提到,但是经测试2.6.1和2.6.2版本的dubbo配置了以后还是有问题,经查证发现解析URL参数时会将unicast丢失,应该是个bug。
使用zookeeper当作registry也可能会遇到多IP的问题,参考https://dubbo.gitbooks.io/dubbo-user-book/content/demos/hostname-binding.html进行绑定provider的IP配置。
网友评论