阿里云
发表主题 回复主题
  • 8424阅读
  • 7回复

[编程语言]基于Dubbo框架构建分布式服务

级别: 论坛版主
发帖
3410
云币
8770

上次只是简单的说了一下 淘宝SOA框架dubbo管理控制台安装和使用 ~YYnn7)  
}Hg G<.H>  
cQU/z"?+  
这里我们介绍一下基于Dubbo框架构建分布式服务 kKX' Y+  
xRh 22z  
XM$ ~HG  
@62T:Vl  
Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配置就能够实现分布式服务调用,也就是说服务提供方(Provider)发布的服务可以天然就是集群服务,比如,在实时性要求很高的应用场景下,可能希望来自消费方(Consumer)的调用响应时间最短,只需要选择Dubbo的Forking Cluster模式配置,就可以对一个调用请求并行发送到多台对等的提供方(Provider)服务所在的节点上,只选择最快一个返回响应的,然后将调用结果返回给服务消费方(Consumer),显然这种方式是以冗余服务为基础的,需要消耗更多的资源,但是能够满足高实时应用的需求。有关Dubbo服务框架的简单使用,可以参考我的其他两篇文章(《基于Dubbo的Hessian协议实现远程调用》,《基于Dubbo的Hessian协议实现远程调用》,后面参考链接中已给出链接),这里主要围绕Dubbo分布式服务相关配置的使用来说明与实践。 5ya9VZ5#  
DR#" 3  
]E`<8hRB  
Dubbo服务集群容错  .U1wVIM  
4E$MhP  
\[MAa:/  
假设我们使用的是单机模式的Dubbo服务,如果在服务提供方(Provider)发布服务以后,服务消费方(Consumer)发出一次调用请求,恰好这次由于网络问题调用失败,那么我们可以配置服务消费方重试策略,可能消费方第二次重试调用是成功的(重试策略只需要配置即可,重试过程是透明的);但是,如果服务提供方发布服务所在的节点发生故障,那么消费方再怎么重试调用都是失败的,所以我们需要采用集群容错模式,这样如果单个服务节点因故障无法提供服务,还可以根据配置的集群容错模式,调用其他可用的服务节点,这就提高了服务的可用性。 &.)=>2  
o@:"3s  
首先,根据Dubbo文档,我们引用文档提供的一个架构图以及各组件关系说明,如下所示:
oTw!#Re)  
上述各个组件之间的关系(引自Dubbo文档)说明如下:
  • }icCp)b>v  
    ●这里的Invoker是Provider的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息
  • yM_/_V|G  
    ●Directory代表多个Invoker,可以把它看成List ,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。
  • \nl(tU#j  
    ●Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。
  • j}+3+ 8D  
    ●Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等。
  • ;+Jx,{ )  
    ●LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选。
_7Y h[I4  
&jqaW 2  
我们也简单说明目前Dubbo支持的集群容错模式,每种模式适应特定的应用场景,可以根据实际需要进行选择。Dubbo内置支持如下6种集群模式: QS#@xhH  
  • KOcB#UHJ  
    ●Failover Cluster模式
\6b~$\~B  
配置值为failover。这种模式是Dubbo集群容错默认的模式选择,调用失败时,会自动切换,重新尝试调用其他节点上可用的服务。对于一些幂等性操作可以使用该模式,如读操作,因为每次调用的副作用是相同的,所以可以选择自动切换并重试调用,对调用者完全透明。可以看到,如果重试调用必然会带来响应端的延迟,如果出现大量的重试调用,可能说明我们的服务提供方发布的服务有问题,如网络延迟严重、硬件设备需要升级、程序算法非常耗时,等等,这就需要仔细检测排查了。例如,可以这样显式指定Failover模式,或者不配置则默认开启Failover模式,配置示例如下:
xl}rdnf}  
<dubbo:service interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService" version="1.0.0"          cluster="failover" retries="2" timeout="1000" ref="chatRoomOnlineUserCounterService" protocol="dubbo" >          <dubbo:method name="queryRoomUserCount" timeout="500" retries="2" />     </dubbo:service> 59E9K)c3  
vX+oZj   
上述配置使用Failover Cluster模式,如果调用失败一次,可以再次重试2次调用,服务级别调用超时时间为100ms,调用方法queryRoomUserCount的超时时间为80ms,允许重试两次,最坏情况调用花费时间160ms。如果该服务接口org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService还有其他的方法可供调用,则其他方法没有显式配置则会继承使用dubbo:service配置的属性值。
!cW rB9  
  • "zEl2Xn28_  
    ●Failfast Cluster模式
'Y?-."eKh  
配置值为failfast。这种模式称为快速失败模式,调用只执行一次,失败则立即报错。这种模式适用于非幂等性操作,每次调用的副作用是不同的,如写操作,比如交易系统我们要下订单,如果一次失败就应该让它失败,通常由服务消费方控制是否重新发起下订单操作请求(另一个新的订单)。 xR+vu>f  
  • ~1G^IZ6  
    ●Failsafe Cluster模式
egoR])2>  
配置值为failsafe。失败安全模式,如果调用失败, 则直接忽略失败的调用,而是要记录下失败的调用到日志文件,以便后续审计。 )%(ZFn}  
  • (.J/Ql0Y  
    ●Failback Cluster模式
xT*'p&ap  
配置值为failback。失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 {R1]tGOf  
  • #:)'D?,  
    ●Forking Cluster模式
qt8Y3:=8l  
配置值为forking。并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。 rj4@  
  • )_#V>cvNG  
    ●Broadcast Cluster模式
ni;_Un~  
配置值为broadcast。广播调用所有提供者,逐个调用,任意一台报错则报错(2.1.0开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。上面的6种模式都可以应用于生产环境,我们可以根据实际应用场景选择合适的集群容错模式。如果我们觉得Dubbo内置提供的几种集群容错模式都不能满足应用需要,也可以定制实现自己的集群容错模式,因为Dubbo框架给我提供的扩展的接口,只需要实现接口com.alibaba.dubbo.rpc.cluster.Cluster即可,接口定义如下所示: aV;|2}q "  
@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;} Jh"[ug  
l}nVWuD  
关于如何实现一个自定义的集群容错模式,可以参考Dubbo源码中内置支持的汲取你容错模式的实现,6种模式对应的实现类如下所示: U:7h>Z0W  
Yh<F-WOo2  
P{Nvt/%  
com.alibaba.dubbo.rpc.cluster.support.FailoverClustercom.alibaba.dubbo.rpc.cluster.support.FailfastClustercom.alibaba.dubbo.rpc.cluster.support.FailsafeClustercom.alibaba.dubbo.rpc.cluster.support.FailbackClustercom.alibaba.dubbo.rpc.cluster.support.ForkingClustercom.alibaba.dubbo.rpc.cluster.support.AvailableCluster 7oZ :/6_>  
ETDWG_H |  
可能我们初次接触Dubbo时,不知道如何在实际开发过程中使用Dubbo的集群模式,后面我们会以Failover Cluster模式为例开发我们的分布式应用,再进行详细的介绍。 Fis!MMh.$  
Y\ [|k-6  
N/ a4Gl(  
Dubbo服务负载均衡 ~Ztn(1N  
h+km?j  
g]~vZj  
Dubbo框架内置提供负载均衡的功能以及扩展接口,我们可以透明地扩展一个服务或服务集群,根据需要非常容易地增加/移除节点,提高服务的可伸缩性。Dubbo框架内置提供了4种负载均衡策略,如下所示: *i"9D:  
  • (HNc9QVC'W  
    ●Random LoadBalance:随机策略,配置值为random。可以设置权重,有利于充分利用服务器的资源,高配的可以设置权重大一些,低配的可以稍微小一些
  • ing'' _  
    ●RoundRobin LoadBalance:轮询策略,配置值为roundrobin。
  • yR$_ZXsd  
    ●LeastActive LoadBalance:配置值为leastactive。根据请求调用的次数计数,处理请求更慢的节点会受到更少的请求
  • YidcVlOsO  
    ●ConsistentHash LoadBalance:一致性Hash策略,具体配置方法可以参考Dubbo文档。相同调用参数的请求会发送到同一个服务提供方节点上,如果某个节点发生故障无法提供服务,则会基于一致性Hash算法映射到虚拟节点上(其他服务提供方)
_}[ Du/c  
5U 84 *RY  
在实际使用中,只需要选择合适的负载均衡策略值,配置即可,下面是上述四种负载均衡策略配置的示例: E,i^rAm  
sI,cX#h&Y  
k1Y\g'1  
<dubbo:service interface="org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService" version="1.0.0"          cluster="failover" retries="2" timeout="1000" loadbalance="random"          ref="chatRoomOnlineUserCounterService" protocol="dubbo" >          <dubbo:method name="queryRoomUserCount" timeout="500" retries="2" loadbalance="leastactive" />     </dubbo:service> jz!I +  
7D'\z IW  
上述配置,也体现了Dubbo配置的继承性特点,也就是dubbo:service元素配置了loadbalance=”random”,则该元素的子元素dubbo:method如果没有指定负载均衡策略,则默认为loadbalance=”random”,否则如果dubbo:method指定了loadbalance=”leastactive”,则使用子元素配置的负载均衡策略覆盖了父元素指定的策略(这里调用queryRoomUserCount方法使用leastactive负载均衡策略)。当然,Dubbo框架也提供了实现自定义负载均衡策略的接口,可以实现com.alibaba.dubbo.rpc.cluster.LoadBalance接口,接口定义如下所示: %. 1/ #{  
/*** 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;} 1W|jC   
.@H:P  
如何实现一个自定义负载均衡策略,可以参考Dubbo框架内置的实现,如下所示的3个实现类: /-M:6  
eWH0zswG  
4Kj.o  
com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalancecom.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalancecom.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance MlV(XG>'  
,_V V;P  
poz_=,c  
Dubbo服务集群容错实践 3kxo1eb  
x`6MAZ  
l b(  
手机应用是以聊天室为基础的,我们需要收集用户的操作行为,然后计算聊天室中在线人数,并实时在手机应用端显示人数,整个系统的架构如图所示:
>LBA0ynh {  
上图中,主要包括了两大主要流程:日志收集并实时处理流程、调用读取实时计算结果流程,我们使用基于Dubbo框架开发的服务来提供实时计算结果读取聊天人数的功能。上图中,实际上业务接口服务器集群也可以基于Dubbo框架构建服务,就看我们想要构建什么样的系统来满足我们的需要。 a_N7X  
*fN+wiPD  
如果不使用注册中心,服务消费方也能够直接调用服务提供方发布的服务,这样需要服务提供方将服务地址暴露给服务消费方,而且也无法使用监控中心的功能,这种方式成为直连。 fD!c t;UK  
GvVkb=="  
如果我们使用注册中心,服务提供方将服务发布到注册中心,而服务消费方可以通过注册中心订阅服务,接收服务提供方服务变更通知,这种方式可以隐藏服务提供方的细节,包括服务器地址等敏感信息,而服务消费方只能通过注册中心来获取到已注册的提供方服务,而不能直接跨过注册中心与服务提供方直接连接。这种方式的好处是还可以使用监控中心服务,能够对服务的调用情况进行监控分析,还能使用Dubbo服务管理中心,方便管理服务,我们在这里使用的是这种方式,也推荐使用这种方式。使用注册中心的Dubbo分布式服务相关组件结构,如下图所示:
0U=wGI O  
下面,开发部署我们的应用,通过如下4个步骤来完成: -XYvjW,|  
  • ;L-=z]IR,  
    ●服务接口定义
3?o4  
服务接口将服务提供方(Provider)和服务消费方(Consumer)连接起来,服务提供方实现接口中定义的服务,即给出服务的实现,而服务消费方负责调用服务。我们接口中给出了2个方法,一个是实时查询获取当前聊天室内人数,另一个是查询一天中某个/某些聊天室中在线人数峰值,接口定义如下所示: TPp]UG  
  1. 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);}
