您现在的位置:首页 > >

Dubbo原理总结

发布时间:

Dubbo主要的节点角色及之间的关系

    主要的节点角色:

    Provider:暴露服务的服务提供者Consumer:调用远程服务的服务消费者Registry:服务注册与发现的注册中心Monitor:统计服务调用次数和调用时间的监控中心Container:服务运行容器节点角色之间的关系:

    服务容器负责加载、运行服务提供者。服务提供者在启动时,向注册中心注册自己提供的服务。服务消费者在启动时,向注册中心订阅自己所需的服务。注册中心返回服务提供者地址列表给服务消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。服务消费者,从服务提供者列表中,基于软负载均衡算法,选择其中一个提供者进行调用,如果调用失败,再基于失败策略进行重试或忽略。服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次给监控中心。Dubbo采用的nio异步的通信,通信协议默认为netty,当然也可以选择 mina、grizzy。在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点,处理消费者请求,返回处理后的消息给消费者,消费者使用服务时主要是订阅服务的节点,监听zookeeper节点目录,服务端的变化时zookeeper会推送给消费者,消费者重新缓存服务地址等。服务者、消费者、zookeeper三者之间都是长连接。

Dubbo的集群容错方案
    Failover:失败自动切换重试其他服务器,多用于读操作。可通过retries来设置重试次数。默认方案。Failfast:失败后立即报错返回,不进行重试,多用于幂等性的写操作,如新增记录。Failsafe:失败后直接忽略,不影响后续操作,多用于记录日志。Failback:失败后记录失败请求,定时重发,多用于消息通知。Forking:并行调用多个服务器,只要一个成功即返回,多用于实时性要求高的都操作。Broadcast:广播所有提供者,逐个调用,任意一台报错则报错,多用于通知所有提供者更新缓存货日志等本地资源信息。

Dubbo负载均衡策略
    随机负载均衡:先统计所有服务器上该接口方法的权重总和,然后对这个总和随机nextInt一下,看生成的随机数落到哪个段内,就调哪个服务器上的该服务。轮询负载均衡:如果所有服务器的该接口方法的权重一样,则直接内部的序列计数器(sequences)+1然后对服务器的数量进行取模来决定调用哪个服务器上的服务;如果服务器的该接口方法的权重不一样,则找到其中最大的权重,然后将内部的权重计数器(weightSequences)+1并对该最大权重数取模,然后再找出权重比该取模后的值大服务器列表,最后通过内部的序列计数器(sequences)+1然后对服务器列表数量进行取模来决定调用哪个服务器上的服务。缺点是存在慢的提供者堆积请求的问题。最少活跃负载均衡:每个接口和接口方法都对应一个RpcStatus对象,记录了他们的活跃数、失败数等等相关统计信息,此种负载均衡方式是在活跃数最低的服务器中对其权重的总和取模来看结果是在哪个权重段中,则选择该服务器来调用,开始调用时活跃数+1,调用结束时活跃数-1,所以活跃值越大,表明该提供者服务器的该接口方法耗时越长,而消费能力强的提供者接口往往活跃值很低。最少活跃负载均衡保证了“慢”提供者能接收到更少的服务器调用。一致哈希负载均衡:一致性哈希算法的负载均衡保证了同样的请求(参数)将会落到同一台服务器上,Dubbo中默认采用了160个虚拟节点,因为Dubbo的请求URL中除了我们使用的参数,还有些额外的系统调用参数,比如timestamp、loadbalance、pid和application等。源码可参考:http://blog.csdn.net/hdu09075340/article/details/53169461

多注册中心

指同一个服务消费者,可以连接两个不同的服务注册中心,不同的服务注册中心中可以提供同一个服务的不同实现版本。


