Dubbo是Alibaba開源的分布式服務框架,我們可以非常容易地通過Dubbo來構建分布式服務,並根據自己實際業務應用場景來選擇合適的集群容錯模式,這個對於很多應用都是迫切希望的,只需要通過簡單的配置就能夠實現分布式服務調用,也就是說服務提供方(Provider)發布的服務可以天然就是集群服務,比如,在實時性要求很高的應用場景下,可能希望來自消費方(Consumer)的調用響應時間最短,只需要選擇Dubbo的Forking Cluster模式配置,就可以對一個調用請求並行發送到多台對等的提供方(Provider)服務所在的節點上,只選擇最快一個返回響應的,然后將調用結果返回給服務消費方(Consumer),顯然這種方式是以冗余服務為基礎的,需要消耗更多的資源,但是能夠滿足高實時應用的需求。
一、Dubbo服務集群容錯
假設我們使用的是單機模式的Dubbo服務,如果在服務提供方(Provider)發布服務以后,服務消費方(Consumer)發出一次調用請求,恰好這次由於網絡問題調用失敗,那么我們可以配置服務消費方重試策略,可能消費方第二次重試調用是成功的(重試策略只需要配置即可,重試過程是透明的);但是,如果服務提供方發布服務所在的節點發生故障,那么消費方再怎么重試調用都是失敗的,所以我們需要采用集群容錯模式,這樣如果單個服務節點因故障無法提供服務,還可以根據配置的集群容錯模式,調用其他可用的服務節點,這就提高了服務的可用性。
首先,根據Dubbo文檔,我們引用文檔提供的一個架構圖以及各組件關系說明,如下所示:
上述各個組件之間的關系(引自Dubbo文檔)說明如下:
- 這里的Invoker是Provider的一個可調用Service的抽象,Invoker封裝了Provider地址及Service接口信息。
- Directory代表多個Invoker,可以把它看成List,但與List不同的是,它的值可能是動態變化的,比如注冊中心推送變更。
- Cluster將Directory中的多個Invoker偽裝成一個Invoker,對上層透明,偽裝過程包含了容錯邏輯,調用失敗后,重試另一個。
- Router負責從多個Invoker中按路由規則選出子集,比如讀寫分離,應用隔離等。
- LoadBalance負責從多個Invoker中選出具體的一個用於本次調用,選的過程包含了負載均衡算法,調用失敗后,需要重選。
我們也簡單說明目前Dubbo支持的集群容錯模式,每種模式適應特定的應用場景,可以根據實際需要進行選擇。
Dubbo內置支持如下6種集群模式:
(1) Failover Cluster模式
配置值為failover。這種模式是Dubbo集群容錯默認的模式選擇,調用失敗時,會自動切換,重新嘗試調用其他節點上可用的服務。對於一些冪等性操作可以使用該模式,如讀操作,因為每次調用的副作用是相同的,所以可以選擇自動切換並重試調用,對調用者完全透明。可以看到,如果重試調用必然會帶來響應端的延遲,如果出現大量的重試調用,可能說明我們的服務提供方發布的服務有問題,如網絡延遲嚴重、硬件設備需要升級、程序算法非常耗時,等等,這就需要仔細檢測排查了。
例如,可以這樣顯式指定Failover模式,或者不配置則默認開啟Failover模式,配置示例如下:
<dubbo:service interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService" version="1.0.0" cluster="failover" retries="2" timeout="100" ref="chatRoomOnlineUserCounterService" protocol="dubbo" > <dubbo:method name="queryRoomUserCount" timeout="80" retries="2" /> </dubbo:service>
上述配置使用Failover Cluster模式,如果調用失敗一次,可以再次重試2次調用,服務級別調用超時時間為100ms,調用方法queryRoomUserCount的超時時間為80ms,允許重試2次,最壞情況調用花費時間160ms。如果該服務接口org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService還有其他的方法可供調用,則其他方法沒有顯式配置則會繼承使用dubbo:service配置的屬性值。
(2) Failfast Cluster模式
配置值為failfast。這種模式稱為快速失敗模式,調用只執行一次,失敗則立即報錯。這種模式適用於非冪等性操作,每次調用的副作用是不同的,如寫操作,比如交易系統我們要下訂單,如果一次失敗就應該讓它失敗,通常由服務消費方控制是否重新發起下訂單操作請求(另一個新的訂單)。
(3) Failsafe Cluster模式
配置值為failsafe。失敗安全模式,如果調用失敗, 則直接忽略失敗的調用,而是要記錄下失敗的調用到日志文件,以便后續審計。
(4) Failback Cluster模式
配置值為failback。失敗自動恢復,后台記錄失敗請求,定時重發。通常用於消息通知操作。
(5) Forking Cluster模式
配置值為forking。並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。
(6) Broadcast Cluster模式
配置值為broadcast。廣播調用所有提供者,逐個調用,任意一台報錯則報錯(2.1.0開始支持)。通常用於通知所有提供者更新緩存或日志等本地資源信息。
上面的6種模式都可以應用於生產環境,我們可以根據實際應用場景選擇合適的集群容錯模式。如果我們覺得Dubbo內置提供的幾種集群容錯模式都不能滿足應用需要,也可以定制實現自己的集群容錯模式,因為Dubbo框架給我提供的擴展的接口,只需要實現接口com.alibaba.dubbo.rpc.cluster.Cluster即可,接口定義如下所示:

@SPI(FailoverCluster.NAME) public interface Cluster { /** * Merge the directory invokers to a virtual invoker. * @param <T> * @param directory * @return cluster invoker * @throws RpcException */ @Adaptive <T> Invoker<T> join(Directory<T> directory) throws RpcException; }
關於如何實現一個自定義的集群容錯模式,可以參考Dubbo源碼中內置支持的汲取你容錯模式的實現,6種模式對應的實現類如下所示:
com.alibaba.dubbo.rpc.cluster.support.FailoverCluster com.alibaba.dubbo.rpc.cluster.support.FailfastCluster com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster com.alibaba.dubbo.rpc.cluster.support.FailbackCluster com.alibaba.dubbo.rpc.cluster.support.ForkingCluster com.alibaba.dubbo.rpc.cluster.support.AvailableCluster
可能我們初次接觸Dubbo時,不知道如何在實際開發過程中使用Dubbo的集群模式,后面我們會以Failover Cluster模式為例開發我們的分布式應用,再進行詳細的介紹。
二、Dubbo服務負載均衡
Dubbo框架內置提供負載均衡的功能以及擴展接口,我們可以透明地擴展一個服務或服務集群,根據需要非常容易地增加/移除節點,提高服務的可伸縮性。
Dubbo框架內置提供了4種負載均衡策略,如下所示:
(1)Random LoadBalance:隨機策略,配置值為random。可以設置權重,有利於充分利用服務器的資源,高配的可以設置權重大一些,低配的可以稍微小一些
(2)RoundRobin LoadBalance:輪詢策略,配置值為roundrobin。
(3)LeastActive LoadBalance:配置值為leastactive。根據請求調用的次數計數,處理請求更慢的節點會受到更少的請求
(4)ConsistentHash LoadBalance:一致性Hash策略,具體配置方法可以參考Dubbo文檔。相同調用參數的請求會發送到同一個服務提供方節點上,如果某個節點發生故障無法提供服務,則會基於一致性Hash算法映射到虛擬節點上(其他服務提供方)
在實際使用中,只需要選擇合適的負載均衡策略值,配置即可,下面是上述四種負載均衡策略配置的示例:
<dubbo:service interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService" version="1.0.0" cluster="failover" retries="2" timeout="100" loadbalance="random" ref="chatRoomOnlineUserCounterService" protocol="dubbo" > <dubbo:method name="queryRoomUserCount" timeout="80" retries="2" loadbalance="leastactive" /> </dubbo:service>
上述配置,也體現了Dubbo配置的繼承性特點,也就是dubbo:service元素配置了loadbalance=”random”,則該元素的子元素dubbo:method如果沒有指定負載均衡策略,則默認為loadbalance=”random”,否則如果dubbo:method指定了loadbalance=”leastactive”,則使用子元素配置的負載均衡策略覆蓋了父元素指定的策略(這里調用queryRoomUserCount方法使用leastactive負載均衡策略)。
當然,Dubbo框架也提供了實現自定義負載均衡策略的接口,可以實現com.alibaba.dubbo.rpc.cluster.LoadBalance接口,接口定義如下所示:

