Dubbo作为一个 RPC 框架,其最核心的功能就是要实现跨网络的远程调用。本小节就是要创建两个应 用,一个作为服务的提供方,一个作为服务的消费方。通过Dubbo 来实现服务消费方远程调用服务提供
方的方法。
4. 服务提供方开发
开发步骤:
( 1 )创建 maven 工程(打包方式为 war ) dubbodemo_provider ,在 pom.xml 文件中导入如下坐标
1.8
1.8
5.0.5.RELEASE
org.springframework
${spring.version}
org.springframework
${spring.version}
org.springframework
${spring.version}
org.springframework
${spring.version}
org.springframework
${spring.version}
org.springframework
${spring.version}
org.springframework
${spring.version}
com.alibaba
2.6.0
org.apache.zookeeper
3.4.7
com.github.sgroschupf
0.1
javassist
3.12.1.GA
com.alibaba
1.2.47
org.apache.maven.plugins
2.3.2
1.8
1.8
org.apache.tomcat.maven
( 2 )配置 web.xml 文件
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
Archetype Created Web Application
classpath:applicationContext*.xml
class> org.springframework.web.context.ContextLoaderListener
( 3 )创建服务接口
package com . lxs . service ;
public interface HelloService {
public String sayHello ( String name );
}
( 4 )创建服务实现类
package com . lxs . service . impl ;
import com . alibaba . dubbo . config . annotation . Service ;
import com . lxs . service . HelloService ;
@Service
public class HelloServiceImpl implements HelloService {
public String sayHello ( String name ) {
return "hello " + name ;
}
}
注意:服务实现类上使用的 Service 注解是 Dubbo 提供的,用于对外发布服务
( 5 )在 src/main/resources 下创建 applicationContext-service.xml
xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:p = "http://www.springframework.org/schema/p"
xmlns:context = "http://www.springframework.org/schema/context" ( 6 )启动服务
tomcat7:run
4.2 服务消费方开发
开发步骤:
( 1 )创建 maven 工程(打包方式为 war ) dubbodemo_consumer , pom.xml 配置和上面服务提供者
相同,只需要将 Tomcat 插件的端口号改为 8082 即可
( 2 )配置 web.xml 文件
( 3 )将服务提供者工程中的 HelloService 接口复制到当前工程
( 4 )编写 Controller
xmlns:dubbo = "http://code.alibabatech.com/schema/dubbo"
xmlns:mvc = "http://www.springframework.org/schema/mvc"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
name = "dubbodemo_provider" />
address = "zookeeper://192.168.134.129:2181" />
name = "dubbo" port = "20881" >
package = "com.lxs.service.impl" />
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
Archetype Created Web Application
springmvc
org.springframework.web.servlet.DispatcherServlet
class>
classpath:applicationContext-web.xml
1
springmvc
*.do
package com . lxs . controller ;
import com . alibaba . dubbo . config . annotation . Reference ;
import com . lxs . service . HelloService ;
import org . springframework . stereotype . Controller ;
import org . springframework . web . bind . annotation . RequestMapping ;
import org . springframework . web . bind . annotation . ResponseBody ;
@Controller
@RequestMapping ( "/demo" )
public class HelloController {
@Reference
private HelloService helloService ;
@RequestMapping ( "/hello" )
@ResponseBody
public String getName ( String name ){
// 远程调用
String result = helloService . sayHello ( name );
System . out . println ( result );
return result ;
}
}
注意: Controller 中注入 HelloService 使用的是 Dubbo 提供的 @Reference 注解
( 5 )在 src/main/resources 下创建 applicationContext-web.xml
xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:p = "http://www.springframework.org/schema/p"
xmlns:context = "http://www.springframework.org/schema/context"
xmlns:dubbo = "http://code.alibabatech.com/schema/dubbo"
xmlns:mvc = "http://www.springframework.org/schema/mvc"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
name = "dubbodemo-consumer" />
address = "zookeeper://192.168.134.129:2181" />
package = "com.lxs.controller" />
( 6 )运行测试
tomcat7:run 启动
在浏览器输入 http://localhost:8082/demo/hello.do?name=Jack ,查看浏览器输出结果 思考一: 上面的 Dubbo 入门案例中我们是将 HelloService 接口从服务提供者工程
(dubbodemo_provider) 复制到服务消费者工程 (dubbodemo_consumer) 中,这种做法是否合适?还有
没有更好的方式?
答: 这种做法显然是不好的,同一个接口被复制了两份,不利于后期维护。更好的方式是单独创建一个
maven 工程,将此接口创建在这个 maven 工程中。需要依赖此接口的工程只需要在自己工程的
pom.xml 文件中引入 maven 坐标即可。
思考二: 在服务消费者工程 (dubbodemo_consumer) 中只是引用了 HelloService 接口,并没有提供实现
类, Dubbo 是如何做到远程调用的?
答: Dubbo 底层是基于代理技术为 HelloService 接口创建代理对象,远程调用是通过此代理对象完成
的。可以通过开发工具的 debug 功能查看此代理对象的内部结构。另外, Dubbo 实现网络传输底层是基
于 Netty 框架完成的。
思考三: 上面的 Dubbo 入门案例中我们使用 Zookeeper 作为服务注册中心,服务提供者需要将自己的服
务信息注册到 Zookeeper ,服务消费者需要从 Zookeeper 订阅自己所需要的服务,此时 Zookeeper 服务
就变得非常重要了,那如何防止 Zookeeper 单点故障呢?
答: Zookeeper 其实是支持集群模式的,可以配置 Zookeeper 集群来达到 Zookeeper 服务的高可用,防
止出现单点故障。
5. Dubbo 管理控制台
我们在开发时,需要知道 Zookeeper 注册中心都注册了哪些服务,有哪些消费者来消费这些服务。我们
可以通过部署一个管理中心来实现。其实管理中心就是一个 web 应用,部署到 tomcat 即可。
5.1 安装
安装步骤:
( 1 )将资料中的 dubbo-admin-2.6.0.war 文件复制到 tomcat 的 webapps 目录下
( 2 )启动 tomcat ,此 war 文件会自动解压
( 3 )修改 WEB-INF 下的 dubbo.properties 文件,注意 dubbo.registry.address 对应的值需要对应当前
使用的 Zookeeper 的 ip 地址和端口号
dubbo.registry.address=zookeeper://192.168.134.129:2181 dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
( 4 )重启 tomcat
5.2 使用
操作步骤:
( 1 )访问 http://localhost:8080/dubbo-admin-2.6.0/ ,输入用户名 (root) 和密码 (root) ( 2 )启动服务提供者工程和服务消费者工程,可以在查看到对应的信息
6. Dubbo 相关配置说明
6.1 包扫描
服务提供者和服务消费者都需要配置,表示包扫描,作用是扫描指定包 ( 包括子包 ) 下的类。
package = "com.lxs.service" /> 如果不使用包扫描,也可以通过如下配置的方式来发布服务:
作为服务消费者,可以通过如下配置来引用服务:
上面这种方式发布和引用服务,一个配置项 ( dubbo:service 、 dubbo:reference ) 只能发布或者引用一个
服务,如果有多个服务,这种方式就比较繁琐了。推荐使用包扫描方式。
6.2 协议
一般在服务提供者一方配置,可以指定使用的协议名称和端口号。
其中 Dubbo 支持的协议有: dubbo 、 rmi 、 hessian 、 http 、 webservice 、 rest 、 redis 等。
推荐使用的是 dubbo 协议。
dubbo 协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机
器数远大于服务提供者机器数的情况。不适合传送大数据量的服务,比如传文件,传视频等,除非请求
量很低。
也可以在同一个工程中配置多个协议,不同服务可以使用不同的协议,例如:
6.3 负载均衡
负载均衡( Load Balance ):其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任
务。
在集群负载均衡时, Dubbo 提供了多种均衡策略(包括随机、轮询、最少活跃调用数、一致性
Hash ),缺省为 random 随机调用。
配置负载均衡策略,既可以在服务提供者一方配置,也可以在服务消费者一方配置,如下:
id = "helloService" class = "com.lxs.service.impl.HelloServiceImpl" />
interface = "com.lxs.api.HelloService" ref = "helloService" />
id = "helloService" interface = "com.lxs.api.HelloService" />
name = "dubbo" port = "20880" />
name = "dubbo" port = "20880" />
name = "rmi" port = "1099" />
interface = "com.lxs.api.HelloService" ref = "helloService"
protocol = "dubbo" />
interface = "com.lxs.api.DemoService" ref = "demoService"
protocol = "rmi" />
@Controller
@RequestMapping ( "/demo" )
public class HelloController {
// 在服务消费者一方配置负载均衡策略
@Reference ( check = false , loadbalance = "random" )
private HelloService helloService ; 可以通过启动多个服务提供者来观察 Dubbo 负载均衡效果。
注意:因为我们是在一台机器上启动多个服务提供者,所以需要修改 tomcat 的端口号和 Dubbo 服务的
端口号来防止端口冲突。
在实际生产环境中,多个服务提供者是分别部署在不同的机器上,所以不存在端口冲突问题。
7. 解决 Dubbo 无法发布被事务代理的 Service 问题
前面我们已经完成了 Dubbo 的入门案例,通过入门案例我们可以看到通过 Dubbo 提供的标签配置就可
以进行包扫描,扫描到 @Service 注解的类就可以被发布为服务。
但是我们如果在服务提供者类上加入 @Transactional 事务控制注解后,服务就发布不成功了。原因是事
务控制的底层原理是为服务提供者类创建代理对象,而默认情况下 Spring 是基于 JDK 动态代理方式创建
代理对象,而此代理对象的完整类名为 com.sun.proxy.$Proxy42 (最后两位数字不是固定的),导致
Dubbo 在发布服务前进行包匹配时无法完成匹配,进而没有进行服务的发布。
7.1 问题展示
在入门案例的服务提供者 dubbodemo_provider 工程基础上进行展示
操作步骤:
( 1 )在 pom.xml 文件中增加 maven 坐标
@RequestMapping ( "/hello" )
@ResponseBody
public String getName ( String name ){
// 远程调用
String result = helloService . sayHello ( name );
System . out . println ( result );
return result ;
}
}
// 在服务提供者一方配置负载均衡
@Service ( loadbalance = "random" )
public class HelloServiceImpl implements HelloService {
public String sayHello ( String name ) {
return "hello " + name ;
}
}
mysql
5.1.47
com.alibaba
1.1.6
org.mybatis
( 2 )在 applicationContext-service.xml 配置文件中加入数据源、事务管理器、开启事务注解的相关配
置
id = "dataSource" class = "com.alibaba.druid.pool.DruidDataSource" destroy-
method = "close" >
name = "username" value = "root" />
name = "password" value = "root" />
name = "driverClassName" value = "com.mysql.jdbc.Driver" />
name = "url" value = "jdbc:mysql://localhost:3306/test" />
id = "transactionManager"
class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
name = "dataSource" ref = "dataSource" />
transaction-manager = "transactionManager" />
上面连接的数据库可以自行创建
( 3 )在 HelloServiceImpl 类上加入 @Transactional 注解
( 4 )启动服务提供者和服务消费者,并访问
上面的错误为没有可用的服务提供者
查看 dubbo 管理控制台发现服务并没有发布,如下:
可以通过断点调试的方式查看 Dubbo 执行过程, Dubbo 通过 AnnotationBean 的
postProcessAfterInitialization 方法进行处理 7.2 解决方案
通过上面的断点调试可以看到,在 HelloServiceImpl 类上加入事务注解后, Spring 会为此类基于 JDK 动
态代理技术创建代理对象,创建的代理对象完整类名为 com.sun.proxy.$Proxy35 ,导致 Dubbo 在进行
包匹配时没有成功(因为我们在发布服务时扫描的包为 com.lxs.service ),所以后面真正发布服务的代
码没有执行。
解决方式操作步骤:
( 1 )修改 applicationContext-service.xml 配置文件,开启事务控制注解支持时指定 proxy-target-class
属性,值为 true 。其作用是使用 cglib 代理方式为 Service 类创建代理对象
( 2 )修改 HelloServiceImpl 类,在 Service 注解中加入 interfaceClass 属性,值为 HelloService.class ,
作用是指定服务的接口类型
transaction-manager = "transactionManager" proxy-target-
class = "true" /> @Service ( interfaceClass = HelloService . class )
@Transactional
public class HelloServiceImpl implements HelloService {
public String sayHello ( String name ) {
return "hello " + name ;
}
}