RPC即Remote Procedure Call,即为远程调用。这和Java的远程代理RMI有点相似,不过RMI只能在Java系统之间进行调用,而且是使用序列化对象的方式进行通讯。相比之下,RPC模式的Dubbo性能更高一些,因为使用HTTP进行通讯,所以能够在不一样语言的服务之间进行调用。html
首先导入Spring以及Dubbo相关依赖:
java
<properties> <spring.version>5.0.5.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- 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>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> </dependencies>
建立公共接口并安装到maven仓库:
git
package cn.bilibili.dubbo.service; public interface HellobilibiliService { String sayHello(String name); }
服务方开发:github
打包为war包,并在web.xml中配置监听器:
web
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext-service.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
引入公共接口依赖并对接口进行实现:
redis
package cn.bilibili.dubbo.service.impl; import cn.bilibili.dubbo.service.HelloService; import com.alibaba.dubbo.config.annotation.Service; //这个Service注解是dubbo的,不是spring的 @Service public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { System.out.println("-------HelloService运行了-----------"); return "hello " + name; } }
建立spring配置文件,并在其中配置dubbo:
spring
<?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: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" xmlns:dubdo="http://code.alibabatech.com/schema/dubbo" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 当前应用名称,用于注册中心计算应用间依赖关系,消费者和提供者应用名不能同样 --> <dubbo:application name="dubbo-service-provider" /> <!-- 链接服务注册中心zookeeper ip为zookeeper所在服务器的ip地址--> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <!-- 注册 协议和port 端口默认是20880 --> <dubbo:protocol name="dubbo" port="20880"></dubbo:protocol> <!-- 扫描指定包,加入@Service注解的类会被发布为服务 --> <dubbo:annotation package="cn.bilibili.dubbo.service.impl" /> </beans>
消费方开发apache
在web后台中,通常是web层依赖Service,所以直接使用Controller进行调用。
建立消费方工程并打war包,而后在web.xml中配置SpringMVC:
json
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定加载的配置文件 ,经过参数contextConfigLocation加载 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext-consumer.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
而后建立一个Controller:
浏览器
@Controller @RequestMapping("/demo") public class HelloController { //这里的注解是Dubbo提供 @Reference private HelloService helloService; @RequestMapping("/hello") @ResponseBody public String getName(String name){ //远程调用 String result = helloService.sayHello(name); return result; } }
而后在spring中配置dubbo参数:
<?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: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"> <!-- 当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要同样 --> <dubbo:application name="dubbo-service-consumer" /> <!-- 链接服务注册中心zookeeper ip为zookeeper所在服务器的ip地址--> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <!-- 扫描的方式controller --> <dubbo:annotation package="cn.bilibili.dubbo.controller" /> </beans>
两个Tomcat和JMX端口号不能同样。
而后即可以在浏览器访问该Controller。
dubbo还有一个叫dubbo-admin的可视化后台管理工具,能够单独运行在一个Tomcat中。
配置方式调整
上面使用的是包扫描的方式,其实也可使用xml直接配置bean:
<!-- 注册一个Bean --> <bean id="helloService" class="cn.bilibili.dubbo.service.impl.HelloServiceImpl"> <!-- 声明须要暴露的服务接口 --> <dubbo:service interface="cn.bilibili.dubbo.service.HelloService" ref="helloService"/>
消费方也可使用xml的方式配置:
<!-- 引用远程服务代理,能够和本地bean同样使用helloService --> <dubbo:reference id="helloService" interface="cn.bilibili.dubbo.service.HelloService"/>
在Controller中改成自动注入:
@Controller @RequestMapping("/demo") public class HelloController { @Autowired private HelloService helloService; }
服务协议:
<dubbo:protocol name="dubbo" port="20880"/>
Dubbo支持的协议有:dubbo、rmi、hessian、http、webservice、rest、redis等。推荐使用的是dubbo协议。dubbo 协议采用单一长链接和 NIO 异步通信,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的状况。不适合传送大数据量的服务,好比传文件,传视频等,除非请求量很低。
同一个工程中能够配置不一样的协议。
启动时检查
消费方在启动时默认会检查依赖的服务是否可用,可使用下面的方式关闭启动检查(配置在消费方):
<dubbo:consumer check="false"/>
负载均衡
在集群负载均衡时,Dubbo 提供了多种均衡策略(包括随机(random)、轮询(roundrobin)、最少活跃调用数、一致性Hash),缺省为random随机调用。
负载均衡能够在消费方配置也能够在服务方配置:
消费方:
/** * 使用轮询负载均衡策略 * check = false 启动时候不检查服务提供者 */ @Reference(check = false, loadbalance = "roundrobin") private DemoService demoService;
服务方:
//在服务提供者一方配置负载均衡 @Service(loadbalance = "roundrobin") public class HelloServiceImpl implements HelloService { public String sayHello(String name) { return "hello " + name; } }
和Dubbo其余的配置相似,多个配置是有覆盖关系的:
因此,4种配置的优先级是:
服务超时
超时机制:在设置的超时时间内,若是 consume 端没有接收到 provider 的返回值,认为本次调用失败。默认超时时间 1秒
消费端设置超时:
//设置整个服务的超时时间为5秒 @Reference(timeout = 5000) private HelloService helloSerice;
xml形式:
<!-- 方式一:设置整个服务的超时时间为5秒 --> <dubbo:reference id="helloService" interface="cn.bilibili.dubbo.service.HelloService" timeout="5000"/> <!-- 方式二:设置服务的某个具体方法的超时时间 --> <dubbo:reference id="helloService" interface="cn.bilibili.dubbo.service.HelloService"> <!-- 设置sayHello方法的超时时间为5秒 --> <dubbo:method name="sayHello" timeout="5000"></dubbo:method> </dubbo:reference>
服务端设置超时:
//设置整个服务的超时时间为5秒 @Service(timeout = 5000) public class HelloServiceImpl implements HelloService { }
xml形式:
<!--方式一:设置整个服务的超时时间为5秒--> <dubbo:service interface="cn.bilibili.dubbo.service.HelloService" ref="helloService" timeout="5000"/> <!--方式二:设置服务的某个具体方法的超时时间 --> <dubbo:service interface="cn.bilibili.dubbo.service.HelloService" ref="helloService"> <!-- 设置sayHello方法的超时时间为5秒 --> <dubbo:method name="sayHello" timeout="5000"></dubbo:method> </dubbo:service>
优先在服务端配置超时。
服务重试
当调用某个服务的方法失败的后, dubbo默认重试2次(不包括默认的第一次调用),在设置的重试次数内,请求都失败,认为这次请求异常,抛出异常 。咱们能够经过retries参数修改重试次数。
dubbo会在服务提供端出现异常进行再次重试调用。这个并不表明服务提供端彻底执行失败了。因此不是全部接口都适合重试,若是一个服务是不等幂,那么不适合重试的机制,由于会存在重复提交的问题,不然是能够进行重试的。好比提交一个订单的接口是不能进行重试的,而查询类型的接口是能够重试的。(关于幂等性能够看看MDN和这篇博客)
服务重试须要慎重使用。
消费端重试设置:
//设置整个服务失败后的重试次数为3,实际调用是4次 @Reference(retries = 3) private HelloService helloSerice;
xml形式
<!--方式一:设置整个服务失败后的重试次数为3,实际调用是4次--> <dubbo:reference id="helloService" interface="cn.bilibili.dubbo.service.HelloService" retries="3"/> <!--方式二:设置服务的某个具体方法的重试次数--> <dubbo:reference id="helloService" interface="cn.bilibili.dubbo.service.HelloService"> <!-- 设置sayHello方法的重试次数为3,实际调用是4次 --> <dubbo:method name="sayHello" retries="3"></dubbo:method> </dubbo:reference>
服务端设置:
//设置整个服务失败后的重试次数为3,实际调用是4次 @Service(retries = 3) public class HelloServiceImpl implements HelloService { }
xml形式
<!--方式一:设置整个服务失败后的重试次数为3,实际调用是4次--> <dubbo:service interface="cn.bilibli.dubbo.service.HelloService" ref="helloService" retries="3"/> <!--方式二:设置服务的某个具体方法的重试次数--> <dubbo:service interface="cn.bilibili.dubbo.service.HelloService" ref="helloService"> <!-- 设置sayHello方法的重试次数为3,实际调用是4次 --> <dubbo:method name="sayHello" retries="3"></dubbo:method> </dubbo:service>
可是咱们若是在服务提供者类上加入@Transactional事务控制注解后,服务就发布不成功了。缘由是事务控制的底层原理是为服务提供者类建立代理对象,而默认状况下Spring是基于JDK动态代理方式建立代理对象,而此代理对象的完整类名为com.sun.proxy.$Proxy42(最后两位数字不是固定的),致使Dubbo在发布服务前进行包匹配时没法完成匹配,进而没有进行服务的发布。
解决方法:
开启事务时开启proxy-target-class,强制使用cglib代理:
proxy-target-class在spring事务、aop、缓存这几块都有设置,其做用都是同样的:
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <aop:config proxy-target-class="true"> <cache:annotation-driven proxy-target-class="true"/>
而后须要在服务提供者的接口上指定服务接口类型:
//指定服务接口类型 @Service(interfaceClass = HelloService.class) @Transactional public class HelloServiceImpl implements HelloService { public String sayHello(String name) { return "hello " + name; } }