dubbo学习过程、使用经验分享及实现原理简单介绍,dubbo经验分享

1、前言

整理这篇文章差很少花了两天半时间,请尊重劳动成果,如转载请注明出处http://blog.csdn.net/hzzhoushaoyu/article/details/43273099git

2、什么是dubbo

Dubbo是阿里巴巴提供的开源的SOA服务化治理的技术框架,听说只是剖出来的一部分开源的,但一些基本的需求已经能够知足的,并且扩展性也很是好(至今没领悟到扩展性怎么作到的),经过spring bean的方式管理配置及实例,较容易上手且对应用无侵入。更多介绍可戳http://alibaba.github.io/dubbo-doc-static/Home-zh.htm。github

3、如何使用dubbo

1.服务化应用基本框架

如上图所示,一个抽象出来的基本框架,consumer和provider是框架中必然存在的,Registry作为全局配置信息管理模块,推荐生产环境使用Registry,可实时推送现存活的服务提供者,Monitor通常用于监控和统计RPC调用状况、成功率、失败率等状况,让开发及运维了解线上运行状况。web

应用执行过程大体以下:redis

  • 服务提供者启动,根据协议信息绑定到配置的IP和端口上,若是已有服务绑定过相同IP和端口的则跳过
  • 注册服务信息至注册中心
  • 客户端启动,根据接口和协议信息订阅注册中心中注册的服务,注册中心将存活的服务地址通知到客户端,当有服务信息变动时客户端能够经过定时通知获得变动信息
  • 在客户端须要调用服务时,从内存中拿到上次通知的全部存活服务地址,根据路由信息和负载均衡机制选择最终调用的服务地址,发起调用
  • 经过filter分别在客户端发送请求前和服务端接收请求后,经过异步记录一些须要的信息传递到monitor作监控或者统计

2.服务接口定义

通常单独有一个jar包,维护服务接口定义、RPC参数类型、RPC返回类型、接口异常、接口用到的常量,该jar包中不处理任何业务逻辑。spring

好比命名api-0.1.jar,在api-0.1.jar中定义接口数据库

 

public interface UserService
{
    public RpcResponseDto isValidUser(RpcAccountRequestDto requestDto) throws new RpcBusinessException, RpcSystemException;
}


并在api-0.1.jar中定义RpcResponseDto,RpcAccountRequestDto,RpcBusinessException,RpcSystemException。api

 

服务端经过引用该jar包实现接口并暴露服务,客户端引用该jar包引用接口的代理实例。缓存

3.注册中心

开源的dubbo已支持4种组件做为注册中心,咱们部门使用推荐的zookeeper作为注册中心,因为就瓶颈来讲不会出如今注册中心,风险较低,未作特别的研究或比较。服务器

  • zookeeper,推荐集群中部署奇数个节点,因为zookeeper挂掉一半的机器集群就不可用,因此部署4台和3台的集群都是在挂掉2台后集群不可用
  • redis
  • multicast,广播受到网络结构的影响,通常本地不想搭注册中心的话使用这种调用
  • dubbo简易注册中心

对于zookeeper客户端,dubbo在2.2.0以后默认使用zkclient,2.3.0以后提供可选配置Curator,提到这个点的缘由主要是由于zkclient发现一些问题:①服务器在修改服务器时间后zkClient会抛出日志错误之类的异常而后容器(咱们使用resin)挂掉了,也不能肯定就是zkClient的问题,接入dubbo以前无该问题②dubbo使用zkclient不传入链接zookeeper等待超时时间,使用默认的Integer.MAX_VALUE,这样在zookeeper连不上的状况下不报错也没法启动;目前咱们准备寻找其余解决方案,好比使用curator试下,还没正式投入。网络

4.服务端

配置应用名

 

<dubbo:application name="test"/>


配置dubbo注解识别处理器,不指定包名的话会在spring bean中查找对应实例的类配置了dubbo注解的

 

 

<dubbo:annotation/>

 

配置注册中心,经过group指定注册中心分组,可经过register配置是否注册到该注册中心以及subscribe配置是否从该注册中心订阅

 

<dubbo:registry address="zookeeper://127.0.0.1:2181/" group="test"/>

配置服务协议,多网卡可经过IP指定绑定的IP地址,不指定或者指定非法IP的状况下会绑定在0.0.0.0,使用Dubbo协议的服务会在初始化时创建长链接

 

 