服务容错性设计
    可以从两个方面思考,微观角度和宏观角度。从微观角度思考(单纯从服务出发):

    超时timeout(consumer端):保护服务,尽量让消费端保持原有的性能。超时时间的选取,一般根据业务的正常响应时间+一个缓冲时间。重试retry(consumer端):失败后重试,可以减少因为网络的偶尔抖动导致的失败。但重试对于一些类似新增的有幂等性要求的接口就必须注意。熔断(consumer端):当检测到某个接口超时或者异常超过一定阈值时进行熔断,对发起的请求直接短路掉,直接返回一个mock值。并定时发送心跳,正常了再恢复请求。主要用于避免对异常接口的堆积请求从而造成系统雪崩。限流(provider端):防止流量突增压垮系统。根据consumer的重要程度以及*时的QPS大小,来给 每个consumer设置一个流量上限,同一时间只对某个consumer提供N个线程支持,超过则等待或拒绝。主要措施:随机拒绝请求、拒绝低级别用户调用,根据白名单或黑名单规则拒绝特定用户请求,利用线程池队列排队处理调用等。降级(provider端):主要措施:部分功能禁用、增加功能的缓存时间、使用本地缓存而不是调用外部服务、减少某些业务特性以降低业务复杂度、异常时采用默认数据或兜底数据,同步变异步调用,减少JOB执行频率或变更业务峰值JOB触发调用时间,利用nginx+lua对某些页面请求进行过滤。从宏观角度思考(从集群环境出发):

    超时timeout:比如服务调用路径为A->B->C,那么服务A的超时时间应该设置大于服务B自身的运行时间加上B调用C耗费的时间总和,即TAB>RB+TBC。重试retry:跟超时的情况差不多。调用路径也是A->B->C,当C超时,B进行重试时,如果按照前面的公式则会没有考虑B->C重试的时间,导致B重试后获取到结果,但A却超时了。所以公式应该考虑重试的次数N在里面,修改为TAB>RB+TBC*N。另外,还需要考虑服务成本的大小,比如B耗费成本很昂贵,但C成本很低,那么对B就尽量少进行重试,而C可以多重试几次。熔断:A->B->C,如果服务C出现问题导致B进行熔断了,那么A就可以不用熔断。限流:B只允许A以QPS<=5的流量请求,而C却只允许B以QPS<=3的QPS请求,那么B给A的设定就有点大,上游的设置依赖下游。而且限流对QPS的配置,可能会随着服务加减机器而变化,最好是能在集群层面配置,自动根据集群大小调整。降级:对优先级低的接口进行降级、如果服务链路没有性能特别差的点,若流量激增则从外到内依次降级、如果服务检测到自身负载上升,则可以对自身进行降级。

Dubbo的降级配置

在消费者对调用服务的配置中有个mock的参数配置,mock只在出现非业务异常(比如超时,网络异常等)时执行。mock的配置支持两种,一种为boolean值,默认的为false。如果配置为true,则缺省使用mock类名,即类名+Mock后缀;另外一种则是配置”return null”,可以很简单的忽略掉异常。


Dubbo的限流(并发)配置
    provider端:

    executes:限制所有消费端对某个服务或某个方法的总并发请求数。actives:限制每个消费端对某个服务或某个方法的并发执行请求数。accepts:限制服务端所能接受的最大连接数。配置在provider或protocol上。connections:限制每个消费端对某个服务或某个方法所能建立的长连接数。consumer端:

    actives:限制每个消费端对某个服务或某个方法的并发执行个数。connections:限制每个消费端对某个服务或某个方法所能建立的长连接数。两边都有配置时,以消费端的为准。

服务限流模式
    常见的限流模式有限制并发的数量、限制并发的速率,限制单位时间窗口内的请求数量。限制并发的数量:属于一种较常见的限流手段,在实际应用中可以通过信号量机制(如Java中的Semaphore)来实现。Semaphore的acquire会阻塞直到获取信号量,tryAcquire则不会阻塞,获取不到直接返回false。限制并发的速率:主要有令牌桶和漏桶两种算法,但一般使用令牌桶,可以使用Guava中的Ratelimiter来实现。可参考:常用限流算法:令牌桶和漏桶算法解析限制单位时间窗口内的请求数:根据具体业务,确定单位时间周期,用AtomicLong来进行计数。