ysnW3q!@  
joY7Vk!<o  
x/,;:S  
,z&S;f.f  
接口是服务提供方和服务消费方公共遵守的协议,一般情况下是服务提供方将接口定义好后提供给服务消费方。 |~Dl<#58  
  • '#6e Ub  
    ●服务提供方
^s)`UZ<C=  
服务提供方实现接口中定义的服务,其实现和普通的服务没什么区别,我们的实现类为ChatRoomOnlineUserCounterServiceImpl,代码如下所示: ]p!{   
  1. package org.shirdrn.dubbo.provider.service;
  2. import java.util.List;
  3. import org.apache.commons.logging.Log;
  4. import org.apache.commons.logging.LogFactory;
  5. import org.shirdrn.dubbo.api.ChatRoomOnlineUserCounterService;
  6. import org.shirdrn.dubbo.common.utils.DateTimeUtils;
  7. import redis.clients.jedis.Jedis;
  8. import redis.clients.jedis.JedisPool;
  9. import com.alibaba.dubbo.common.utils.StringUtils;
  10. import com.google.common.base.Strings;
  11. import com.google.common.collect.Lists;
  12. public class ChatRoomOnlineUserCounterServiceImpl implements ChatRoomOnlineUserCounterService {
  13.      private static final Log LOG = LogFactory.getLog(ChatRoomOnlineUserCounterServiceImpl.class);
  14.      private JedisPool jedisPool;
  15.      private static final String KEY_USER_COUNT = "chat::room::play::user::cnt";
  16.      private static final String KEY_MAX_USER_COUNT_PREFIX = "chat::room::max::user::cnt::";
  17.      private static final String DF_YYYYMMDD = "yyyyMMdd";
  18.      public String queryRoomUserCount(String rooms) {
  19.           LOG.info("Params[Server|Recv|REQ] rooms=" + rooms);
  20.           StringBuffer builder = new StringBuffer();
  21.           if(!Strings.isNullOrEmpty(rooms)) {
  22.                Jedis jedis = null;
  23.                try {
  24.                     jedis = jedisPool.getResource();
  25.                     String[] fields = rooms.split(",");
  26.                     List<String> results = jedis.hmget(KEY_USER_COUNT, fields);
  27.                     builder.append(StringUtils.join(results, ","));
  28.                } catch (Exception e) {
  29.                     LOG.error("", e);
  30.                } finally {
  31.                     if(jedis != null) {
  32.                          jedis.close();
  33.                     }
  34.                }
  35.           }
  36.           LOG.info("Result[Server|Recv|RES] " + builder.toString());
  37.           return builder.toString();
  38.      }
  39.     
  40.      @Override
  41.      public List<String> getMaxOnlineUserCount(List<String> rooms, String date, String dateFormat) {
  42.           // HGETALL chat::room::max::user::cnt::20150326
  43.           LOG.info("Params[Server|Recv|REQ] rooms=" + rooms + ",date=" + date + ",dateFormat=" + dateFormat);
  44.           String whichDate = DateTimeUtils.format(date, dateFormat, DF_YYYYMMDD);
  45.           String key = KEY_MAX_USER_COUNT_PREFIX + whichDate;
  46.           StringBuffer builder = new StringBuffer();
  47.           if(rooms != null && !rooms.isEmpty()) {
  48.                Jedis jedis = null;
  49.                try {
  50.                     jedis = jedisPool.getResource();
  51.                     return jedis.hmget(key, rooms.toArray(new String[rooms.size()]));
  52.                } catch (Exception e) {
  53.                     LOG.error("", e);
  54.                } finally {
  55.                     if(jedis != null) {
  56.                          jedis.close();
  57.                     }
  58.                }
  59.           }
  60.           LOG.info("Result[Server|Recv|RES] " + builder.toString());
  61.           return Lists.newArrayList();
  62.      }
  63.     
  64.      public void setJedisPool(JedisPool jedisPool) {
  65.           this.jedisPool = jedisPool;
  66.      }
  67. }
Odwe1q&  
$zP5Hzx  
H07\z1?.K  
]N'3jf`W  
代码中通过读取Redis中数据来完成调用,逻辑比较简单。对应的Maven POM依赖配置,如下所示:   U9oUY> 9  
  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.           <dependency>
  8.                <groupId>org.shirdrn.dubbo</groupId>
  9.                <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.j boss.netty</groupId>
  24.                <artifactId>netty</artifactId>
  25.                <version>3.2.7.Final</version>
  26.           </dependency>
  27.      </dependencies>