<dubbo:protocol name="dubbo" port="20880" accesslog="d:/access.log"></dubbo:protocol>

经过xml配置文件配置服务暴露,首先要有个spring bean实例(不管是注解配置的仍是配置文件配置的),在下面ref中指定bean实例ID,做为服务实现类

 

 

<dubbo:service interface="com.web.foo.service.FirstDubboService" ref="firstDubboServiceImpl" version="1.0"></dubbo:service>

经过注解方式配置服务暴露,Component是Spring bean注解,Service是dubbo的注解(不要和spring bean的service注解弄混),如前文所述,dubbo注解只会在spring bean中被识别

@Component
@Service(version="1.0")
public class FirstDubboServiceImpl implements FirstDubboService
{

	@Override
	public void sayHello(TestDto test)
	{
		System.out.println("Hello World!");
	}

}

 

5.客户端

同服务端配置应用名、注解识别处理器和注册中心。 
配置客户端reference bean。客户端跟服务端不一样的是客户端这边没有实际的实现类的,因此配置的dubbo:reference实际会生成一个spring bean实例,做为代理处理Dubbo请求,而后其余要调用处直接使用spring bean的方式使用这个实例便可。

 

xml配置文件配置方式,id即为spring bean的id,以后不管是在spring配置中使用ref="firstDubboService"仍是经过@Autowired注解都OK

 

<dubbo:reference interface="com.web.foo.service.FirstDubboService"
		version="1.0" id="firstDubboService" ></dubbo:reference>

 

另外开发、测试环境可经过指定Url方式绕过注册中心直连指定的服务地址,避免注册中心中服务过多,启动创建链接时间过长,如

 

<dubbo:reference interface="com.web.foo.service.FirstDubboService"
		version="1.0" id="firstDubboService" url="dubbo://127.0.0.1:20880/"></dubbo:reference>

 

注解配置方式引用,

 

@Component
public class Consumer
{
	@Reference(version="1.0")
	private FirstDubboService service;
	
	public void test()
	{
		TestDto test = new TestDto();
		test.setList(Arrays.asList(new String[]{"a", "b"}));
		test.setTest("t");
		service.sayHello(test);
	}
}

Reference被识别的条件是spring bean实例对应的当前类中的field,如上是直接修饰spring bean当前类中的属性

 

这个地方看了下源码,本应该支持当前类和父类中的public set方法,可是看起来是个BUG,Dubbo处理reference处部分源码以下

 