Dubbo监控中心Monitor
    Monitor挂掉不会影响Consumer和Provider之间的调用,所以用于生产环境不会有风险。配置好了可以结合admin管理后台使用,可以清晰的看到服务的访问记录、成功次数、失败次数等。Monitor采用磁盘存储统计信息,请注意安装机器的磁盘限制,如果要集群,建议用mount共享磁盘。

Dubbo管理后台Admin

主要功能包含提供者、路由规则、动态配置、访问控制、权重调节、负载均衡、负责人等管理功能。


Dubbo异步调用的实现方式
    NIO Future主动获取结果,返回结果放在RpcContext中。需要注意的是,由于RpcContext是单例模式,所以每次调用完后,需要保存一个Future实例。通过回调(Callback)参数。Callback并不是dubbo内部类或接口,而是由应用自定义的、实现了Serializable的接口。事件通知(推荐)。消费端定义一个“通知者”的Spring Bean,指定方法的onreturn和onthrow事件action就可以。只管发送请求,忽略结果值。

    只把消息放到发送队列中消息发送出去后,不关心结果值具体实现参照:Dubbo异步调用的方式及配置异步调用这里有个BUG:Dubbo异步方法调用里有个坑

Dubbo缓存策略
    lru基于最*最少使用原则删除多余缓存,保持最热的数据被缓存。threadlocal当前线程缓存,比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。jcache与JSR107集成,可以桥接各种缓存实现。

Dubbo定义的元素注册到Spring容器的流程
    Spring提供了可扩展Schema的支持,可以为系统提供可配置化支持。完成一个自定义配置一般需要以下步骤:

    设计配置属性和JavaBean编写XSD文件编写NamespaceHandler和BeanDefinitionParser完成解析工作编写spring.handlers和spring.schemas串联起所有部件。文件放在META-INF文件夹下在Bean文件中应用基本原理:NamespaceHandler会根据schema和节点名找到某个BeanDefinitionParser,然后由BeanDefinitionParser完成具体的解析工作。Spring提供了默认实现类NamespaceHandlerSupport和AbstractSingleBeanDefinitionParser。DubboNamespaceHandler实现了父类中的init方法,并定义了自己的DubboBeanDefinitionParser。最终将其转换成BeanDefinition并注册到spring容器中,提供spring容器实例化使用。详情参考:Dubbo定义的元素注册到Spring容器的原理解析

Dubbo源码的结构概述
    Dubbo模块之间的依赖关系:

    dubbo.registry?>dbubbo.cluster?>dubbo.rpc?>dubbo.remoting?>dubbo.commondubbo.monitor?>dubbo.rpc?>dubbo.remoting?>dubbo.commondubbo.container?>dubbo.config?>dubbo.rpc?>dubbo.remoting?>dubbo.common模块的主要功能:

    dubbo-common:公共逻辑模块,包括Util类和通用模型。 dubbo-remoting:远程通讯模块,相当于Dubbo协议的实现,如果RPC用RMI协议,则不需要使用此包。 dubbo-rpc:远程调用模块,抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。 dubbo-cluster:集群模块,将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。 dubbo-registry:注册中心模块,基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。 dubbo-monitor:监控模块,统计服务调用次数,调用时间的,调用链跟踪的服务。 dubbo-config:配置模块,是Dubbo对外的API,用户通过Config使用Dubbo,隐藏Dubbo所有细节。 dubbo-container:容器模块,是一个Standlone的容器,以简单的Main加载Spring启动,因为服务通常不需要Tomcat/JBoss等Web容器的特性,没必要用Web容器去加载服务。

