基於Dubbo框架構建分佈式服務

Dubbo是Alibaba開源的分佈式服務框架,咱們能夠很是容易地經過Dubbo來構建分佈式服務,並根據本身實際業務應用場景來選擇合適的集羣容錯模式,這個對於不少應用都是迫切但願的,只須要經過簡單的配置就可以實現分佈式服務調用,也就是說服務提供方(Provider)發佈的服務能夠自然就是集羣服務,好比,在實時性要求很高的應用場景下,可能但願來自消費方(Consumer)的調用響應時間最短,只須要選擇Dubbo的Forking Cluster模式配置,就能夠對一個調用請求並行發送到多臺對等的提供方(Provider)服務所在的節點上,只選擇最快一個返回響應的,而後將調用結果返回給服務消費方(Consumer),顯然這種方式是以冗餘服務爲基礎的,須要消耗更多的資源,可是可以知足高實時應用的需求。
有關Dubbo服務框架的簡單使用,能夠參考個人其餘兩篇文章(《基於Dubbo的Hessian協議實現遠程調用》,《Dubbo實現RPC調用使用入門》,後面參考連接中已給出連接),這裏主要圍繞Dubbo分佈式服務相關配置的使用來講明與實踐。java

Dubbo服務集羣容錯git

假設咱們使用的是單機模式的Dubbo服務,若是在服務提供方(Provider)發佈服務之後,服務消費方(Consumer)發出一次調用請求,剛好此次因爲網絡問題調用失敗,那麼咱們能夠配置服務消費方重試策略,可能消費方第二次重試調用是成功的(重試策略只須要配置便可,重試過程是透明的);可是,若是服務提供方發佈服務所在的節點發生故障,那麼消費方再怎麼重試調用都是失敗的,因此咱們須要採用集羣容錯模式,這樣若是單個服務節點因故障沒法提供服務,還能夠根據配置的集羣容錯模式,調用其餘可用的服務節點,這就提升了服務的可用性。
首先,根據Dubbo文檔,咱們引用文檔提供的一個架構圖以及各組件關係說明,以下所示:

上述各個組件之間的關係(引自Dubbo文檔)說明以下:github

  • 這裏的Invoker是Provider的一個可調用Service的抽象,Invoker封裝了Provider地址及Service接口信息。
  • Directory表明多個Invoker,能夠把它當作List,但與List不一樣的是,它的值多是動態變化的,好比註冊中心推送變動。
  • Cluster將Directory中的多個Invoker假裝成一個Invoker,對上層透明,假裝過程包含了容錯邏輯,調用失敗後,重試另外一個。
  • Router負責從多個Invoker中按路由規則選出子集,好比讀寫分離,應用隔離等。
  • LoadBalance負責從多個Invoker中選出具體的一個用於本次調用,選的過程包含了負載均衡算法,調用失敗後,須要重選。

咱們也簡單說明目前Dubbo支持的集羣容錯模式,每種模式適應特定的應用場景,能夠根據實際須要進行選擇。Dubbo內置支持以下6種集羣模式:web

  • Failover Cluster模式

配置值爲failover。這種模式是Dubbo集羣容錯默認的模式選擇,調用失敗時,會自動切換,從新嘗試調用其餘節點上可用的服務。對於一些冪等性操做可使用該模式,如讀操做,由於每次調用的反作用是相同的,因此能夠選擇自動切換並重試調用,對調用者徹底透明。能夠看到,若是重試調用必然會帶來響應端的延遲,若是出現大量的重試調用,可能說明咱們的服務提供方發佈的服務有問題,如網絡延遲嚴重、硬件設備須要升級、程序算法很是耗時,等等,這就須要仔細檢測排查了。
例如,能夠這樣顯式指定Failover模式,或者不配置則默認開啓Failover模式,配置示例以下:redis

1 <dubbo:service interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService"version="1.0.0"
2      cluster="failover" retries="2" timeout="100" ref="chatRoomOnlineUserCounterService"protocol="dubbo" >
3      <dubbo:method name="queryRoomUserCount" timeout="80" retries="2" />
4 </dubbo:service>

