代码思路
Dubbo作为一个RPC框架,最核心的功能就是远程调用。本文会先给出一个服务提供者(Provider)和一个服务消费者(Consumer)的经典代码,并在此基础上进行扩展说明。
首先明确,远程调用的双方不能在同一个内存中(不然就是最普通的调用啊喂),因此我们把提供者和消费者通过Tomcat分别发布在两个接口上,来模拟远程调用。
一个接口就是一个服务。在提供者的代码中通过 @Service注解 暴露该接口(服务),在消费者的代码中通过 @Reference注解
需要万分注意的是,此处的@Service注解是dubbo包下的,而不是spring包下的。
另一个关键就是Zookepper注册中心的配置。首先在服务器/虚拟机中开启Zookeeper服务,然后分别在提供者的applicationContext.xml、消费者的spring-mvc.xml中对其进行配置。
提供者(Provider)
① 坐标依赖
<dependencies>
<!-- spring相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-xxxxxx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- dubbo相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
</dependency>
<!-- zookeeper相关 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>
② 配置web.xml
<!-- 经典环境搭建的Spring部分 -->
<!-- Spring全局初始化参数 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- Spring监听器(容器集成web环境) -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
③ 配置applicationContext.xml
<!-- 注意引入dubbo命名空间时,网址写http://code.alibabatech.com/schema/dubbo -->
<!-- 每一个dubbo应用(服务提供者&服务消费者)的唯一名称 -->
<dubbo:application name="dubbo_provider"/>
<!-- 指定服务的注册中心 -->
<dubbo:registry address="zookeeper://10.211.55.4:2181"/>
<!-- 配置协议和端口 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 包扫描(用于发布dubbo服务) -->
<dubbo:annotation package="com.samarua.service.impl"/>
④ 编写服务接口
com.samarua.service.HelloService接口
-------------------------------------------------------------------------------------------
public interface HelloService {
public String sayHello(String name);
}
⑤ 编写服务实现类
com.samarua.service.impl.HelloServiceImpl实现类
-------------------------------------------------------------------------------------------
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
}
消费者(Consumer)
① 坐标依赖
<dependencies>
<!-- spring相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-xxxxxx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- dubbo相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
</dependency>
<!-- zookeeper相关 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>
② 配置web.xml
<!-- 经典环境搭建的SpringMVC部分 -->
<!-- SpringMVC前端控制器 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
③ 配置spring-mvc.xml
<!-- 注意引入dubbo命名空间时,网址写http://code.alibabatech.com/schema/dubbo -->
<!-- 每一个dubbo应用(服务提供者&服务消费者)的唯一名称 -->
<dubbo:application name="dubbo_consumer"/>
<!-- 指定服务的注册中心 -->
<dubbo:registry address="zookeeper://10.211.55.4:2181"/>
<!-- 包扫描(用于引用dubbo服务) -->
<dubbo:annotation package="com.samarua.controller"/>
④ 编写服务接口(和Provider的服务接口完全相同)
com.samarua.service.HelloService接口
-------------------------------------------------------------------------------------------
public interface HelloService {
public String sayHello(String name);
}
⑤ 编写控制器
com.samarua.controller.HelloController控制器
-------------------------------------------------------------------------------------------
@Controller
@RequestMapping("/hello")
public class HelloController {
@Reference
private HelloService helloService;
@RequestMapping("/sayHello")
@ResponseBody
public String sayHello(String name) {
return helloService.sayHello(name);
}
}
扩展说明
1. 接口耦合
提供者和消费者中编写了完全相同的服务接口,是不是耦合过高了?有没有什么解决思路?
没错,同一个接口被复制了两份,并不优雅。更好的思路是单独创建一个Maven工程,将接口写在这个Maven工程中,提供者和消费者只需要引入该Maven工程的坐标即可。
2. xml开发
上面的Demo中使用注解+包扫描开发:
<!-- Provider -->
<dubbo:annotation package="com.samarua.service.impl"/>
<!-- Consumer -->
<dubbo:annotation package="com.samarua.controller"/>
那么我们自然可以用xml注解方式替代:
<!-- Provider -->
<bean id="helloService" class="com.samarua.service.impl.HelloServiceImpl" />
<dubbo:service interface="com.samarua.service.HelloService" ref="helloService" />
<!-- Consumer -->
<dubbo:reference id="helloService" interface="com.samarua.service.HelloService" />
3. 协议
在提供者一方可以配置协议,并指定端口号:
<dubbo:protocol name="dubbo" port="20880"/>
Dubbo支持的协议有:dubbo、rmi、hession、http…
其中推荐使用dubbo协议。dubbo协议采用单一长连接和NIO异步通信,适合小数据大并发的服务调用,不适合传送大数据量的服务,比如传文件、传视频。
另外,Dubbo也支持对不同的服务使用不同的协议:
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="20881" />
<!-- 使用dubbo协议暴露服务 -->
<dubbo:service interface="com.samarua.service.HelloService" ref="helloService" protocol="dubbo" />
<!-- 使用rmi协议暴露服务 -->
<dubbo:service interface="com.samarua.service.HelloService" ref="demoService" protocol="rmi" />
4. 启动时检查
消费者在启动时,默认会检查它依赖的服务者是否已经开启,如果所依赖的服务不可用会抛出异常。
我们当然可以控制这种检查(当然是在消费者一方配置):
<dubbo:consumer check="false"/>
建议在开发阶段置为false(更加专注开发,而不被服务器开启顺序所困扰),在产品上线后置为true(早发现早治疗,防止造成更严重后果)。
5. 负载均衡
Dubbo提供了多种负载均衡策略(随机、轮询、最少活跃调用数、一致性Hash),缺省为random随机调用。
随机调用非常非常好理解,假设有1个消费者机器,10个提供者机器,且提供者们提供的是相同的服务,那么当消费者进行远程调用时,会随机的调用其中任意一个机器的服务。从稍底层的视角来看,Zookeeper注册中心会给消费者一个提供者地址列表,消费者从中进行随机选择。
在代码中通过注解参数可以轻易配置负载均衡算法:
// 提供者
@Service(loadbalance = "random")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
}
// 消费者
@Controller
@RequestMapping("/hello")
public class HelloController {
@Reference(loadbalance = "random")
private HelloService helloService;
@RequestMapping("/sayHello")
@ResponseBody
public String sayHello(String name) {
return helloService.sayHello(name);
}
}