Dubbojava
Author: Lijbnode
Email: lijb1121@163.commysql
1.什麼是Dubbogit
dubbo:是一個基於soa思想的rpc框架github
soa思想:面向服務的架構web
給每個模塊暴露對應的ip和端口,當作一個服務進行運行算法
重點在於服務的管理(負載均衡,容災模式,服務的橫向擴展)spring
2.架構的演變sql
節點角色說明數據庫
節點 角色說明
Provider 暴露服務的服務提供方
Consumer 調用遠程服務的服務消費方
Registry 服務註冊與發現的註冊中心
Monitor 統計服務的調用次數和調用時間的監控中心 Container 服務運行容器
調用關係說明
4.Dubbo的入門案例
dubbo的開發方式
引入Dubbo依賴
<dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.5</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-cluster</artifactId> <version>2.6.5</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-config-spring</artifactId> <version>2.6.5</version> </dependency> <!-- curator-framework 鏈接zk--> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.7.1</version> </dependency>
若是是2.6.2之前版本默認使用的zkclient鏈接zookeeper。
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:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!--對服務起名字,通常要求服務名惟一--> <dubbo:application name="usercenter-provider"/> <!--鏈接zookeer,將來將服務註冊到zk中--> <dubbo:registry protocol="zookeeper" address="CentOS:2181"/> <!--定製服務接受協議 啓動netty-server --> <dubbo:protocol name="dubbo" port="20880"/> <!--暴露本地的bean服務--> <dubbo:service interface="com.baizhi.service.IUserService" ref="userService" /> </beans>
開發服務提供者接口
publicinterface UserService { public String findName(String name); }
開發服務提供者實現類
public classUserServiceImpl implements UserService { public String findName(String name) { System.out.println("這是根據用戶名: "+name+" 查詢用戶的業務..."); return name; } }
啓動服務提供者
publicstatic void main(String[] args) throws IOException { ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("spring-dubbo.xml"); System.in.read(); }
服務消費方
引入依賴同服務方依賴
將服務方的接口複製到消費方項目中
publicinterface UserService { public String findName(String name); }
注意: 包結構要與服務提供方嚴格一致
配置文件
<?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:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!--對服務起名字,通常要求服務名惟一--> <dubbo:application name="usercenter-consumer"/> <!--鏈接zookeer,將來將服務註冊到zk中--> <dubbo:registry protocol="zookeeper" address="CentOS:2181"/> <!--引用遠程接口,在本地產生代理bean--> <dubbo:reference id="userService" interface="com.baizhi.service.IUserService" /> </beans>
調用服務方服務
publicstatic void main(String[] args) { ApplicationContext context = newClassPathXmlApplicationContext("spring-dubbo.xml"); UserService userService = (UserService)context.getBean("userService"); System.out.println(userService.findName("張三")); }
Dubbo+Spring註解版本
服務提供端配置
import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.ProtocolConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration //掃描的是接口實現類,該類上通常會使用@Service註解(dubbo) @DubboComponentScan(basePackages = {"com.baizhi.service.impl"}) public class Application_Provider { /* <dubbo:application name="usercenter-provider"/> */ [@Bean](https://my.oschina.net/bean) public ApplicationConfig applicationConfig(){ ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("usercenter-provider"); return applicationConfig; } /* <dubbo:registry protocol="zookeeper" address="CentOS:2181"/> * */ @Bean public RegistryConfig registryConfig(){ RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("CentOS:2181"); registryConfig.setProtocol("zookeeper"); registryConfig.setClient("curator"); return registryConfig; } /* <dubbo:protocol name="dubbo" port="20880"/> */ @Bean public ProtocolConfig protocolConfig(){ ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); return protocolConfig; } } -- ApplicationContext ctx= new AnnotationConfigApplicationContext(Application_Provider.class);
服務消費端
@Configuration //掃描的是接口,更具接口建立代理 @DubboComponentScan(basePackages = "com.baizhi.service") public class Application_Consumer { // <dubbo:reference id=「userService」 interface=""/> @Reference private IUserService userService; @Bean public IUserService userService(){ return userService; } /* <dubbo:application name="usercenter-consumer"/> */ @Bean public ApplicationConfig applicationConfig(){ ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("usercenter-consumer"); return applicationConfig; } /* <dubbo:registry protocol="zookeeper" address="CentOS:2181"/> * */ @Bean public RegistryConfig registryConfig(){ RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("CentOS:2181"); registryConfig.setProtocol("zookeeper"); registryConfig.setClient("curator"); return registryConfig; } } --- ApplicationContext ctx= new AnnotationConfigApplicationContext(Application_Consumer.class);
Spring Boot 整合 Dubbo
pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.17.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>com.alibaba.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>0.1.0</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.24.Final</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
</dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
參考 http://start.dubbo.io/ 自動生成服務提供者和服務消費者
服務方代碼
server.port= 8989 dubbo.application.name = dubbo-demo-server # Base packages to scan Dubbo Component: @com.alibaba.dubbo.config.annotation.Service dubbo.scan.basePackages = com.example ## RegistryConfig Bean dubbo.registry.id = my-registry dubbo.registry.address = CentOS:2181 dubbo.registry.client= curator dubbo.registry.protocol= zookeeper ## Legacy QOS Config dubbo.application.qosEnable=true dubbo.application.qosPort= 22222 dubbo.application.qosAcceptForeignIp=false
入口代碼
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
客戶端代碼
# Dubbo Config properties ## ApplicationConfig Bean dubbo.application.name= dubbo-demo-client ## RegistryConfig Bean dubbo.registry.id = my-registry dubbo.registry.address = CentOS:2181 dubbo.registry.protocol= zookeeper dubbo.registry.client = curator dubbo.application.qosEnable=false
入口代碼
@SpringBootApplication public class DemoApplication { @Reference private HelloService demoService; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @PostConstruct public void init() { String sayHello = demoService.sayHello("world"); System.err.println(sayHello); } }
以下:
<!--全局配置--> <dubbo:provider timeout="6000"/> <!--接口配置--> <dubbo:service interface="com.baizhi.service.UserService" ref="userService" timeout="5000" > <!--方法配置--> <dubbo:method name="findName" timeout="4000"/> </dubbo:service>
啓動時檢查
局部配置
<dubbo:reference interface="xx" check="false" />
全局配置
<dubbo:consumer check="false" />
Dubbo中集羣的負載均衡
只須要將服務提供者代碼,複製幾份,修改一下的端口,所有啓動便可(端口號不能衝突):
server1:<dubbo:protocol name="dubbo" port="20881"/> server2:<dubbo:protocol name="dubbo" port="20882"/> server3:<dubbo:protocol name="dubbo" port="20883"/>
啓動全部的服務提供者集羣
使用服務消費者進行調用
注意:默認dubbo使用負載均衡算法是random 隨機的
常見的負載均衡算法
1.在一個截面上碰撞的機率高,但調用量越大分佈越均勻,並且按機率使用權重後也比較均勻,有利於動態調整提供者權重 2.RoundRobin LoadBalance 輪循,按公約後的權重設置輪循比率 3.存在慢的提供者累積請求的問題,好比:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,長此以往,全部請求都卡在調到第二臺上 4.LeastActive LoadBalance 最少活躍調用數,相同活躍數的隨機,活躍數指調用先後計數差。 5.使慢的提供者收到更少請求,由於越慢的提供者的調用先後計數差會越大。 6.ConsistentHash LoadBalance 一致性Hash,相同參數的請求老是發到同一提供者。 若是對應的hash值上沒有服務提供者,按照就近原則順時針找離該hash值最近的服務端。
算法參見:http://en.wikipedia.org/wiki/Consistent_hashing
<!--缺省只對第一個參數 Hash,若是要修改,請配置--> <dubbo:parameterkey="hash.arguments" value="0,1" /> <!--缺省用160份虛擬節點,若是要修改,請配置--> <dubbo:parameterkey="hash.nodes" value="320" /> <!--當某一臺提供者掛時,本來發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引發劇烈變更。-->
a.客戶端設置集負載均衡策略
基於權重隨機
<!--服務端,默認權重是100--> <dubbo:service interface="..." weight="100" /> <!--客戶端--> <dubbo:reference interface="..." loadbalance="random" />
基於權重的輪訓策略
<dubbo:reference interface="..." loadbalance="roundrobin" />
基於調用次數(致使慢機器接受更少的請求,下降負載)
<dubbo:reference interface="..." loadbalance="leastactive" />
一致性hash(默認使用參數hash)
<dubbo:reference interface="..." loadbalance="consistenthash" />
b . 客戶端配置
<dubbo:referenceid="userService" loadbalance="consistenthash"interface="com.baizhi.service.UserService"/>
c. 服務端方法級別
<dubbo:serviceinterface="..."> <dubbo:method name="..." loadbalance="roundrobin"/> </dubbo:service>
配置優先級: 消費者優先級最高,服務端優先級就近原則,通常優先選擇在服務提供方配置
Dubbo中集羣的容錯
在集羣調用失敗時,Dubbo 提供了多種容錯方案,缺省爲 failover 重試。
集羣容錯模式配置:
<dubbo:servicecluster="failsafe"/> 或 <dubbo:referencecluster="failsafe"/
各節點關係:
a. 這裏的 Invoker 是 Provider 的一個可調用 Service 的抽象,Invoker 封裝了 Provider 地址及 Service 接口信息
Directory 表明多個 Invoker,能夠把它當作 List<Invoker> ,但與 List 不一樣的是,它的值多是動態變化的,好比註冊中心推送變動
b.Cluster 將 Directory 中的多個 Invoker 假裝成一個 Invoker,對上層透明,假裝過程包含了容錯邏輯,調用失敗後,重試另外一個
c.Router 負責從多個 Invoker 中按路由規則選出子集,好比讀寫分離,應用隔離等
d.LoadBalance 負責從多個 Invoker 中選出具體的一個用於本次調用,選的過程包含了負載均衡算法,調用失敗後,須要重選
失敗自動切換,當出現失敗,重試其它服務器 。一般用於讀操做,但重試會帶來更長延遲。可經過 retries="2" 來設置重試次數(不含第一次)。
重試次數配置以下:
<dubbo:service retries="2" /> 或 <dubbo:referenceretries="2" /> 或 <dubbo:reference> <dubbo:method name="findFoo" retries="2" /> </dubbo:reference>
快速失敗,只發起一次調用,失敗當即報錯。一般用於非冪等性的寫操做,好比新增記錄。
失敗安全,出現異常時,直接忽略。一般用於寫入審計日誌等操做。
失敗自動恢復,後臺記錄失敗請求,定時重發。一般用於消息通知操做。
並行調用多個服務器,只要一個成功即返回。一般用於實時性要求較高的讀操做,但須要浪費更多服務資源。可經過 forks="2" 來設置最大並行數。
廣播調用全部提供者,逐個調用,任意一臺報錯則報錯 [2]。一般用於通知全部提供者更新緩存或日誌等本地資源信息。
Dubbo中線程模型
若是事件處理的邏輯能迅速完成,而且不會發起新的 IO 請求,好比只是在內存中記個標識,則直接在 IO 線程上處理更快,由於減小了線程池調度。
但若是事件處理邏輯較慢,或者須要發起新的 IO 請求,好比須要查詢數據庫,則必須派發到線程池,不然 IO 線程阻塞,將致使不能接收其它請求。
若是用 IO 線程處理事件,又在事件處理過程當中發起新的 IO 請求,好比在鏈接事件中發起登陸請求,會報「可能引起死鎖」異常,但不會真死鎖。
所以,須要經過不一樣的派發策略和不一樣的線程池配置的組合來應對不一樣的場景:
<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />
Dispatcher
all 全部消息都派發到線程池,包括請求,響應,鏈接事件,斷開事件,心跳等。
direct 全部消息都不派發到線程池,所有在 IO 線程上直接執行。
message 只有請求響應消息派發到線程池,其它鏈接斷開事件,心跳等消息,直接在 IO 線程上執行。
execution 只請求消息派發到線程池,不含響應,響應和其它鏈接斷開事件,心跳等消息,直接在 IO 線程上執行。
connection 在 IO 線程上,將鏈接斷開事件放入隊列,有序逐個執行,其它消息派發到線程池。
ThreadPool
fixed 固定大小線程池,啓動時創建線程,不關閉,一直持有。(缺省)
cached 緩存線程池,空閒一分鐘自動刪除,須要時重建。
limited 可伸縮線程池,但池中的線程數只會增加不會收縮。只增加不收縮的目的是爲了不收縮時忽然來了大流量引發的性能問題。
eager 優先建立Worker線程池。在任務數量大於corePoolSize可是小於maximumPoolSize時,優先建立Worker來處理任務。當任務數量大於maximumPoolSize時,將任務放入阻塞隊列中。阻塞隊列充滿時拋出RejectedExecutionException。(相比於cached:cached在任務數量超過maximumPoolSize時直接拋出異常而不是將任務放入阻塞隊列)
線程自動dump
當業務線程池滿時,咱們須要知道線程都在等待哪些資源、條件,以找到系統的瓶頸點或異常點。dubbo經過Jstack自動導出線程堆棧來保留現場,方便排查問題
默認策略:
指定導出路徑:
# dubbo.properties dubbo.application.dump.directory=/tmp <dubbo:application ...> <dubbo:parameter key="dump.directory" value="/tmp" /> </dubbo:application>
直連提供者
在開發及測試環境下,常常須要繞過註冊中心,只測試指定服務提供者,這時候可能須要點對點直連,點對點直連方式,將以服務接口爲單位,忽略註冊中心的提供者列表,A 接口配置點對點,不影響 B 接口從註冊中心獲取列表。
若是是線上需求須要點對點,可在 dubbo:reference 中配置 url 指向提供者,將繞過註冊中心,多個地址用分號隔開,配置以下 [1]:
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
在 JVM 啓動參數中加入-D參數映射服務地址 [2],如:
java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890
若是服務比較多,也能夠用文件映射,用 -Ddubbo.resolve.file 指定映射文件路徑,此配置優先級高於 dubbo:reference 中的配置 [3],如:
java -Ddubbo.resolve.file=xxx.properties
而後在映射文件 xxx.properties 中加入配置,其中 key 爲服務名,value 爲服務提供者 URL:
com.alibaba.xxx.XxxService=dubbo://localhost:20890
注意 爲了不復雜化線上環境,不要在線上使用這個功能,只應在測試階段使用。
多協議/多版本/多註冊中心/服務分組
Dubbo 支持同一服務向多註冊中心同時註冊,或者不一樣服務分別註冊到不一樣的註冊中心上去,甚至能夠同時引用註冊在不一樣註冊中心上的同名服務。另外,註冊中心是支持自定義擴展的 [1]。也就是註冊中心的集羣。
好比:中文站有些服務來不及在青島部署,只在杭州部署,而青島的其它應用須要引用此服務,就能夠將服務同時註冊到兩個註冊中心。
<dubbo:protocol name="dubbo" port="20880"/> <dubbo:protocol name="rmi" port="20881"/> <!--暴露本地的bean服務--> <dubbo:service protocol="dubbo,rmi" interface="" ref="userService" weight="100" /> <!--消費端必須指定一個協議--> <dubbo:reference interface="..." protocol="rmi" /> <!--多版本--> <dubbo:service interface="com.foo.BarService" ref="XxBean_V1" version="1.0.0" /> <dubbo:service interface="com.foo.BarService" ref="XxBean_V2" version="2.0.0" /> <dubbo:reference interface="com.foo.BarService" version="2.0.0" /> <!--不關注寫*--> <dubbo:reference interface="com.foo.BarService" version="*" /> <!--服務分組--> <dubbo:service group="g1" interface="com.xxx.IndexService" ref="xxBean1" /> <dubbo:service group="g2" interface="com.xxx.IndexService" ref="xxBean2"/> <dubbo:reference interface="com.xxx.IndexService" group="g1" /> <!--若是不關注組,能夠寫*--> <dubbo:reference interface="com.xxx.IndexService" group="*" /> <!--多註冊中心--> <dubbo:registry id="reg1" address="" /> <dubbo:registry id="reg2" address="" default="false" /> <dubbo:service interface="" ref="xx" registry="reg1,reg2" />
上下文信息|上下文傳參
隱式傳參
RpcContext.getContext().setAttachment("index", "1"); // 隱式傳參,後面的遠程調用都會隱式將這些參數發送到服務器端,相似cookie,用於框架集成,不建議常規業務使用 xxxService.xxx(); // 遠程調用 --- public class XxxServiceImpl implements XxxService { public void xxx() { // 獲取客戶端隱式傳入的參數,用於框架集成,不建議常規業務使用 String index = RpcContext.getContext().getAttachment("index"); } }
上下文信息
// 遠程調用 xxxService.xxx(); // 本端是否爲消費端,這裏會返回true boolean isConsumerSide = RpcContext.getContext().isConsumerSide(); // 獲取最後一次調用的提供方IP地址 String serverIP = RpcContext.getContext().getRemoteHost(); // 獲取當前服務配置信息,全部配置信息都將轉換爲URL的參數 String application = RpcContext.getContext().getUrl().getParameter("application"); // 注意:每發起RPC調用,上下文狀態會變化 yyyService.yyy(); --- public class XxxServiceImpl implements XxxService { public void xxx() { // 本端是否爲提供端,這裏會返回true boolean isProviderSide = RpcContext.getContext().isProviderSide(); // 獲取調用方IP地址 String clientIP = RpcContext.getContext().getRemoteHost(); // 獲取當前服務配置信息,全部配置信息都將轉換爲URL的參數 String application = RpcContext.getContext().getUrl().getParameter("application"); // 注意:每發起RPC調用,上下文狀態會變化 yyyService.yyy(); // 此時本端變成消費端,這裏會返回false boolean isProviderSide = RpcContext.getContext().isProviderSide(); } }
服務治理
|-dubbo | |--com.baizhi.service.IUserService |-consumers |-configurators |-routers |-providers |--com.baizhi.service.IOrderService |-consumers |-configurators |-routers |-providers
dubbo提供一個服務治理的webapp叫作dubboAdmin,因爲該應有bug層出不窮,最終被dubbo拋棄。目前使用dubbo公司通常都會選擇dubbokeeper第三方提供的服務治理應用管理Dubbo集羣。用戶可使用DubboKeeper實現配置規則(動態配置服務端、消費端運行參數例如:權重/容錯/屏蔽)、路由規則
1)下載Dubbo-keeper安裝包
https://github.com/dubboclub/dubbokeeper/archive/dubbokeeper-1.0.1.tar.gz
2)解壓該文件
3)在window是執行install-mysql.bat
4)將Dubbo-keeper目錄下的target下的mysql-dubbokeeper-ui/dubbokeeper-ui-1.0.1.war拷貝到tomcat的webapps下
5)啓動tomcat ,修改上面的war包解壓出來後對其中WEB-INF/classes/dubbo.properties文件中的配置項進行調整。
dubbo.application.name=common-monitor dubbo.application.owner=bieber dubbo.registry.address=zookeeper://192.168.128.128:2181 #use netty4 dubbo.reference.client=netty4 #peeper config peeper.zookeepers=192.168.128.128:2181 peeper.zookeeper.session.timeout=60000 #logger monitor.log.home=/monitor-log monitor.collect.interval=6000
6)訪問Dubbo-keeper
http://localhost:8080/dubbokeeper-ui-1.0.1
泛化調用
泛化接口調用方式主要用於客戶端沒有 API 接口及模型類元的狀況,參數及返回值中的全部 POJO 均用 Map 表示,一般用於框架集成,好比:實現一個通用的服務測試框架,可經過 GenericService 調用全部服務實現。
經過 Spring 使用泛化調用
在 Spring 配置申明 generic="true":
<dubbo:reference id="barService" interface="com.foo.BarService" generic="true" />
在 Java 代碼獲取 barService 並開始泛化調用:
GenericService barService = (GenericService) applicationContext.getBean("barService"); Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new Object[] { "World" });
5.分佈式架構
當服務集羣規模進一步擴大,帶動IT治理結構進一步升級,須要實現動態部署,進行流動計算,現有分佈式服務架構不會帶來阻力。下圖是將來可能的一種架構:
節點角色說明
節點 角色說明 Deployer 自動部署服務的本地代理 Repository 倉庫用於存儲服務應用發佈包 Scheduler 調度中心基於訪問壓力自動增減服務提供者 Admin 統一管理控制檯 Registry 服務註冊與發現的註冊中心 Monitor 統計服務的調用次數和調用時間的監控中心