上述配置使用Failover Cluster模式,若是調用失敗一次,能夠再次重試2次調用,服務級別調用超時時間爲100ms,調用方法queryRoomUserCount的超時時間爲80ms,容許重試2次,最壞狀況調用花費時間160ms。若是該服務接口org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService還有其餘的方法可供調用,則其餘方法沒有顯式配置則會繼承使用dubbo:service配置的屬性值。算法

  • Failfast Cluster模式

配置值爲failfast。這種模式稱爲快速失敗模式,調用只執行一次,失敗則當即報錯。這種模式適用於非冪等性操做,每次調用的反作用是不一樣的,如寫操做,好比交易系統咱們要下訂單,若是一次失敗就應該讓它失敗,一般由服務消費方控制是否從新發起下訂單操做請求(另外一個新的訂單)。spring

  • Failsafe Cluster模式

配置值爲failsafe。失敗安全模式,若是調用失敗, 則直接忽略失敗的調用,而是要記錄下失敗的調用到日誌文件,以便後續審計。apache

  • Failback Cluster模式

配置值爲failback。失敗自動恢復,後臺記錄失敗請求,定時重發。一般用於消息通知操做。json

  • Forking Cluster模式

配置值爲forking。並行調用多個服務器,只要一個成功即返回。一般用於實時性要求較高的讀操做,但須要浪費更多服務資源。api

  • Broadcast Cluster模式

配置值爲broadcast。廣播調用全部提供者,逐個調用,任意一臺報錯則報錯(2.1.0開始支持)。一般用於通知全部提供者更新緩存或日誌等本地資源信息。
上面的6種模式均可以應用於生產環境,咱們能夠根據實際應用場景選擇合適的集羣容錯模式。若是咱們以爲Dubbo內置提供的幾種集羣容錯模式都不能知足應用須要,也能夠定製實現本身的集羣容錯模式,由於Dubbo框架給我提供的擴展的接口,只須要實現接口com.alibaba.dubbo.rpc.cluster.Cluster便可,接口定義以下所示:

01 @SPI(FailoverCluster.NAME)
02 public interface Cluster {
03  
04     /**
05      * Merge the directory invokers to a virtual invoker.
06      * @param <T>
07      * @param directory
08      * @return cluster invoker
09      * @throws RpcException
10      */
11     @Adaptive
12     <T> Invoker<T> join(Directory<T> directory) throws RpcException;
13  
14 }

關於如何實現一個自定義的集羣容錯模式,能夠參考Dubbo源碼中內置支持的汲取你容錯模式的實現,6種模式對應的實現類以下所示:

1 com.alibaba.dubbo.rpc.cluster.support.FailoverCluster
2 com.alibaba.dubbo.rpc.cluster.support.FailfastCluster
3 com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster
4 com.alibaba.dubbo.rpc.cluster.support.FailbackCluster
5 com.alibaba.dubbo.rpc.cluster.support.ForkingCluster
6 com.alibaba.dubbo.rpc.cluster.support.AvailableCluster

可能咱們初次接觸Dubbo時,不知道如何在實際開發過程當中使用Dubbo的集羣模式,後面咱們會以Failover Cluster模式爲例開發咱們的分佈式應用,再進行詳細的介紹。

Dubbo服務負載均衡

Dubbo框架內置提供負載均衡的功能以及擴展接口,咱們能夠透明地擴展一個服務或服務集羣,根據須要很是容易地增長/移除節點,提升服務的可伸縮性。Dubbo框架內置提供了4種負載均衡策略,以下所示:

  • Random LoadBalance:隨機策略,配置值爲random。能夠設置權重,有利於充分利用服務器的資源,高配的能夠設置權重大一些,低配的能夠稍微小一些
  • RoundRobin LoadBalance:輪詢策略,配置值爲roundrobin。
  • LeastActive LoadBalance:配置值爲leastactive。根據請求調用的次數計數,處理請求更慢的節點會受到更少的請求
  • ConsistentHash LoadBalance:一致性Hash策略,具體配置方法能夠參考Dubbo文檔。相同調用參數的請求會發送到同一個服務提供方節點上,若是某個節點發生故障沒法提供服務,則會基於一致性Hash算法映射到虛擬節點上(其餘服務提供方)

在實際使用中,只須要選擇合適的負載均衡策略值,配置便可,下面是上述四種負載均衡策略配置的示例:

1 <dubbo:service interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService"version="1.0.0"
2      cluster="failover" retries="2" timeout="100" loadbalance="random"
3      ref="chatRoomOnlineUserCounterService" protocol="dubbo" >
4      <dubbo:method name="queryRoomUserCount" timeout="80" retries="2"loadbalance="leastactive" />
5 </dubbo:service>

上述配置,也體現了Dubbo配置的繼承性特色,也就是dubbo:service元素配置了loadbalance=」random」,則該元素的子元素dubbo:method若是沒有指定負載均衡策略,則默認爲loadbalance=」random」,不然若是dubbo:method指定了loadbalance=」leastactive」,則使用子元素配置的負載均衡策略覆蓋了父元素指定的策略(這裏調用queryRoomUserCount方法使用leastactive負載均衡策略)。
固然,Dubbo框架也提供了實現自定義負載均衡策略的接口,能夠實現

核心技術:Maven,Springmvc mybatis shiro, Druid, Restful, Dubbo, ZooKeeper,Redis,FastDFS,ActiveMQ,Nginx 
1.     項目核心代碼結構截圖

分佈式框架介紹 - kafkaee - kafkaee的博客

   項目模塊依賴分佈式框架介紹 - kafkaee - kafkaee的博客

特別提醒:開發人員在開發的時候能夠將本身的業務REST服務化或者Dubbo服務化

2.    項目依賴介紹

   2.1 後臺管理系統、Rest服務系統、Scheculer定時調度系統依賴以下圖:
 

分佈式框架介紹 - kafkaee - kafkaee的博客

       2.2 Dubbo獨立服務項目依賴以下圖:

 分佈式框架介紹 - kafkaee - kafkaee的博客

3.  項目功能部分截圖:

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客
 

zookeeper、dubbo服務啓動 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客
 

dubbo管控臺 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 REST服務平臺

分佈式框架介紹 - kafkaee - kafkaee的博客

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

 

分佈式框架介紹 - kafkaee - kafkaee的博客

 

com.alibaba.dubbo.rpc.cluster.LoadBalance接口,接口定義以下所示:

01 /**
02 * LoadBalance. (SPI, Singleton, ThreadSafe)
03 *
04 * <a href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load-Balancing</a>
05 *
06 * @see com.alibaba.dubbo.rpc.cluster.Cluster#join(Directory)
07 * @author qian.lei
08 * @author william.liangf
09 */
10 @SPI(RandomLoadBalance.NAME)
11 public interface LoadBalance {
12  
13      /**
14      * select one invoker in list.
15      * @param invokers invokers.
16      * @param url refer url
17      * @param invocation invocation.
18      * @return selected invoker.
19      */
20     @Adaptive("loadbalance")
21      <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throwsRpcException;
22  
23 }

如何實現一個自定義負載均衡策略,能夠參考Dubbo框架內置的實現,以下所示的3個實現類:

1 com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
2 com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
3 com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance

Dubbo服務集羣容錯實踐

手機應用是以聊天室爲基礎的,咱們須要收集用戶的操做行爲,而後計算聊天室中在線人數,並實時在手機應用端顯示人數,整個系統的架構如圖所示:

上圖中,主要包括了兩大主要流程:日誌收集並實時處理流程、調用讀取實時計算結果流程,咱們使用基於Dubbo框架開發的服務來提供實時計算結果讀取聊天人數的功能。上圖中,實際上業務接口服務器集羣也能夠基於Dubbo框架構建服務,就看咱們想要構建什麼樣的系統來知足咱們的須要。
若是不使用註冊中心,服務消費方也可以直接調用服務提供方發佈的服務,這樣須要服務提供方將服務地址暴露給服務消費方,並且也沒法使用監控中心的功能,這種方式成爲直連。
若是咱們使用註冊中心,服務提供方將服務發佈到註冊中心,而服務消費方能夠經過註冊中心訂閱服務,接收服務提供方服務變動通知,這種方式能夠隱藏服務提供方的細節,包括服務器地址等敏感信息,而服務消費方只能經過註冊中心來獲取到已註冊的提供方服務,而不能直接跨過註冊中心與服務提供方直接鏈接。這種方式的好處是還可使用監控中心服務,可以對服務的調用狀況進行監控分析,還能使用Dubbo服務管理中心,方便管理服務,咱們在這裏使用的是這種方式,也推薦使用這種方式。使用註冊中心的Dubbo分佈式服務相關組件結構,以下圖所示:

下面,開發部署咱們的應用,經過以下4個步驟來完成:

  • 服務接口定義

服務接口將服務提供方(Provider)和服務消費方(Consumer)鏈接起來,服務提供方實現接口中定義的服務,即給出服務的實現,而服務消費方負責調用服務。咱們接口中給出了2個方法,一個是實時查詢獲取當前聊天室內人數,另外一個是查詢一天中某個/某些聊天室中在線人數峯值,接口定義以下所示:

01 package org.shirdrn.dubbo.api;
02  
03 import java.util.List;
04  
05 public interface ChatRoomOnlineUserCounterService {
06  
07      String queryRoomUserCount(String rooms);
08      
09      List<String> getMaxOnlineUserCount(List<String> rooms, String date, String dateFormat);
10 }

接口是服務提供方和服務消費方公共遵照的協議,通常狀況下是服務提供方將接口定義好後提供給服務消費方。

  • 服務提供方

服務提供方實現接口中定義的服務,其實現和普通的服務沒什麼區別,咱們的實現類爲ChatRoomOnlineUserCounterServiceImpl,代碼以下所示:

01 package org.shirdrn.dubbo.provider.service;
02  
03 import java.util.List;
04  
05 import org.apache.commons.logging.Log;
06 import org.apache.commons.logging.LogFactory;
07 import org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService;
08 import org.shirdrn.dubbo.common.utils.DateTimeUtils;
09  
10 import redis.clients.jedis.Jedis;
11 import redis.clients.jedis.JedisPool;
12  
13 import com.alibaba.dubbo.common.utils.StringUtils;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.Lists;
16  
17 public class ChatRoomOnlineUserCounterServiceImpl implements ChatRoomOnlineUserCounterService {
18  
19      private static final Log LOG = LogFactory.getLog(ChatRoomOnlineUserCounterServiceImpl.class);
20      private JedisPool jedisPool;
21      private static final String KEY_USER_COUNT = "chat::room::play::user::cnt";
22      private static final String KEY_MAX_USER_COUNT_PREFIX = "chat::room::max::user::cnt::";
23      private static final String DF_YYYYMMDD = "yyyyMMdd";
24  
25      public String queryRoomUserCount(String rooms) {
26           LOG.info("Params[Server|Recv|REQ] rooms=" + rooms);
27           StringBuffer builder = new StringBuffer();
28           if(!Strings.isNullOrEmpty(rooms)) {
29                Jedis jedis = null;
30                try {
31                     jedis = jedisPool.getResource();
32                     String[] fields = rooms.split(",");
33                     List<String> results = jedis.hmget(KEY_USER_COUNT, fields);
34                     builder.append(StringUtils.join(results, ","));
35                catch (Exception e) {
36                     LOG.error("", e);
37                finally {
38                     if(jedis != null) {
39                          jedis.close();
40                     }
41                }
42           }
43           LOG.info("Result[Server|Recv|RES] " + builder.toString());
44           return builder.toString();
45      }
46      
47      @Override
48      public List<String> getMaxOnlineUserCount(List<String> rooms, String date, String dateFormat) {
49           // HGETALL chat::room::max::user::cnt::20150326
50           LOG.info("Params[Server|Recv|REQ] rooms=" + rooms + ",date=" + date +",dateFormat=" + dateFormat);
51           String whichDate = DateTimeUtils.format(date, dateFormat, DF_YYYYMMDD);
52           String key = KEY_MAX_USER_COUNT_PREFIX + whichDate;
53           StringBuffer builder = new StringBuffer();
54           if(rooms != null && !rooms.isEmpty()) {
55                Jedis jedis = null;
56                try {
57                     jedis = jedisPool.getResource();
58                     return jedis.hmget(key, rooms.toArray(new String[rooms.size()]));
59                catch (Exception e) {
60                     LOG.error("", e);
61                finally {
62                     if(jedis != null) {
63                          jedis.close();
64                     }
65                }
66           }
67           LOG.info("Result[Server|Recv|RES] " + builder.toString());
68           return Lists.newArrayList();
69      }
70      
71      public void setJedisPool(JedisPool jedisPool) {
72           this.jedisPool = jedisPool;
73      }
74  
75 }

代碼中經過讀取Redis中數據來完成調用,邏輯比較簡單。對應的Maven POM依賴配置,以下所示:

01 <dependencies>
02      <dependency>
03           <groupId>org.shirdrn.dubbo</groupId>
04           <artifactId>dubbo-api</artifactId>
05           <version>0.0.1-SNAPSHOT</version>
06      </dependency>
07      <dependency>
08           <groupId>org.shirdrn.dubbo</groupId>
09           <artifactId>dubbo-commons</artifactId>
10           <version>0.0.1-SNAPSHOT</version>
11      </dependency>
12      <dependency>
13           <groupId>redis.clients</groupId>
14           <artifactId>jedis</artifactId>
15           <version>2.5.2</version>
16      </dependency>
17      <dependency>
18           <groupId>org.apache.commons</groupId>
19           <artifactId>commons-pool2</artifactId>
20           <version>2.2</version>
21      </dependency>
22      <dependency>
23           <groupId>org.jboss.netty</groupId>
24           <artifactId>netty</artifactId>
25           <version>3.2.7.Final</version>
26      </dependency>
27 </dependencies>

有關對Dubbo框架的一些依賴,咱們單獨放到一個通用的Maven Module中(詳見後面「附錄:Dubbo使用Maven構建依賴配置」),這裏再也不多說。服務提供方實現,最關鍵的就是服務的配置,由於Dubbo基於Spring來管理配置和實例,因此經過配置能夠指定服務是不是分佈式服務,以及經過配置增長不少其它特性。咱們的配置文件爲provider-cluster.xml,內容以下所示:

01 <?xml version="1.0" encoding="UTF-8"?>
02  
03 <beans xmlns="http://www.springframework.org/schema/beans"
04      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
05      xmlns:p="http://www.springframework.org/schema/p"
06      xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
07      http://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd">
08  
09      <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
10           <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
11           <property name="ignoreResourceNotFound" value="true" />
12           <property name="locations">
13                <list>
14                     <value>classpath*:jedis.properties</value>
15                </list>
16           </property>
17      </bean>
18      
19      <dubbo:application name="chatroom-cluster-provider" />
20      <dubbo:registry address="zookeeper://zk1:2181?backup=zk2:2181,zk3:2181" />
21      
22      <dubbo:protocol name="dubbo" port="20880" />
23      
24      <dubbo:service interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService"version="1.0.0"
25           cluster="failover" retries="2" timeout="1000" loadbalance="random" actives="100"executes="200"
26           ref="chatRoomOnlineUserCounterService" protocol="dubbo" >
27           <dubbo:method name="queryRoomUserCount" timeout="500" retries="2"loadbalance="roundrobin" actives="50" />
28      </dubbo:service>
29      
30      <bean id="chatRoomOnlineUserCounterService"class="org.shirdrn.dubbo.provider.service.ChatRoomOnlineUserCounterServiceImpl" >
31           <property name="jedisPool" ref="jedisPool" />
32      </bean>
33      
34      <bean id="jedisPool" class="redis.clients.jedis.JedisPool" destroy-method="destroy">
35           <constructor-arg index="0">
36                <bean class="org.apache.commons.pool2.impl.GenericObjectPoolConfig">
37                     <property name="maxTotal" value="${redis.pool.maxTotal}" />
38                     <property name="maxIdle" value="${redis.pool.maxIdle}" />
39                     <property name="minIdle" value="${redis.pool.minIdle}" />
40                     <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
41                     <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
42                     <property name="testOnReturn" value="${redis.pool.testOnReturn}" />
43                     <property name="testWhileIdle" value="true" />
44                </bean>
45           </constructor-arg>
46           <constructor-arg index="1" value="${redis.host}" />
47           <constructor-arg index="2" value="${redis.port}" />
48           <constructor-arg index="3" value="${redis.timeout}" />
49      </bean>
50      
51 </beans>

上面配置中,使用dubbo協議,集羣容錯模式爲failover,服務級別負載均衡策略爲random,方法級別負載均衡策略爲roundrobin(它覆蓋了服務級別的配置內容),其餘一些配置內容能夠參考Dubbo文檔。咱們這裏是從Redis讀取數據,因此使用了Redis鏈接池。
啓動服務示例代碼以下所示:

01 package org.shirdrn.dubbo.provider;
02  
03 import org.shirdrn.dubbo.provider.common.DubboServer;
04  
05 public class ChatRoomClusterServer {
06  
07      public static void main(String[] args) throws Exception {
08           DubboServer.startServer("classpath:provider-cluster.xml");
09      }
10  
11 }

上面調用了DubboServer類的靜態方法startServer,以下所示:

01 public static void startServer(String config) {
02      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
03      try {
04           context.start();
05           System.in.read();
06      catch (IOException e) {
07           e.printStackTrace();
08      finally {
09           context.close();
10      }
11 }

方法中主要是初始化Spring IoC容器,所有對象都交由容器來管理。

  • 服務消費方

服務消費方就容易了,只須要知道註冊中心地址,並引用服務提供方提供的接口,消費方調用服務實現以下所示:

01 package org.shirdrn.dubbo.consumer;
02  
03 import java.util.Arrays;
04 import java.util.List;
05  
06 import org.apache.commons.logging.Log;
07 import org.apache.commons.logging.LogFactory;
08 import org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService;
09 import org.springframework.context.support.AbstractXmlApplicationContext;
10 import org.springframework.context.support.ClassPathXmlApplicationContext;
11  
12 public class ChatRoomDubboConsumer {
13  
14      private static final Log LOG = LogFactory.getLog(ChatRoomDubboConsumer.class);
15      
16      public static void main(String[] args) throws Exception {
17           AbstractXmlApplicationContext context = newClassPathXmlApplicationContext("classpath:consumer.xml");
18           try {
19                context.start();
20                ChatRoomOnlineUserCounterService chatRoomOnlineUserCounterService = (ChatRoomOnlineUserCounterService) context.getBean("chatRoomOnlineUserCounterService");        
21                getMaxOnlineUserCount(chatRoomOnlineUserCounterService);             
22                getRealtimeOnlineUserCount(chatRoomOnlineUserCounterService);             
23                System.in.read();
24           finally {
25                context.close();
26           }
27           
28      }
29  
30      private static void getMaxOnlineUserCount(ChatRoomOnlineUserCounterService liveRoomOnlineUserCountService) {
31           List<String> maxUserCounts = liveRoomOnlineUserCountService.getMaxOnlineUserCount(
32                     Arrays.asList(new String[] {"1482178010" "1408492761""1430546839","1412517075""1435861734"}), "20150327""yyyyMMdd");
33           LOG.info("After getMaxOnlineUserCount invoked: maxUserCounts= " + maxUserCounts);
34      }
35  
36      private static void getRealtimeOnlineUserCount(ChatRoomOnlineUserCounterService liveRoomOnlineUserCountService)
37                throws InterruptedException {
38           String rooms = "1482178010,1408492761,1430546839,1412517075,1435861734";
39           String onlineUserCounts = liveRoomOnlineUserCountService.queryRoomUserCount(rooms);
40           LOG.info("After queryRoomUserCount invoked: onlineUserCounts= " + onlineUserCounts);
41      }
42 }

對應的配置文件爲consumer.xml,內容以下所示:

01 <?xml version="1.0" encoding="UTF-8"?>
02  
03 <beans xmlns="http://www.springframework.org/schema/beans"
04      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
05      xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
06      http://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd">
07  
08      <dubbo:application name="chatroom-consumer" />
09      <dubbo:registry address="zookeeper://zk1:2181?backup=zk2:2181,zk3:2181" />
10      
11      <dubbo:reference id="chatRoomOnlineUserCounterService"interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService" version="1.0.0">
12           <dubbo:method name="queryRoomUserCount" retries="2" />
13      </dubbo:reference>
14  
15 </beans>

也能夠根據須要配置dubbo:reference相關的屬性值,也能夠配置dubbo:method指定調用的方法的配置信息,詳細配置屬性能夠參考Dubbo官方文檔。

  • 部署與驗證

開發完成提供方服務後,在本地開發調試的時候能夠怎麼簡單怎麼作,若是是要部署到生產環境,則須要打包後進行部署,能夠參考下面的Maven POM配置:

01 <build>
02      <plugins>
03           <plugin>
04                <groupId>org.apache.maven.plugins</groupId>
05                <artifactId>maven-shade-plugin</artifactId>
06                <version>1.4</version>
07                <configuration>
08                     <createDependencyReducedPom>true</createDependencyReducedPom>
09                </configuration>
10                <executions>
11                     <execution>
12                          <phase>package</phase>
13                          <goals>
14                               <goal>shade</goal>
15                          </goals>
16                          <configuration>
17                               <transformers>
18                                    <transformerimplementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
19                                    <transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
20                                         <mainClass>org.shirdrn.dubbo.provider.ChatRoomClusterServer</mainClass>
21                                    </transformer>
22                               </transformers>
23                          </configuration>
24                     </execution>
25                </executions>
26           </plugin>
27      </plugins>
28 </build>

這裏也給出Maven POM依賴的簡單配置:

1 <dependencies>
2      <dependency>
3           <groupId>org.shirdrn.dubbo</groupId>
4           <artifactId>dubbo-api</artifactId>
5           <version>0.0.1-SNAPSHOT</version>
6      </dependency>
7 </dependencies>

咱們開發的服務應該是分佈式的,首先是經過配置內容來決定,例如設置集羣模式、設置負載均衡模式等,而後在部署的時候,能夠在多個節點上同一個服務,這樣多個服務都會註冊到Dubbo註冊中心,若是某個節點上的服務不可用了,能夠根據咱們配置的策略來選擇其餘節點上的可用服務,後面經過Dubbo服務管理中心和監控中心就能更加清楚明瞭。

Dubbo服務管理與監控

咱們須要在安裝好管理中心和監控中心之後,再將上面的開發的提供方服務部署到物理節點上,而後就可以經過管理中心和監控中心來查看對應的詳細狀況。

  • Dubbo服務管理中心

安裝Dubbo服務管理中心,須要選擇一個Web容器,咱們使用Tomcat服務器。首先下載Dubbo管理中心安裝文件dubbo-admin-2.5.3.war,或者直接從源碼構建獲得該WAR文件。這裏,咱們已經構建好對應的WAR文件,而後進行安裝,執行以下命令:

1 cd apache-tomcat-6.0.35
2 rm -rf webapps/ROOT
3 unzip ~/dubbo-admin-2.5.3.war -d webapps/ROOT

修改配置文件~/apache-tomcat-6.0.35/webapps/ROOT/WEB-INF/dubbo.properties,指定咱們的註冊中心地址以及登陸密碼,內容以下所示:

1 dubbo.registry.address=zookeeper://zk1:2181?backup=zk2:2181,zk3:2181
2 dubbo.admin.root.password=root
3 dubbo.admin.guest.password=guest

而後,根據須要修改~/apache-tomcat-6.0.35/conf/server.xml配置文件,主要是Tomcat HTTP 端口號(我這裏使用8083端口),完成後能夠直接啓動Tomcat服務器:

1 cd ~/apache-tomcat-6.0.35/
2 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連接,示例如圖所示:

上圖能夠看到服務地址:

1 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的內容,以下所示:

01 dubbo.container=log4j,spring,registry,jetty
02 dubbo.application.name=simple-monitor
03 dubbo.application.owner=
04 dubbo.registry.address=zookeeper://zk1:2181?backup=zk2:2181,zk3:2181
05 dubbo.protocol.port=7070
06 dubbo.jetty.port=8087
07 dubbo.jetty.directory=${user.home}/monitor
08 dubbo.charts.directory=${dubbo.jetty.directory}/charts
09 dubbo.statistics.directory=${user.home}/monitor/statistics
10 dubbo.log4j.file=logs/dubbo-monitor-simple.log
11 dubbo.log4j.level=WARN

而後啓動簡易監控中心,執行以下命令:

1 cd ~/dubbo-monitor-simple-2.5.3
2 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接口定義以下所示:

01 /**
02 * MonitorFactory. (SPI, Singleton, ThreadSafe)
03 *
04 * @author william.liangf
05 */
06 @SPI("dubbo")
07 public interface MonitorFactory {
08     
09     /**
10      * Create monitor.
11      * @param url
12      * @return monitor
13      */
14     @Adaptive("protocol")
15     Monitor getMonitor(URL url);
16  
17 }

Monitor接口定義以下所示:

1 /**
2 * Monitor. (SPI, Prototype, ThreadSafe)
3 *
4 * @see com.alibaba.dubbo.monitor.MonitorFactory#getMonitor(com.alibaba.dubbo.common.URL)
5 * @author william.liangf
6 */
7 public interface Monitor extends Node, MonitorService {
8  
9 }

具體定義內容能夠查看MonitorService接口,再也不累述。

總結

Dubbo還提供了其餘不少高級特性,如路由規則、參數回調、服務分組、服務降級等等,並且不少特性在給出內置實現的基礎上,還給出了擴展的接口,咱們能夠給出自定義的實現,很是方便並且強大。更多能夠參考Dubbo官網用戶手冊和開發手冊。

附錄:Dubbo使用Maven構建依賴配置

01 <properties>
02     <spring.version>3.2.8.RELEASE</spring.version>
03     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
04 </properties>
05  
06 <dependencies>
07     <dependency>
08         <groupId>com.alibaba</groupId>
09         <artifactId>dubbo</artifactId>
10         <version>2.5.3</version>
11         <exclusions>
12             <exclusion>
13                 <groupId>org.springframework</groupId>
14                 <artifactId>spring</artifactId>
15             </exclusion>
16             <exclusion>
17                 <groupId>org.apache.zookeeper</groupId>
18                 <artifactId>zookeeper</artifactId>
19             </exclusion>
20             <exclusion>
21                 <groupId>org.jboss.netty</groupId>
22                 <artifactId>netty</artifactId>
23             </exclusion>
24         </exclusions>
25     </dependency>
26     <dependency>
27         <groupId>org.springframework</groupId>
28         <artifactId>spring-core</artifactId>
29         <version>${spring.version}</version>
30     </dependency>
31     <dependency>
32         <groupId>org.springframework</groupId>
33         <artifactId>spring-beans</artifactId>
34         <version>${spring.version}</version>
35     </dependency>
36     <dependency>
37         <groupId>org.springframework</groupId>
38         <artifactId>spring-context</artifactId>
39         <version>${spring.version}</version>
40     </dependency>
41     <dependency>
42         <groupId>org.springframework</groupId>
43         <artifactId>spring-context-support</artifactId>
44         <version>${spring.version}</version>
45     </dependency>
46     <dependency>
47         <groupId>org.springframework</groupId>
48         <artifactId>spring-web</artifactId>
49         <version>${spring.version}</version>
50     </dependency>
51  
52     <dependency>
53         <groupId>org.slf4j</groupId>
54         <artifactId>slf4j-api</artifactId>
55         <version>1.6.2</version>
56     </dependency>
57     <dependency>
58         <groupId>log4j</groupId>
59         <artifactId>log4j</artifactId>
60         <version>1.2.16</version>
61     </dependency>
62     <dependency>
63         <groupId>org.javassist</groupId>
64         <artifactId>javassist</artifactId>
65         <version>3.15.0-GA</version>
66     </dependency>
67     <dependency>
68         <groupId>com.alibaba</groupId>
69         <artifactId>hessian-lite</artifactId>
70         <version>3.2.1-fixed-2</version>
71     </dependency>
72     <dependency>
73         <groupId>com.alibaba</groupId>
74         <artifactId>fastjson</artifactId>
75         <version>1.1.8</version>
76     </dependency>
77     <dependency>
78         <groupId>org.jvnet.sorcerer</groupId>
79         <artifactId>sorcerer-javac</artifactId>
80         <version>0.8</version>
81     </dependency>
82     <dependency>
83         <groupId>org.apache.zookeeper</groupId>
84         <artifactId>zookeeper</artifactId>
85         <version>3.4.5</version>
86     </dependency>
87     <dependency>
88         <groupId>com.github.sgroschupf</groupId>
89         <artifactId>zkclient</artifactId>
90         <version>0.1</version>
91     </dependency>
92     <dependency>
93         <groupId>org.jboss.netty</groupId>
94         <artifactId>netty</artifactId>
95         <version>3.2.7.Final</version>
96     </dependency>
97 </dependencies>
相關文章
相關標籤/搜索