Method[] methods = bean.getClass().getMethods();
        for (Method method : methods) {
            String name = method.getName();
            if (name.length() > 3 && name.startsWith("set")
                    && method.getParameterTypes().length == 1
                    && Modifier.isPublic(method.getModifiers())
                    && ! Modifier.isStatic(method.getModifiers())) {
                try {
                	Reference reference = method.getAnnotation(Reference.class);
                	if (reference != null) {
	                	Object value = refer(reference, method.getParameterTypes()[0]);
	                	if (value != null) {
	                		method.invoke(bean, new Object[] {  });//??这里不是应该把value做为参数调用么,并且为何上面if条件判断参数为1这里不传参数
	                	}
                	}
                } catch (Throwable e) {
                    logger.error("Failed to init remote service reference at method " + name + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
                }
            }
        }

 

6.监控中心

若是使用Dubbo自带的监控中心,可经过简单配置便可,先经过github得到dubbo-monitor的源码,部署启动后在应用配置以下

<dubbo:monitor protocol="registry" /> <!--经过注册中心获取monitor地址后创建链接-->

 

<dubbo:monitor address="dubbo://127.0.0.1:7070/com.alibaba.dubbo.monitor.MonitorService" /> <!--绕过注册中心直连monitor,同consumer直连-->

 

7.服务路由

最重要辅助功能之一,可随时配置路由规则调整客户端调用策略,目前dubbo-admin中已提供基本路由规则的配置UI,到github下载源码部署后很容易找到地方,这里简单介绍下怎么用路由。 下面是dubbo-admin的新建路由界面,可配置信息都在图片中有, 好比如今咱们有10.0.0.1~3三台消费者和10.0.0.4~6三台服务提供者,想让1和2调用4,3调用5和6的话,则能够配置两个规则, 1.消费者IP:10.0.0.1,10.0.0.2 ;提供者IP:10.0.0.4 2.消费者IP:10.0.0.3;提供者IP:10.0.0.5,10.0.0.6 另外,IP地址支持结尾为*匹配全部,如10.0.0.*或者10.0.*等。 不匹配的配置规则和匹配的配置规则是一致的。 
配置完成后可在消费者标签页查看路由结果 

8.负载均衡

dubbo提供4种负载均衡方式:

  • Random,随机,按权重配置随机几率,调用量越大分布越均匀,默认是这种方式
  • RoundRobin,轮询,按权重设置轮询比例,若是存在比较慢的机器容易在这台机器的请求阻塞较多
  • LeastActive,最少活跃调用数,不支持权重,只能根据自动识别的活跃数分配,不能灵活调配
  • ConsistentHash,一致性hash,对相同参数的请求路由到一个服务提供者上,若是有相似灰度发布需求可采用

dubbo的负载均衡机制是在客户端调用时经过内存中的服务方信息及配置的负责均衡策略选择,若是对本身系统没有一个全面认知,建议先采用random方式。

9.dubbo过滤器

有须要本身实现dubbo过滤器的,可关注以下步骤: 以下是dubbo rpc access log的过滤器,仅对服务提供方有效,且参数中须要带accesslog,也就是配置protocol或者serivce时配置的accesslog="d:/rpc_access.log"

@Activate(group = Constants.PROVIDER, value = Constants.ACCESS_LOG_KEY)
public class AccessLogFilter implements Filter {
}

 

10.其余特性

http://alibaba.github.io/dubbo-doc-static/User+Guide-zh.htm#UserGuide-zh-%3Cdubbo%3Amonitor%2F%3E

可关注以上连接内容,dubbo提供较多的辅助功能特性,大多目前咱们暂时未使用到,后续咱们这边关注到的两个特性可能会再引进来使用:

  • 结果缓存,免得本身再去写一个缓存,对缓存没有特殊要求的话直接使用dubbo的好了
  • 分组合并,对RPC接口不一样的实现方式分别调用而后合并结果的一种调用模式,好比咱们要查用户是否合法,一种咱们要查是否在黑名单,同时咱们还要关注登陆信息是否异常,而后合并结果

4、前车可鉴

这个主要是在整个学习及使用过程当中记录的,以及一些同事在初识过程问过个人,这边作了整理而后直接列举在下面:

1.服务版本号

  • 引用只会找相应版本的服务

 

<dubbo:serviceinterface=“com.xxx.XxxService” ref=“xxxService” version=“1.0” />
<dubbo:referenceid=“xxxService” interface=“com.xxx.XxxService” version=“1.0”/>

 

  • 为了从此更换接口定义发布在线时,可不停机发布,使用版本号

 

2.暴露一个内网一个外网IP问题

为了在测试环境提供一个内网访问的地址和一个办公区访问的地址。

•增长一个指定IP为内网地址的服务协议 •增长一个不指定IP的服务协议,可是在/etc/hosts中hostname对应的IP要为外网IP 
上面这种方案是一开始使用的方案,后面发现dubbo在启动过程不管是否配路由仍是会一个个去链接,虽然不影响启动,可是因为存在超时因此会影响启动时间,并且每台机器还得特别配置指定IP,后面使用另一套方案:

 

 

使用这种方式须要注意作好防火墙控制等,好比在线默认也是不指定IP,会绑定在0.0.0.0,若是非法人员知道调用的外网IP和端口,并且能够直接访问就麻烦了(若是在应用中作IP拦截也成,须要注意有防范措施)。 

 

 

3.dubbo reference注解问题

前文介绍使用时已经提到过,@Reference只能在spring bean实例对应的当前类中使用,暂时没法在父类使用;若是确实要在父类声明一个引用,可经过配置文件配置dubbo:reference,而后在须要引用的地方跟引用spring bean同样就行 

4.服务超时问题

目前若是存在超时,状况基本都在以下几点:

  • 客户端耗时大,也就是超时异常时的client elapsed xxx,这个是从建立Future对象开始到使用channel发出请求的这段时间,中间没有复杂操做,只要CPU没问题基本不会出现大耗时,顶多1ms属于正常
  • IOThread繁忙,默认状况下,dubbo协议一个客户端与一个服务提供者会创建一个共享长链接,若是某个客户端处于特别繁忙并且一直往一个服务提供者塞请求,可能形成IOThread阻塞,通常很是特殊的状况才会出现
  • 服务端工做线程池中线程所有繁忙,接收消息后塞入队列等待,若是等待时间比预想长会引发超时
  • 网络抖动,若是上述状况都排除了,还出如今请求发出后,服务接收请求前超过预想时间,只能归类到网络抖动了,须要SA一块儿查看问题
  • 服务自身耗时大,这个须要应用自身作好耗时统计,当出现这种状况的时候须要用数据来讲明问题及规划优化方案,建议采用缓存埋点的方式统计服务中各个执行阶段的耗时状况,最终若是超过预想时间则把缓存统计的耗时状况打日志,减小日志量,且可以获得更明确的信息

如今咱们应用使用过程当中发现两种类型的耗时,一种咱们目前只能归类到网络抖动,后续须要找运维一块儿关注这个问题,另一种是因为一些历史缘由,数据库查询容易发生抖动,总有一个时间点会忽然多出不少超时。 

5.服务保护

服务保护的原则上是避免发生相似雪崩效应,尽可能将异常控制在服务周围,不要扩散开。 说到雪崩效应,还得提下dubbo自身的重试机制,默认3次,当失败时会进行重试,这样在某个时间点出现性能问题,而后调用方再连续重复调用,很容易引发雪崩,建议的话仍是很据业务状况规划好如何进行异常处理,什么时候进行重试。 服务保护的话,目前咱们主要从如下几个方面来实施,也不成熟,还在摸索:

  • 考虑服务的dubbo线程池类型(fix线程池的话考虑线程池大小)、数据库链接池、dubbo链接数限制是否都合适 
  • 考虑服务超时时间和重试的关系,设置合适的值 
  • 必定时间内服务异常数较大,则可考虑使用failfast让客户端请求直接返回或者让客户端再也不请求 

经领导推荐,还在学习Release it,后续有其余想法,再回头来编辑。 

6.zkclient的问题

前文已经提到过zkclient有两个问题,修改服务器时间会致使容器挂掉;dubbo使用zkclient没有传超时时间致使zookeeper没法链接的时候,直接阻塞Integer.MAX_VALUE。 正在调研curator,目前只能说curator不会在没法链接的时候直接阻塞。 另外zkclient和curator的jar包应该都是jdk1.6编译的,因此系统还在jdk1.5如下的话没法使用。 

7.注册中心的分组group和服务的不一样实现group

这两个东西彻底不一样的概念,使用的时候不要弄混了。 registry上能够配置group,用于区分不一样分组的注册中心,好比在同一个注册中心下,有一部分注册信息是要给开发环境用的,有一部分注册信息时要给测试环境用的,能够分别用不一样的group区分开,目前对这个理解还不透彻,大体就是用于区分不一样环境。 service和reference上也能够配置group,这个用于区分同一个接口的不一样实现,只有在reference上指定与service相同的group才会被发现,还有前文提到的分组合并结果也是用的这个。 

5、dubbo如何工做的

其实dubbo整个框架内容并不算大,仔细看的话可能最多两天看完一遍,可是目前仍是没领悟到怎么作到的扩展性,学习深度还不够~ 要学习dubbo源码的话,必需要拿出官方高清大图才行。 
这张图看起来挺复杂的样子,真正拆分以后对照源码来看会发现很是清晰、简单直观。

1.如何跟进源码

入口就是各类dubbo配置项的解析,<dubbo:xxx />都是spring namespace,能够看到dubbo jar包下META-INF里面的spring.handlers,自定义的spring namespace处理器。 对于spring不太熟的同窗能够先了解下这个功能,入口都在这里,解析成功后每一个<dubbo:xxx />配置项都对应一个spring实例。

2.服务提供者

首先把这张图拆分红三块,首先是服务端剖去网络传输模块,也就是大图中的右上角。 

这里主要抽几个主要的类,从服务初始化到接收消息的流程简单说明下,有兴趣的再对照源码看下会比较清晰。

  • ServiceBean

继承ServiceConfig,作为服务配置管理和配置信息校验,每个dubbo:service配置或者注解都会对应生成一个ServiceBean的实例,维护当前服务的配置信息,并把一些全局配置塞入到该服务配置中。 另外ServiceBean自己是一个InitializingBean,在afterPropertiesSet时经过配置信息引导服务绑定和注册。 能够留意到ServiceBean还实现了ApplicationListener,在所有spring bean加载完成后判断是否延迟加载的逻辑。

  • ProtocolFilterWrapper

通过serviceBean引导后进入该类,这个地方注意下,Protocol使用的装饰模式,叶子只有DubboProtocol和RegistryProtocol,在中间调用中会绕来绕去,并且registry会走一遍这个流程,而后在RegistryProtocol中暴露服务再走一遍,注意每一个类的做用,不要被绕昏了就行,第一次跟进代码的时候没留意就晕头转向的。 在这以前其实还有个ProtocolListenerWrapper,封装监听器,在服务暴露后通知到监听器,没有复杂逻辑,若是没特殊需求能够先绕过。 再来讲ProtocolFIlterWrapper,这个类的做用就是串联filter调用链,若是有看过struts或者spring mvc拦截器源码的应该不会陌生。

  • RegistryProtocol

注册中心协议,若是配置了注册中心地址,每次服务暴露确定首先引导进入这个类中,若是没有注册中心链接则会先建立链接,而后再引导真正的服务协议暴露流程,会再走一次ProtocolFilterWrapper的流程(此次引导到的叶子是DubboProtocol)。 在服务暴露返回后,会再执行服务信息的注册和订阅操做。

  • DubboProtocol

这个类的export相对较简单,就是引导服务bind server socket。 另外该类还提供了一个内部类,用于处理接收请求,就是下面要提到的ExchangeHandler。

  • DubboProtocol$ExchangeHandler

接收反序列化好的请求消息,而后根据请求信息找到执行链,将请求再丢入执行链,让其最终执行到实现类再将执行结果返回即整个过程完成。 

3.客户端

客户端模块与服务端模块比较相似,只是恰好反过来,一个是暴露服务,一个是引用服务,而后客户端多出路由和负载均衡。 

  • ReferenceBean

继承ReferenceConfig,维护配置信息和配置信息的校验,该功能与ServiceBean相似 其自己还实现了FactoryBean,做为实例工厂,建立远程调用代理类;并且若是不指定为init的reference都是在首次getBean的时候调用到该factoryBean的getObject才进行初始化 另外实现了InitializingBean,在初始化过程当中引导配置信息初始化和构建init的代理实例

  • InvokerInvocationHandler

看到这个类名应该就知道是动态代理的handler,这里做为远程调用代理类的处理器在客户端调用接口时引导进入invoker调用链

  • ProtocolFIlterWrapper

与Service那边的功能相似,构建调用链

  • RegistryProtocol

与service那边相似,若是与注册中心尚未链接则创建链接,以后注册和订阅,再根据配置的策略返回相应的clusterInvoker 比service那边有个隐藏较深的逻辑须要留意的,就是订阅过程,RegistryDirectory做为订阅监听器,在订阅完成后会通知到RegistryDirectory,而后会刷新invoker,进入引导至DubboProtocol的流程,与变动的service创建长链接,第一次发生订阅时就会同步接收到通知并将已存在的service存到字典

  • DubboProtocol

在订阅过程当中发现有service变动则会引导至这里,与服务创建长链接,整个过程为了获得串联执行链Invoker

  • ClusterInvoker

ClusterInvoker由RegistryProtocol构建完成后,内部封装了Directory,在调用时会从Directory列举存活的service对应的Invoker,Directory做为被通知对象,在service有变动时也会及时获得通知 调用时在集群中发现存在多节点的话都会经过clusterInvoker来根据配置抉择最终调用的节点,包括路由方式、负载均衡等 dubbo自己支持的节点调用策略包括好比failoverClusterInvoker在失败时进行重试其余节点,failfastClusterInvoker在失败时返回异常,mergeableClusterInvoker则是对多个实现结果进行合并的等等不少

  • DubboInvoker

承接上层的调用信息,做为调用结构的叶子,将信息传递到exchange层,主要用来和echange交互的功能模块

4.网络传输层

从exchange往下都是算网络传输,包括作序列化、反序列化,使用Netty等IO框架发送接收消息等逻辑,先前看的时候没有作统一梳理,后续有机会再来编辑吧。 

相关文章
相关标签/搜索