Apache Dubbo
Apache Dubbo 的学习
本篇博客主要记录本人学习Apache Dubbo的一些知识点。
Dubbo 简介
Apache Dubbo是一款高性能的Java RPC框架。
RPC全称为remote procedure call,即远程过程调用。
例子:两台服务器A和B,分别部署一个服务。这时如果服务器A想要调用服务器B的服务就需要通过网络表达调用的语义和传达调用的数据。
而RPC并不是一个具体的技术,是指整个网络远程调用过程。
Dubbo则提供三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbo的架构
架构图:
各节点说明
Provider:暴露服务的服务提供方
Container:服务运行容器
Registry: 服务注册与发现的注册中心
Monitor: 统计服务的调用次数和调用时间的监控中心
Consumer: 调用远程服务的服务消费方
其中虚线是异步访问,实线是同步访问
红线是程序运行时执行的功能,而蓝线是dubbo启动时完成的功能
调用的顺序:
0. 服务运行容器(Container)启动,加载,运行服务提供方(Provider)
1.服务提供方(Provider)启动时,向注册中心(Registry)注册自己提供的服务
2.服务消费方(Consumer)启动时,向注册中心(Registry)订阅自己需要的服务
3.注册中心(Registry)返回服务提供方的地址列表给消费方,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4.服务消费者(Consumer),从提供者地址列表中,基于软负载均衡算法(默认为Random),选一台提供者进行调用。
5.服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
本次学习将使用zookeeper作为dubbo的服务中心(zookeeper安装在虚拟机上)
dubbo使用的示例:
服务方的编写:
pom.xml需要导入的dubbo部分:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.7</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
web.xml配置文件:这里配置ContextLoaderListener用于读取applicationContext*.xml的配置文件
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
创建服务接口
public interface HelloService {
public String sayHello(String name);
}
创建服务实现类:这里需要注意要使用dubbo提供@service注解,才可以将服务发布到zookeeper上。
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "8083:hello : " + name;
}
}
applicationContext-service.xml的配置
<!--当前引用名称:用于注册中心计算应用间的关系,注意:生产者和消费者名称要不一样-->
<dubbo:application name="dubbodemo_provider"></dubbo:application>
<!--连接服务注册中心zookeeper的地址-->
<dubbo:registry address="zookeeper://192.168.235.133:2181"></dubbo:registry>
<!--服务注册的协议和端口:默认端口20880-->
<dubbo:protocol name="dubbo" port="20883"></dubbo:protocol>
<!--扫描指定的包:加入@service注解的类会被发布成服务-->
<dubbo:annotation package="com.jc.service.impl"></dubbo:annotation>
服务发布的效果截图:
消费方的编写:
前面的pom.xml的配置基本与服务方一致,除了pom.xml中需要更改不同于服务方的端口号。
ps:这里的服务接口是从服务方直接复制的,正常情况下,应当将服务接口整合到一个项目中,再使用maven的坐标引入服务接口。
编写Controller:这里使用dubbo提供的@Reference注解实现注入
@RestController
@RequestMapping("/demo")
public class HelloController {
@Reference
private HelloService helloService;
@RequestMapping("/hello")
public String sayHello(String name) {
//远程调用
String res = helloService.sayHello(name);
System.out.println(res);
return res;
}
}
applicationContext-web.xml的配置:
<!--当前引用名称:用于注册中心计算应用间的关系,注意:生产者和消费者名称要不一样-->
<dubbo:application name="dubbodemo_consumer"></dubbo:application>
<!--连接服务注册中心zookeeper的地址-->
<dubbo:registry address="zookeeper://192.168.235.133:2181"></dubbo:registry>
<!--扫描指定的包:加入@service注解的类会被发布成服务-->
<dubbo:annotation package="com.jc.controller"></dubbo:annotation>
随后就可以启动服务提供方和消费方,进行远程调用。
ps:注意zookeeper的虚拟机的防火墙需要关闭,不然会调用失败
远程调用效果截图:
Dubbo xml配置的说明:
<dubbo:annotation package="com.jc.controller"></dubbo:annotation>
包扫描:消费方和服务方都需要配置,用于扫描指定包下的类。
如果不适用包扫描和注解的方式进行发布服务的话,可以使用以下配置:
服务方的配置:
<bean id="helloService" class="com.jc.service.impl.HelloServiceImpl" />
<dubbo:service interface="com.lxs.jc.HelloService" ref="helloService" />
消费方的配置:
<dubbo:reference id="helloService" interface="com.lxs.api.HelloService" />
但如果用这种配置方法,每有一个服务就要再配置一遍,是相当麻烦的。所以还是注解的方式更方便。
在前面使用dubbo时,遇到了一个问题:使用dubbo的@service注解,导致spring出现找不到相应的bean的报错。
当时,想到的解决方法就是使用spring的@service注解取代dubbo的@service注解,再添加上面的service和reference的xml配置。问题的确可以通过这个方式解决,但是十分不方便。
最后,我重新开个项目再次编写时,就没有再出现这个问题。前一个项目的报错估计是因为我编写完配置文件后,将服务类所在包移动了多次,导致找不到相应的服务类而出现找不到相应的bean的问题。
dubbo 协议:
此处可以指定不同的协议和端口号。
dubbo协议适合小数据量大并发的服务调用,以及当服务消费机器数大于服务提供机数的情况。
ps:可以在同一工程中配置多个协议,不同的服务使用不同的协议
<dubbo:protocol name="dubbo" port="20883"></dubbo:protocol>
负载均衡:dubbo提供多个负载均衡策略,默认为random
Dubbo无法发布被事务代理的service问题:
如果在服务提供方加上@Transaction事务注解后,服务就会发布失败。
原因:因为spring是基于JDK动态代理方式创建代理对象,而此代理对象的完整类名为com.sun.proxy.$Proxy42(最后两位数字不是固定的),我们在配置中进行包扫描不是com.sun.proxy,而是:com.jc.service.impl。所以就导致Dubbo在发布服务前进行包匹配时无法完成匹配,进而没有进行服务的发布。
<dubbo:annotation package="com.jc.service.impl"></dubbo:annotation>
解决方法:使用cglib代理方法
applicationContext-service.xml配置使用cglib代理方式:
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
这里还需要在xml文件前面添加以下内容,不然就会出现无法识别tx:annotation-driven的错误。
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
修改服务类:在Service注解中加入interfaceClass属性,值为HelloService.class,作用是指定服务的接口类型
@Service(interfaceClass = HelloService.class)
@Transactional
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "8083:hello : " + name;
}
}
这个时候,服务就可以发布到zookeeper上了。
在解决Dubbo无法发布被事务代理的service问题时遇到的问题:
在配置好后,通过消费方调用服务提供方的服务时出现服务调用超时的问题。
随后进行了一系列的问题排查:
- 确保了zookeeper的虚拟机已关闭防火墙,服务提供方已成功发布服务
- 在服务提供方的@service中添加timeout的参数,设为5000ms,仍然会出现服务调用超时的问题
- 最后,我查看主机的mysql服务是否开启,发现也是开启的。我试着重启mysql服务,发现是我自己在applicationContext-service.xml中把数据源的密码配置错误,从而导致连接数据库失败,并且使得调用服务超时。