Dubbo服务暴露的原理和流程
    Dubbo服务提供方的JavaBean对应的就是ServiceBean。ServiceBean除了继承dubbo自己的配置抽象类以外,还实现了一系列的spring接口用来参与到spring容器的启动以及bean的创建过程中去。由于ServiceBean是单例模式的,在Spring的容器启动过程中会预先初始化。最后finishRefresh会触发ContextRefreshedEvent事件,而ServiceBean实现了ApplicationListener接口监听了此事件,所以ServiceBean的onApplicationEvent(ApplicationEvent event)方法被触发,在这个方法中触发了export方法来暴露服务。服务暴露的流程:

    对解析完的配置做一次检查,所有的检查通过之后,会调用ServiceConfig#doExportUrls方法,因为dubbo支持多通信协议时,都进行暴露。将所有的配置转化成map,然后将map转化成dubbo的统一URL,最终暴露的dubbo服务也就是这个统一的url,这个url也会注册到zookeeper的节点上。根据获取的统一URL从代理工厂ProxyFactory获取代理Invoker(使用jdk的动态代理)。调用RegistryProtocol#export(invoker),准备对服务进行暴露。(这里主要做了两点:开启netty服务端和创建zookeeper服务节点)。

    调用DubboProtocol#export开启netty服务监听。调用zodoRegister的doRegister创建zookeeper的服务节点。消费者在消费服务时会根据消费的接口名找到对应的zookeeper节点目录,对目录进行监听,接收推送。
    3.详细解析参考:Dubbo暴露服务源码解析

Dubbo服务调用的原理和流程
    Dubbo服务消费方的JavaBean对应的就是ReferenceBean。容器启动的时候对所有的配置进行检查,检查通过后将所有配置转化成map,然后根据map创建代理类缓存起来。服务调用的流程:

    获取远程调用的方法名和参数,构建远程调用对象RpcInvocation。判断是否配置了mock,走相应的分支(容错性)。通过目录服务查找所有订阅的服务提供者的Invoker对象,路由服务根据策略来过滤选择调用的Invokers列表。通过负载均衡策略LoadBanlance来选择一个Invoker。执行选择的Invoker.invoker(invocation),经过监听器链(默认没有),经过过滤器链,内置实现了很多,执行到远程调用的DubboInvoker。判断远程调用的类型是同步、异步还是oneway模式,然后ExchangeClient发起远程调用。获取调用结果(oneway返回空RpcResult;异步直接返回空RpcResult,ResponseFuture回调;同步则ResponseFuture模式异步转同步,等待响应返回)。详细解析参考:http://blog.csdn.net/hdu09075340/article/details/71078633

Dubbo消息的发送和接收解析

https://yq.aliyun.com/articles/97522
http://blog.csdn.net/youaremoon/article/details/51520144


Dubbo注册原理

http://blog.csdn.net/hdu09075340/article/details/71123605


实现Dubbo服务环境隔离
    服务提供者只订阅不发布,然后服务消费者进行直连。对服务接口进行分组,然后提供者和消费者分别针对不同环境设置对应的分组。具体参考:实现DUBBO服务环境隔离

Dubbo通信原理(单一长连接和NIO异步通讯)
    消费者一个线程调用远程接口,生成一个唯一的ID(比如一段随机字符串,UUID等),Dubbo是使用AtomicLong从0开始累计数字的。将方法调用信息(如调用的接口名称,方法名称,参数值列表等)和处理结果的回调对象callback(即ResponseFuture对象),全部封装在一起组成一个对象object。向专门存放调用信息的全局ConcurrentHashMap里面put(ID, object)。将ID和打包的方法调用信息封装成一对象connRequest,通过Netty异步发送出去。当前线程再使用callback的get()方法试图获取远程返回的结果,在get()内部,则使用lock.lock()获取回调对象callback的锁, 再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。服务端接收到请求并处理后,将结果(此结果中包含了前面的ID,即回传)发送给客户端,客户端socket连接上专门监听消息的线程收到消息,分析结果,取到ID,再从前面的ConcurrentHashMap里面get(ID),从而找到callback,将方法调用结果设置到callback对象里。监听线程接着使用lock.lock()获取回调对象callback的锁,再调用signal()(类似notifyAll())方法唤醒前面处于等待状态的线程继续执行。至此,整个过程结束详情参考:Dubbo并发通信原理解析

