代码思路

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);
    }

}