/** * LoadBalance. (SPI, Singleton, ThreadSafe) * * <a href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load-Balancing</a> * * @see com.alibaba.dubbo.rpc.cluster.Cluster#join(Directory) * @author qian.lei * @author william.liangf */ @SPI(RandomLoadBalance.NAME) public interface LoadBalance { /** * select one invoker in list. * @param invokers invokers. * @param url refer url * @param invocation invocation. * @return selected invoker. */ @Adaptive("loadbalance") <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException; }
如何實現一個自定義負載均衡策略,可以參考Dubbo框架內置的實現,如下所示的3個實現類:
com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
Dubbo服務集群容錯實踐
手機應用是以聊天室為基礎的,我們需要收集用戶的操作行為,然后計算聊天室中在線人數,並實時在手機應用端顯示人數,整個系統的架構如圖所示:
上圖中,主要包括了兩大主要流程:日志收集並實時處理流程、調用讀取實時計算結果流程,我們使用基於Dubbo框架開發的服務來提供實時計算結果讀取聊天人數的功能。上圖中,實際上業務接口服務器集群也可以基於Dubbo框架構建服務,就看我們想要構建什么樣的系統來滿足我們的需要。
如果不使用注冊中心,服務消費方也能夠直接調用服務提供方發布的服務,這樣需要服務提供方將服務地址暴露給服務消費方,而且也無法使用監控中心的功能,這種方式成為直連。
如果我們使用注冊中心,服務提供方將服務發布到注冊中心,而服務消費方可以通過注冊中心訂閱服務,接收服務提供方服務變更通知,這種方式可以隱藏服務提供方的細節,包括服務器地址等敏感信息,而服務消費方只能通過注冊中心來獲取到已注冊的提供方服務,而不能直接跨過注冊中心與服務提供方直接連接。這種方式的好處是還可以使用監控中心服務,能夠對服務的調用情況進行監控分析,還能使用Dubbo服務管理中心,方便管理服務,我們在這里使用的是這種方式,也推薦使用這種方式。使用注冊中心的Dubbo分布式服務相關組件結構,如下圖所示:
下面,開發部署我們的應用,通過如下4個步驟來完成:
- 服務接口定義
服務接口將服務提供方(Provider)和服務消費方(Consumer)連接起來,服務提供方實現接口中定義的服務,即給出服務的實現,而服務消費方負責調用服務。我們接口中給出了2個方法,一個是實時查詢獲取當前聊天室內人數,另一個是查詢一天中某個/某些聊天室中在線人數峰值,接口定義如下所示:
package org.shirdrn.dubbo.api; import java.util.List; public interface ChatRoomOnlineUserCounterService { String queryRoomUserCount(String rooms); List<String> getMaxOnlineUserCount(List<String> rooms, String date, String dateFormat); }
接口是服務提供方和服務消費方公共遵守的協議,一般情況下是服務提供方將接口定義好后提供給服務消費方。
- 服務提供方
服務提供方實現接口中定義的服務,其實現和普通的服務沒什么區別,我們的實現類為ChatRoomOnlineUserCounterServiceImpl,代碼如下所示:

package org.shirdrn.dubbo.provider.service; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService; import org.shirdrn.dubbo.common.utils.DateTimeUtils; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import com.alibaba.dubbo.common.utils.StringUtils; import com.google.common.base.Strings; import com.google.common.collect.Lists; public class ChatRoomOnlineUserCounterServiceImpl implements ChatRoomOnlineUserCounterService { private static final Log LOG = LogFactory.getLog(ChatRoomOnlineUserCounterServiceImpl.class); private JedisPool jedisPool; private static final String KEY_USER_COUNT = "chat::room::play::user::cnt"; private static final String KEY_MAX_USER_COUNT_PREFIX = "chat::room::max::user::cnt::"; private static final String DF_YYYYMMDD = "yyyyMMdd"; public String queryRoomUserCount(String rooms) { LOG.info("Params[Server|Recv|REQ] rooms=" + rooms); StringBuffer builder = new StringBuffer(); if(!Strings.isNullOrEmpty(rooms)) { Jedis jedis = null; try { jedis = jedisPool.getResource(); String[] fields = rooms.split(","); List<String> results = jedis.hmget(KEY_USER_COUNT, fields); builder.append(StringUtils.join(results, ",")); } catch (Exception e) { LOG.error("", e); } finally { if(jedis != null) { jedis.close(); } } } LOG.info("Result[Server|Recv|RES] " + builder.toString()); return builder.toString(); } @Override public List<String> getMaxOnlineUserCount(List<String> rooms, String date, String dateFormat) { // HGETALL chat::room::max::user::cnt::20150326 LOG.info("Params[Server|Recv|REQ] rooms=" + rooms + ",date=" + date + ",dateFormat=" + dateFormat); String whichDate = DateTimeUtils.format(date, dateFormat, DF_YYYYMMDD); String key = KEY_MAX_USER_COUNT_PREFIX + whichDate; StringBuffer builder = new StringBuffer(); if(rooms != null && !rooms.isEmpty()) { Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.hmget(key, rooms.toArray(new String[rooms.size()])); } catch (Exception e) { LOG.error("", e); } finally { if(jedis != null) { jedis.close(); } } } LOG.info("Result[Server|Recv|RES] " + builder.toString()); return Lists.newArrayList(); } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } }
代碼中通過讀取Redis中數據來完成調用,邏輯比較簡單。對應的Maven POM依賴配置,如下所示:
<dependencies> <dependency> <groupId>org.shirdrn.dubbo</groupId> <artifactId>dubbo-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.shirdrn.dubbo</groupId> <artifactId>dubbo-commons</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.jboss.netty</groupId> <artifactId>netty</artifactId> <version>3.2.7.Final</version> </dependency> </dependencies>
有關對Dubbo框架的一些依賴,我們單獨放到一個通用的Maven Module中(詳見后面“附錄:Dubbo使用Maven構建依賴配置”),這里不再多說。服務提供方實現,最關鍵的就是服務的配置,因為Dubbo基於Spring來管理配置和實例,所以通過配置可以指定服務是否是分布式服務,以及通過配置增加很多其它特性。我們的配置文件為provider-cluster.xml,內容如下所示:

<?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://code.alibabatech.com/schema/dubbo" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" /> <property name="ignoreResourceNotFound" value="true" /> <property name="locations"> <list> <value>classpath*:jedis.properties</value> </list> </property> </bean> <dubbo:application name="chatroom-cluster-provider" /> <dubbo:registry address="zookeeper://zk1:2181?backup=zk2:2181,zk3:2181" /> <dubbo:protocol name="dubbo" port="20880" /> <dubbo:service interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService" version="1.0.0" cluster="failover" retries="2" timeout="1000" loadbalance="random" actives="100" executes="200" ref="chatRoomOnlineUserCounterService" protocol="dubbo" > <dubbo:method name="queryRoomUserCount" timeout="500" retries="2" loadbalance="roundrobin" actives="50" /> </dubbo:service> <bean id="chatRoomOnlineUserCounterService" class="org.shirdrn.dubbo.provider.service.ChatRoomOnlineUserCounterServiceImpl" > <property name="jedisPool" ref="jedisPool" /> </bean> <bean id="jedisPool" class="redis.clients.jedis.JedisPool" destroy-method="destroy"> <constructor-arg index="0"> <bean class="org.apache.commons.pool2.impl.GenericObjectPoolConfig"> <property name="maxTotal" value="${redis.pool.maxTotal}" /> <property name="maxIdle" value="${redis.pool.maxIdle}" /> <property name="minIdle" value="${redis.pool.minIdle}" /> <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" /> <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /> <property name="testOnReturn" value="${redis.pool.testOnReturn}" /> <property name="testWhileIdle" value="true" /> </bean> </constructor-arg> <constructor-arg index="1" value="${redis.host}" /> <constructor-arg index="2" value="${redis.port}" /> <constructor-arg index="3" value="${redis.timeout}" /> </bean> </beans>
上面配置中,使用dubbo協議,集群容錯模式為failover,服務級別負載均衡策略為random,方法級別負載均衡策略為roundrobin(它覆蓋了服務級別的配置內容),其他一些配置內容可以參考Dubbo文檔。我們這里是從Redis讀取數據,所以使用了Redis連接池。
啟動服務示例代碼如下所示:
package org.shirdrn.dubbo.provider; import org.shirdrn.dubbo.provider.common.DubboServer; public class ChatRoomClusterServer { public static void main(String[] args) throws Exception { DubboServer.startServer("classpath:provider-cluster.xml"); } }
上面調用了DubboServer類的靜態方法startServer,如下所示:
public static void startServer(String config) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config); try { context.start(); System.in.read(); } catch (IOException e) { e.printStackTrace(); } finally { context.close(); } }
方法中主要是初始化Spring IoC容器,全部對象都交由容器來管理。
- 服務消費方
服務消費方就容易了,只需要知道注冊中心地址,並引用服務提供方提供的接口,消費方調用服務實現如下所示:

package org.shirdrn.dubbo.consumer; import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ChatRoomDubboConsumer { private static final Log LOG = LogFactory.getLog(ChatRoomDubboConsumer.class); public static void main(String[] args) throws Exception { AbstractXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:consumer.xml"); try { context.start(); ChatRoomOnlineUserCounterService chatRoomOnlineUserCounterService = (ChatRoomOnlineUserCounterService) context.getBean("chatRoomOnlineUserCounterService"); getMaxOnlineUserCount(chatRoomOnlineUserCounterService); getRealtimeOnlineUserCount(chatRoomOnlineUserCounterService); System.in.read(); } finally { context.close(); } } private static void getMaxOnlineUserCount(ChatRoomOnlineUserCounterService liveRoomOnlineUserCountService) { List<String> maxUserCounts = liveRoomOnlineUserCountService.getMaxOnlineUserCount( Arrays.asList(new String[] {"1482178010" , "1408492761", "1430546839", "1412517075", "1435861734"}), "20150327", "yyyyMMdd"); LOG.info("After getMaxOnlineUserCount invoked: maxUserCounts= " + maxUserCounts); } private static void getRealtimeOnlineUserCount(ChatRoomOnlineUserCounterService liveRoomOnlineUserCountService) throws InterruptedException { String rooms = "1482178010,1408492761,1430546839,1412517075,1435861734"; String onlineUserCounts = liveRoomOnlineUserCountService.queryRoomUserCount(rooms); LOG.info("After queryRoomUserCount invoked: onlineUserCounts= " + onlineUserCounts); } }
對應的配置文件為consumer.xml,內容如下所示:
<?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://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <dubbo:application name="chatroom-consumer" /> <dubbo:registry address="zookeeper://zk1:2181?backup=zk2:2181,zk3:2181" /> <dubbo:reference id="chatRoomOnlineUserCounterService" interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService" version="1.0.0"> <dubbo:method name="queryRoomUserCount" retries="2" /> </dubbo:reference> </beans>
也可以根據需要配置dubbo:reference相關的屬性值,也可以配置dubbo:method指定調用的方法的配置信息,詳細配置屬性可以參考Dubbo官方文檔。
- 部署與驗證
開發完成提供方服務后,在本地開發調試的時候可以怎么簡單怎么做,如果是要部署到生產環境,則需要打包后進行部署,可以參考下面的Maven POM配置:

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>1.4</version> <configuration> <createDependencyReducedPom>true</createDependencyReducedPom> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.shirdrn.dubbo.provider.ChatRoomClusterServer</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>
這里也給出Maven POM依賴的簡單配置:
<dependencies> <dependency> <groupId>org.shirdrn.dubbo</groupId> <artifactId>dubbo-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
我們開發的服務應該是分布式的,首先是通過配置內容來決定,例如設置集群模式、設置負載均衡模式等,然后在部署的時候,可以在多個節點上同一個服務,這樣多個服務都會注冊到Dubbo注冊中心,如果某個節點上的服務不可用了,可以根據我們配置的策略來選擇其他節點上的可用服務,后面通過Dubbo服務管理中心和監控中心就能更加清楚明了。
Dubbo服務管理與監控
我們需要在安裝好管理中心和監控中心以后,再將上面的開發的提供方服務部署到物理節點上,然后就能夠通過管理中心和監控中心來查看對應的詳細情況。
- Dubbo服務管理中心
安裝Dubbo服務管理中心,需要選擇一個Web容器,我們使用Tomcat服務器。首先下載Dubbo管理中心安裝文件dubbo-admin-2.5.3.war,或者直接從源碼構建得到該WAR文件。這里,我們已經構建好對應的WAR文件,然后進行安裝,執行如下命令:
cd apache-tomcat-6.0.35 rm -rf webapps/ROOT unzip ~/dubbo-admin-2.5.3.war -d webapps/ROOT
修改配置文件~/apache-tomcat-6.0.35/webapps/ROOT/WEB-INF/dubbo.properties,指定我們的注冊中心地址以及登錄密碼,內容如下所示:
dubbo.registry.address=zookeeper://zk1:2181?backup=zk2:2181,zk3:2181 dubbo.admin.root.password=root dubbo.admin.guest.password=guest
然后,根據需要修改~/apache-tomcat-6.0.35/conf/server.xml配置文件,主要是Tomcat HTTP 端口號(我這里使用8083端口),完成后可以直接啟動Tomcat服務器:
cd ~/apache-tomcat-6.0.35/
bin/catalina.sh start
然后訪問地址http://10.10.4.130:8083/即可,根據配置文件指定的root用戶密碼,就可以登錄Dubbo管理控制台。
我們將上面開發的服務提供方服務,部署到2個獨立的節點上(192.168.14.1和10.10.4.125),然后可以通過Dubbo管理中心查看對應服務的狀況,如圖所示:
上圖中可以看出,該服務有兩個獨立的節點可以提供,因為配置的集群模式為failover,如果某個節點的服務發生故障無法使用,則會自動透明地重試另一個節點上的服務,這樣就不至於出現拒絕服務的情況。如果想要查看提供方某個節點上的服務詳情,可以點擊對應的IP:Port鏈接,示例如圖所示:
上圖可以看到服務地址:
dubbo://10.10.4.125:20880/org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService?actives=100&anyhost=true&application=chatroom-cluster-provider&cluster=failover&dubbo=0.0.1-SNAPSHOT&executes=200&interface=org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService&loadbalance=random&methods=getMaxOnlineUserCount,queryRoomUserCount&pid=30942&queryRoomUserCount.actives=50&queryRoomUserCount.loadbalance=leastactive&queryRoomUserCount.retries=2&queryRoomUserCount.timeout=500&retries=2&revision=0.0.1-SNAPSHOT&side=provider&timeout=1000×tamp=1427793652814&version=1.0.0
如果我們直接暴露該地址也是可以的,不過這種直連的方式對服務消費方不是透明的,如果以后IP地址更換,也會影響調用方,所以最好是通過注冊中心來隱蔽服務地址。同一個服務所部署在的多個節點上,也就對應對應着多個服務地址。另外,也可以對已經發布的服務進行控制,如修改訪問控制、負載均衡相關配置內容等,可以通過上圖中“消費者”查看服務消費方調用服務的情況,如圖所示:
也在管理控制台可以對消費方進行管理控制。
- Dubbo監控中心
Dubbo監控中心是以Dubbo服務的形式發布到注冊中心,和普通的服務時一樣的。例如,我這里下載了Dubbo自帶的簡易監控中心文件dubbo-monitor-simple-2.5.3-assembly.tar.gz,可以解壓縮以后,修改配置文件~/dubbo-monitor-simple-2.5.3/conf/dubbo.properties的內容,如下所示:
dubbo.container=log4j,spring,registry,jetty dubbo.application.name=simple-monitor dubbo.application.owner= dubbo.registry.address=zookeeper://zk1:2181?backup=zk2:2181,zk3:2181 dubbo.protocol.port=7070 dubbo.jetty.port=8087 dubbo.jetty.directory=${user.home}/monitor dubbo.charts.directory=${dubbo.jetty.directory}/charts dubbo.statistics.directory=${user.home}/monitor/statistics dubbo.log4j.file=logs/dubbo-monitor-simple.log dubbo.log4j.level=WARN
然后啟動簡易監控中心,執行如下命令:
cd ~/dubbo-monitor-simple-2.5.3
bin/start.sh
這里使用了Jetty Web容器,訪問地址http://10.10.4.130:8087/就可以查看監控中心,Applications選項卡頁面包含了服務提供方和消費方的基本信息,如圖所示:
上圖主要列出了所有提供方發布的服務、消費方調用、服務依賴關系等內容。
接着,查看Services選項卡頁面,包含了服務提供方提供的服務列表,如圖所示:
點擊上圖中Providers鏈接就能看到服務提供方的基本信息,包括服務地址等,如圖所示:
點擊上圖中Consumers鏈接就能看到服務消費方的基本信息,包括服務地址等,如圖所示:
由於上面是Dubbo自帶的一個簡易監控中心,可能所展現的內容並不能滿足我們的需要,所以可以根據需要開發自己的監控中心。Dubbo也提供了監控中心的擴展接口,如果想要實現自己的監控中心,可以實現接口com.alibaba.dubbo.monitor.MonitorFactory和com.alibaba.dubbo.monitor.Monitor,其中MonitorFactory接口定義如下所示:
/** * MonitorFactory. (SPI, Singleton, ThreadSafe) * * @author william.liangf */ @SPI("dubbo") public interface MonitorFactory { /** * Create monitor. * @param url * @return monitor */ @Adaptive("protocol") Monitor getMonitor(URL url); }
Monitor接口定義如下所示:
/** * Monitor. (SPI, Prototype, ThreadSafe) * * @see com.alibaba.dubbo.monitor.MonitorFactory#getMonitor(com.alibaba.dubbo.common.URL) * @author william.liangf */ public interface Monitor extends Node, MonitorService { }
具體定義內容可以查看MonitorService接口,不再累述。
總結
Dubbo還提供了其他很多高級特性,如路由規則、參數回調、服務分組、服務降級等等,而且很多特性在給出內置實現的基礎上,還給出了擴展的接口,我們可以給出自定義的實現,非常方便而且強大。更多可以參考Dubbo官網用戶手冊和開發手冊。