/8GdCac  
%bnXZA2Sx  
;,bgJgK  
o/N!l]r  
有关对Dubbo框架的一些依赖,我们单独放到一个通用的Maven Module中(详见后面“附录:Dubbo使用Maven构建依赖配置”),这里不再多说。服务提供方实现,最关键的就是服务的配置,因为Dubbo基于Spring来管理配置和实例,所以通过配置可以指定服务是否是分布式服务,以及通过配置增加很多其它特性。我们的配置文件为provider-cluster.xml,内容如下所示: N{%7OG  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
  4.      xmlns:p="http://www.springframework.org/schema/p"
  5.      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  6.      http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
fo ~uI(rk  
]BO:*&O  
6ll!7U(9(  
[ 此帖被小柒2012在2016-05-24 15:14重新编辑 ]
级别: 论坛版主
发帖
4292
云币
2522

只看该作者 沙发  发表于: 2016-05-24
Java么?最近开始学Javal啦~
级别: 论坛版主
发帖
1783
云币
3325

只看该作者 板凳  发表于: 2016-05-24
阿里的分布式开源框架,不过我看着有点头疼!
本人不是云栖社区工作人员。
无论您在使用中遇到什么问题,不要出言不逊!谢谢合作!
级别: 论坛版主
发帖
3410
云币
8770

只看该作者 地板  发表于: 2016-05-26
回 1楼(西秦) 的帖子
恩恩 项目中使用 就了解了一下
级别: 论坛版主
发帖
3410
云币
8770

只看该作者 4楼 发表于: 2016-05-26
回 2楼(鬼才神兵) 的帖子
dan 不疼 就行
级别: 新人
发帖
2
云币
6
只看该作者 5楼 发表于: 2017-12-26
Re为什么在界面上的服务名不是ip的时候页面跳转失败?
请问一下这种情况的跳转是在原始设计上就不能跳转还是系统的一个bug?如果是我使用上有问题,请问一下什么地方可以找到使用文档呢?
级别: 架构狮
发帖
1217
云币
1936
只看该作者 6楼 发表于: 01-25
您的帖子很精彩!希望很快能再分享您的下一帖!
级别: 架构狮
发帖
1217
云币
1936
只看该作者 7楼 发表于: 01-25
发表主题 回复主题
« 返回列表上一主题下一主题

限100 字节
如果您在写长篇帖子又不马上发表,建议存为草稿
 
验证问题: ECS是阿里云提供的什么服务? 正确答案:云服务器
上一个 下一个