为什么Dubbo不要传输大文件

因dubbo协议采用单一长连接,如果发送数据太大,会导致传输时间过长,一直阻塞其他消息的发送,导致吞吐量下降。如果每次请求的数据包大小为500KByte,假设网络为千兆网卡(1024Mbit=128MByte),每条连接最大7MByte(不同的环境可能不一样,供参考),单个服务提供者的TPS(每秒处理事务数)最大为:128MByte / 500KByte = 262。单个消费者调用单个服务提供者的TPS(每秒处理事务数)最大为:7MByte / 500KByte = 14。如果能接受,可以考虑使用,否则网络将成为瓶颈。


警惕Dubbo因超时和重试引起的系统雪崩
    超时时间一定要设置,要根据业务场景而定,设置太短容易引发重试,设置太长容易引发请求堆积。超时设置过短的后果:Dubbo默认失败重试2次,所以假设有1000个并发,都超时了,则会重试2次,变成3000个请求,导致请求流量翻了3倍。重试设置:写服务一般要考虑幂等性,所以失败后不进行重试。

分布式调用跟踪系统的实现原理(依据Google的Dapper论文)
    Trace表示一次请求的完整调用链追踪。Span用于体现服务与服务之间的一次调用,所以多个服务之间调用,Span就能体现具体的依赖关系。每一次请求都会分配一个全局唯一的TraceID,整个调用链中所有的Span都会获取到同一个TraceID。一般采集的数据信息包括TraceID、SpanID和ParentSpanID、RpcContext中包含的数据信息、服务执行异常时的堆栈信息、以及Trace中各个Span过程的开始和结束时间,这些数据信息都是需要在Trace的上下文信息中进行传递的。

基于Dubbo实现分布式调用跟踪系统方案
    目前主流的跟踪系统有:淘宝的EagleEye,嵌入HSF框架、Twitter的Zipkin,嵌入Finagle框架。Dubbo提供了专门用于拦截RPC请求的Filter接口,不过目前Dubbo的Filter并没有纳入Spring的IOC容器管理,所以配置可以查看文档或书pg26。主要的类:由Invocation派生可以传递当前Trace上下文信息的RpcInvocation和临时状态记录器RpcContext执行流程:

    当服务调用方向服务提供方发起RPC请求时,Filter会对服务调用方法进行拦截,然后试图从ThreadLocal中获取当前线程的Trace上下文信息,如果不存在则说明是根调用,需要生成TraceID,然后将生成的TraceID、SpanID设置在Invocation中传递给服务提供方,接着执行前置数据上报(开始时间维度为Client Send Time)。当调用Invoker接口的invoke()方法执行完远程服务方法后,再执行后置数据收集上报(结束时间维度为Clinet Receive Time)。当RPC请求到达服务提供方后,Filter会对其拦截,然后从Invocation中获取由服务调用方传递过来的Trace上下文信息,并将其存储到当前线程的ThreadLocal中,然后执行前置数据收集上报(开始时间维度为Server Receive Time)。当调用Invoker接口的invoke()方法执行完服务方法后,再执行后置数据收集上报(结束时间维度为Server Send Time),最后还要删除存储在ThreadLocal中当前线程的Trace上下文信息。收集到的信息通过发送消息队列,再异步持久化到数据库中。数据量大的放Hbase,量不大的可以放mysql即可,不过需要定时清除。当并发量太大时,全跟踪记录损耗较大,可以只对其中一些请求进行采样跟踪(比如1/1000,1000个请求才记录1个),采样率可以配置在配置中心动态更改。


热文推荐
猜你喜欢
友情链接: 医学资料大全 农林牧渔 幼儿教育心得 小学教育 中学 高中 职业教育 成人教育 大学资料 求职职场 职场文档 总结汇报