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