zoukankan      html  css  js  c++  java
  • 基于Dubbo框架构建分布式服务

    基于Dubbo框架构建分布式服务

    Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配置就能够实现分布式服务调用,也就是说服务提供方(Provider)发布的服务可以天然就是集群服务,比如,在实时性要求很高的应用场景下,可能希望来自消费方(Consumer)的调用响应时间最短,只需要选择Dubbo的Forking Cluster模式配置,就可以对一个调用请求并行发送到多台对等的提供方(Provider)服务所在的节点上,只选择最快一个返回响应的,然后将调用结果返回给服务消费方(Consumer),显然这种方式是以冗余服务为基础的,需要消耗更多的资源,但是能够满足高实时应用的需求。
    有关Dubbo服务框架的简单使用,可以参考我的其他两篇文章(《基于Dubbo的Hessian协议实现远程调用》,《Dubbo实现RPC调用使用入门》,后面参考链接中已给出链接),这里主要围绕Dubbo分布式服务相关配置的使用来说明与实践。

    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种集群模式:

    • Failover Cluster模式

    配置值为failover。这种模式是Dubbo集群容错默认的模式选择,调用失败时,会自动切换,重新尝试调用其他节点上可用的服务。对于一些幂等性操作可以使用该模式,如读操作,因为每次调用的副作用是相同的,所以可以选择自动切换并重试调用,对调用者完全透明。可以看到,如果重试调用必然会带来响应端的延迟,如果出现大量的重试调用,可能说明我们的服务提供方发布的服务有问题,如网络延迟严重、硬件设备需要升级、程序算法非常耗时,等等,这就需要仔细检测排查了。
    例如,可以这样显式指定Failover模式,或者不配置则默认开启Failover模式,配置示例如下:

    1
    2
    3
    4
    <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配置的属性值。

    • Failfast Cluster模式

    配置值为failfast。这种模式称为快速失败模式,调用只执行一次,失败则立即报错。这种模式适用于非幂等性操作,每次调用的副作用是不同的,如写操作,比如交易系统我们要下订单,如果一次失败就应该让它失败,通常由服务消费方控制是否重新发起下订单操作请求(另一个新的订单)。

    • Failsafe Cluster模式

    配置值为failsafe。失败安全模式,如果调用失败, 则直接忽略失败的调用,而是要记录下失败的调用到日志文件,以便后续审计。

    • Failback Cluster模式

    配置值为failback。失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

    • Forking Cluster模式

    配置值为forking。并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。

    • Broadcast Cluster模式

    配置值为broadcast。广播调用所有提供者,逐个调用,任意一台报错则报错(2.1.0开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。
    上面的6种模式都可以应用于生产环境,我们可以根据实际应用场景选择合适的集群容错模式。如果我们觉得Dubbo内置提供的几种集群容错模式都不能满足应用需要,也可以定制实现自己的集群容错模式,因为Dubbo框架给我提供的扩展的接口,只需要实现接口com.alibaba.dubbo.rpc.cluster.Cluster即可,接口定义如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    @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种模式对应的实现类如下所示:

    1
    2
    3
    4
    5
    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种负载均衡策略,如下所示:

    • Random LoadBalance:随机策略,配置值为random。可以设置权重,有利于充分利用服务器的资源,高配的可以设置权重大一些,低配的可以稍微小一些
    • RoundRobin LoadBalance:轮询策略,配置值为roundrobin。
    • LeastActive LoadBalance:配置值为leastactive。根据请求调用的次数计数,处理请求更慢的节点会受到更少的请求
    • ConsistentHash LoadBalance:一致性Hash策略,具体配置方法可以参考Dubbo文档。相同调用参数的请求会发送到同一个服务提供方节点上,如果某个节点发生故障无法提供服务,则会基于一致性Hash算法映射到虚拟节点上(其他服务提供方)

    在实际使用中,只需要选择合适的负载均衡策略值,配置即可,下面是上述四种负载均衡策略配置的示例:

    1
    2
    3
    4
    5
    <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接口,接口定义如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
    * 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个实现类:

    1
    2
    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个方法,一个是实时查询获取当前聊天室内人数,另一个是查询一天中某个/某些聊天室中在线人数峰值,接口定义如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    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,代码如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    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依赖配置,如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <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,内容如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    <?xml version="1.0" encoding="UTF-8"?>
     
     
         <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连接池。
    启动服务示例代码如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    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,如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    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容器,全部对象都交由容器来管理。

    • 服务消费方

    服务消费方就容易了,只需要知道注册中心地址,并引用服务提供方提供的接口,消费方调用服务实现如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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,内容如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8"?>
     
     
         <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配置:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <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依赖的简单配置:

    1
    2
    3
    4
    5
    6
    7
    <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文件,然后进行安装,执行如下命令:

    1
    2
    3
    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,指定我们的注册中心地址以及登录密码,内容如下所示:

    1
    2
    3
    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服务器:

    1
    2
    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链接,示例如图所示:

    上图可以看到服务地址:

    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
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    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

    然后启动简易监控中心,执行如下命令:

    1
    2
    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接口定义如下所示:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 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接口定义如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 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官网用户手册和开发手册。

    附录:Dubbo使用Maven构建依赖配置

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

    参考链接

    Creative Commons License
  • 相关阅读:
    [Leetcode] Merge Intervals
    [Leetcode] Sort Colors
    junit
    DBUnit的使用
    xml简介---来自百度百科
    今天开始深入学习XML
    Java 用Myeclipse部署项目基础坏境搭建
    properties配置文件读取方法
    Java web做服务器之间的通信方法
    Java Socket简单的客服端及其服务器端
  • 原文地址:https://www.cnblogs.com/williamjie/p/9112035.html
Copyright © 2011-2022 走看看