dubbo负载均衡(dubbo负载均衡 在客户端还是服务端)
本篇文章给大家谈谈dubbo负载均衡,以及dubbo负载均衡 在客户端还是服务端对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、dubbo之Cluster(容错)
- 2、什么是dubbo
- 3、dubbo和Eureka的区别
- 4、Dubbo的调用过程及工作原理
- 5、7.Dubbo远程调用(要配合下一篇一起看)
- 6、dubbo与nginx都可以做负载均衡,哪个相对来说更优秀?为什么?
dubbo之Cluster(容错)
在介绍dubbo的cluster之前,先来看一下cluster在dubbo整体设计中的位置。按照官网的说法,Cluster作为路由层, 封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,核心扩展接口为 Cluster , Directory , Router , LoadBalance ,接口间的依赖关系如下:
虚拟Invoker暴露流程程: Cluster = (Directory = Router) = LoadBalance = Invoker ,依照这个顺序,我们先来看Cluster。Cluster不属于核心层,目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。本文主要关注Cluster层的容错及其核心接口(LoadBalance在之前的文章已经做过介绍)。
先来看Cluster层中的Cluster接口,支持SPI扩展、自适应扩展,默认SPI实现空陆是FailOverCluster,核心只有一个join接口
比较好理解,把Directory中的所有原始Invoker合并成一个虚拟Invoker,虚拟Invoker是一系列通过合并原始Invoker,并在此基础上扩展带有容错机制的Invoker。以FailOverCluster为例,join返回FailoverClusterInvoker,具体的invoke逻辑由虚拟Invoker( FailoverClusterInboker )实现,构造方法( 这里以FailoverClsterInvoker为例,其他虚拟Invoker的构造方法大同小异 )通过继承父类AbstractClusterInvoker实现,只有一个Directory参数:
当前dubbo版本提供的虚拟Invoker主要有下面几种,下面来分别态宴介绍:
所有8种cluster中,除去AvailableCluster、MergeableCluster、BroadcastCluster,其他都需要根据LoadBalance选取可用Invoker,具体逻辑在AbstractClusterInvoker.select。先来看AbstractClusterInvoker的构造方法:
两个核心参数,Directory和URL,Directory本节先不做介绍,这里的URL是被调用服务的URL;availableCheck为了与服务的availableCheck做区分,这里的参数名是 cluster.availablecheck ;核心关注上面提到的select方法,先来看逻辑:
整体select方法都是为了尽可能保证每次选出斗闭顷的Invoker不重复,也就是说最大限度的保证负载均衡;doSelect方法在处理的时候,通过loadBalance选出的Invoker,还会对其进一步判断是否已被选中过,步骤如下:
doSelect方法中的loadbalance.select已经在LoadBalance部分做了分析,这里不再冗述,重点关注reSelect方法;先把备选Invoker中,未被选中过的Invoker过滤出来,优先从中选取可用Invoker,步骤如下:
Cluster层的容错主要通过几种常用的容错机制配合负载均衡,保证最终通过Cluster暴露可用的Invoker;而且,dubbo在保证Invoker可用性前提下,要求尽可能均衡负载,过程会多次执行负载均衡策略。
注:dubbo源码版本2.7.1,欢迎指正。
什么是dubbo
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调锋闭扒用的东东,说白了就是个远程服务调用的分布式框架(告别Web Service模式中的WSdl,以服务者与消费者的方式在dubbo上注册)。
Dubbo的核心部分包括:
1、远程通讯:提供银昌对多种基于长连接的NIO框架抽态中象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
2、集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
3、服务自动注册与发现:基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
Dubbo的作用
1、透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
2、软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
3、服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
[img]dubbo和Eureka的区别
要说两者的区别,必须提一下分布式架构中的CAP理论,即一个分布式框架,只能同橘桥耐时满足C一致性、A可用性、P网络分区容错性这三者中的两个,消销不可能同时兼备三者。
从这个角度上来看,Dubbo推荐的注册中心首选ZK,而ZK是一个满足CP的框架;Eureka由于其架构设计,更多专注于AP。
对于容错机制,Dubbo自身实现了多个错误处理方式,比如失败切换Failover、快速失败Failfast、失败安全Failsafe等,Eureka是借助于Spring Cloud中的熔断器Hytrix实现的容错。
对于负载均衡,Dubbo自身实现了多种负载均衡方式,比如随机权重、哈希一致性等,Eureka同样是将此功能外放,通过Ribbon等实现了负载均衡。
服务注册及发现,Dubbo自身封装了NettyClient等通讯工具,圆春而Eureka都是采用的应用层通讯HttpClient。
由此可以看出,微服务框架本身也是采用了领域拆分的设计理念,将相对独立的不同功能拆分成单独的模块,想用什么模块就组合什么模块。从这个角度上看,Dubbo更多的是提供了一个组合起来不可拆分的整体功能,而Eureka与其他组件则简单轻便的多。
Dubbo的调用过程及工作原理
节点角色说明:
1️⃣Container:服务运行容器。
2️⃣Provider:暴露服务的服务提供方。
3️⃣Consumer:调用远程服务的服务消费方。
4️⃣Registry:服务注册与发现的注册中心。
服务提供者先启动 start,然后注册 register 服务。消费订阅 subscribe 服务,如果没有订阅到自己想获得的服务,它会不断的尝试订阅。新的服务注册到注册中心以后,注册中心会将这些服务通过 notify 到消费者。
5️⃣Monitor:统计服务的调用次数和调用时间的弯陪监控中心。
这是一个监控,图中虚线表明 Consumer 和 Provider 通过异步的方式发送消息至 Monitor,Consumer 和 Provider 会将信息存放在本地磁盘,平均一分钟发送一次信息。Monitor 在整个架构中是可选的(图中的虚线并不是可选的意思),Monitor 功能需要单独配置,不配置或者配置以后,Monitor 挂掉并不会影响服务的调用。
①服务容器 Container 负责启动加载运行服务提供者 Provider。根据 Provider 配置的文件根据协议发布服务,完成服务的初始化。
②Provider 在启动时,根据配置中的 Registry 地址连接 Registry,将 Provider 的服务信息发布到 Registry,在 Registry 注册自己提供的服务。
③Consumer 在启动时,根据消指厅费者 XML 配置文件中的服务引用信息,连接到 Registry,向 Registry 订阅自己所需的服务。
④Registry 根据服务订阅关系,返回 Provider 地址列表给 Consumer。如果有变更,Registry 会基于长连接推送最新的服务地址信息埋逗蠢给 Consumer。
⑤Consumer 调用远程服务时,基于 软负载均衡算法 ,先从缓存的 Provider 地址列表中选择一台进行跨进程调用服务,假如调用失败,再重新选另一台调用。
⑥服务 Provider 和 Consumer,会在内存中记录调用次数和调用时间,每分钟发送一次统计数据到 Monitor。
7.Dubbo远程调用(要配合下一篇一起看)
如果我们手动写一个简单的RPC调用,一般需要把服务调用的信息传递给服务端,包括每次服务调用的一些共用信息包括服务调用接口、方法名、方法参数类型和方法参数值等,在传递方法参数值时需要先序列化对象并经过网络传输到服务端,在服务端接受后再按照客户端序列化的顺序再做一次反序列化,然后拼装成请求对象进行服务反射调用,最终将调用结果传给客户端。Dubbo的实现也基本是相同的原理,下图是Dubbo一次完整RPC调用中经过的步骤:
首先在客户端启动时,会从注册中心拉取和订阅对应的服务列表,Cluster会把拉取的服务列表聚合成一个Invoker,每次RPC调用前会通过Directory#list获取providers地址(已经生成好的Invoker地址),获取这些服务列表给后续路由和负载均衡使用。对应上图①中将多个服务提供者做聚合。在框架内部实现Directory接口的是RegistryDirectory类,它和接口名是一对一的关系(每一个接口都有一个RegistryDirectory实例),主要负责拉取和订阅服务提供者、动态配置和路由项。
在Dubbo发起服务调用时,所有路由和负载均衡都是在客户端实现的。客户端服务调用首先会触发路由操作,然后将路由结果得到的服务列表作为负载均衡参数,经过负载均衡后会选出一台机器进行RPC调用,这3个步骤一次对应图中②③④。客户端经过路由和负载均衡后,会将请求交给底层IO线程池(如Netty)进行处理,IO线程池主要处理读写、序列化和反序列化等逻辑,因此这里一定不能阻塞操作,Dubbo也提供参数控制(decode.in.io)参数,在处理反序列化对象时会在业务线程池中处理。在⑤中包含两种类似的线程池,一种是IO线程池(Netty),另一种是Dubbo业务线程池(承载业务方法调用)。
目前Dubbo将服务调用和Telnet调用做了端口复用,子啊编解码层面也做了适配。在Telnet调用时,会新建立一个TCP连接,传递接口、方法和json格式的参数进行服务调用,在编解码层面简单读取流中的字符串(因为不是Dubbo标准头报文),最终交给Telnet对应的Handler去解析方法调用。如果不是Telnet调用,则服务提供方会根据传递过来的接口、分组和版本信息查找Invoker对应的实例进行反射调用。在⑦中进行了端口复用,Telnet和正常RPC调用不一样的地方是序列化和反序列化使用的不是Hessian方式,而是直接使用fastjson进行处理。
讲解完主要调用原理,接下来开始探讨细节,比如Dubbo协议、编解码实现和线程模型等,本篇重点主要放在⑤⑥⑦。
Dubbo协议参考了现有型册轮卜信的TCP/IP协议,每一次RPC调用包括协议头和协议体两部分。16字节长的报文头部主要包含魔数(0xdabb),以及当前请求报文是否是Request、Response、心跳和事件的信息,请求时也会携带当前报文体内序列化协议编号。除此之外,报文头还携带了请求状态,以及请求唯一标识和报文体长度。
在消息体中,客户端严格按照序列化顺序写入消姿凯息,服务端也会遵循相同的顺序读取消息,客户端发起的请求消息体一次依次保存下列内容:Dubbo版本号、服务接口名、服务接口版本、方法名、参数类型、方法参数值和请求额外参数(attachment)。
服务端返回的响应消息体主要包含回值状态标记和返回值,其中回值状态标记包含6中:
我们知道在网络通信中(TCP)需要解决网络粘包/解包的问题,常用的方法比如用回车、换行、固定长度和特殊分隔符进行处理,而Dubbo是使用特殊符号0xdabb魔法数来分割处理粘包问题。
在实际场景中,客户端会使用多线程并发调用服务,Dubbo如何做到正确响应调用线程呢?关键在于协议头的全局请求id标识,先看原理图:
当客户端多个线程并发请求时,框架内部会调用DefaultFuture对象的get方法进行等待。在请求发起时,框架内部会创建Request对象,这时候会被分配一个唯一id,DefaultFuture可以从Request中获取id,并将关联关系存储到静态HashMap中,就是上图中的Futures集合。当客户端收到响应时,会根据Response对象中的id,从Futures集合中查找对应DefaultFuture对象,最终会唤醒对应的线程并通知结果。客户端也会启动一个定时扫描线程去探测超时没有返回的请求。
先了解一下编解码器的类关系图:
如上,AbstractCodec主要提供基础能力,比如校验报文长度和查找具体编解码器等。TransportCodec主要抽象编解码实现,自动帮我们去调用序列化、反序列化实现和自动cleanup流。我们通过Dubbo编解码继承结构可以清晰看到,DubboCodec继承自ExchageCodec,它又再次继承了TelnetCodec实现。我们前面说过Telnet实现复用了Dubbo协议端口,其实就是在这层编解码做了通用处理。因为流中可能包含多个RPC请求,Dubbo框架尝试一次性读取更多完整报文编解码生成对象,也就是图中的DubboCountCodec,它的实现思想比较简单,依次调用DubboCodec去解码,如果能解码成完整报文,则加入消息列表,然后触发下一个Handler方法调用。
编码器的作用是将Java对象转成字节流,主要分两部分,构造报文头部,和对消息体进行序列化处理。所有编辑码层实现都应该继承自ExchangeCodec,当Dubbo协议编码请求对象时,会调用ExchangeCodec#encode方法。我们来看下这个方法是如何对请求对象进行编码的:
如上,是Dubbo将请求对象转成字节流的过程,其中encodeRequestData方法是对RpcInvocation调用对象的编码,主要是对接口、方法、方法参数类型、方法参数等进行编码,在DubboCodec#encodeRequestData中对此方法进行了重写:
如上,响应编码与请求编码的逻辑基本大同小异,在编码出现异常时,会将异常响应返回给客户端,防止客户端只能一直等到超时。为了防止报错对象无法在客户端反序列化,在服务端会将异常信息转成字符串处理。对于响应体的编码,在DubboCodec#encodeResponseData方法中实现:
注意不管什么样的响应,都会先写入1个字节的标识符,具体的值和含义前面已经讲过。
解码相对更复杂一些,分为2部分,第一部分是解码报文的头部,第二部分是解码报文体内容并将其转换成RpcInvocation对象。我们先看服务端接受到请求后的解码过程,具体解码实现在ExchangeCodec#decode方法:
可以看出,解码过程中需要解决粘包和半包问题。接下来我们看一下DubboCodec对消息题解码的实现:
如上,如果默认配置在IO线程解码,直接调用decode方法;否则不做解码,延迟到业务线程池中解码。这里没有提到的是心跳和事件的解码,其实很简单,心跳报文是没有消息体的,事件又消息体,在使用Hessian2协议的情况下默认会传递字符R,当优雅停机时会通过发送readonly事件来通知客户端当前服务端不可用。
接下来,我们分析一下如何把消息体转换成RpcInvocation对象,具体在DecodeableRpcInvocation#decode方法中:
解码请求时,严格按照客户端写数据的顺序处理。
解码响应和解码请求类似,调用的同样是DubboCodec#decodeBody,就是上面省略的部分,这里就不赘述了,重点看下响应体的解码,即DecodeableRpcResult#decode方法:
如果读者熟悉Netty,就很容易理解Dubbo内部使用的ChannelHandler组件的原理,Dubbo内部使用了大量的Handler组成类似链表,依次处理具体逻辑,包括编解码、心跳时间戳和方法调用Handler等。因为Nettty每次创建Handler都会经过ChannelPipeline,大量的事件经过很多Pipeline会有较多开销,因此Dubbo会将多个Handler聚合成一个Handler。(个人表示,这简直bullshit)
Dubbo的Channelhandler有5中状态:
Dubbo针对每个特性都会实现对应的ChannelHandler,在讲解Handler的指责之前,我们Dubbo有哪些常用的Handler:
Dubbo提供了大量的Handler去承载特性和扩展,这些Handler最终会和底层通信框架做关联,比如Netty等。一次完整的RPC调用贯穿了一系列的Handler,如果直接挂载到底层通信框架(Netty),因为整个链路比较长,则需要大量链式查找和事件,不仅低效,而且浪费资源。
下图展示了同时具有入站和出站ChannelHandler的布局,如果一个入站事件被触发,比如连接或数据读取,那么它会从ChannelPipeline头部一直传播到ChannelPipeline的尾端。出站的IO事件将从ChannelPipeline最右边开始,然后向左传播。当然ChannelPipeline传播时,会检测入站的是否实现了ChannelInboundHandler,出站会检测是否实现了ChannelOutboundHandler,如果没有实现,则自动跳过。Dubbo框架中实现这两个接口类主要是NettyServerHandler和NettyClientHandler。Dubbo通过装饰者模式包装Handler,从而不需要将每个Handler都追加到Pipeline中。因此NettyServer和NettyClient中最多有3个Handler,分别是编码、解码和NettyHandler。
讲完Handler的流转机制后,我们再来探讨RPC调用Provider方处理Handler的逻辑,在DubboProtocol中通过内部类继承自ExchangeHandlerAdapter,完成服务提供方Invoker实例的查找并进行服务的真实调用。
如上是触发业务方法调用的关键,在服务暴露时服务端已经按照特定规则(端口、接口名、接口版本和接口分组)把实例Invoker存储到HashMap中,客户端调用过来时必须携带相同信息构造的key,找到对应Exporter(里面持有Invoker)然后调用。
我们先跟踪getInvoker的实现,会发现服务端唯一标识的服务由4部分组成:端口、接口名、接口版本和接口分组。
如上,Dispatcher是线程池的派发器。这里需要注意的是,Dispatcher真实的职责是创建有线程派发能力的ChannelHandler,比如AllChannelHandler、MessageOnlyChannelHandler和ExecutionChannelHanlder,其本身并不具备线程派发能力。
Dispatcher属于Dubbo中的扩展点,这个扩展点用来动态产生Handler,以满足不同的场景,目前Dubbo支持一下6种策略调用:
具体需要按照使用场景不同启用不同的策略,建议使用默认策略,如果在TCP连接中需要做安全或校验,则可以使用ConnectionOrderedDispatcher策略。如果引入新的线程池,则不可避免的导致额外的线程切换,用户可在Dubbo配置中指定dispatcher属性让具体策略生效。
在Dubbo内部,所有方法调用都被抽象成Request/Response,每次调用都会创建一个Request,如果是方法调用则返回一个Response对象。HeaderExceptionExchangeHandler就是用了处理这种场景,主要负责4中事情:
(1) 更新发送和读取请求时间戳。
(2) 判断请求格式或编解码是否有错,并响应客户端失败的具体原因。
(3) 处理Request请求和Response正常响应。
(4) 支持Telnet调用。
我们先来看一下HeaderExchangeHandler#received实现:
dubbo与nginx都可以做负载均衡,哪个相对来说更优秀?为什么?
dubbo的负载均衡已经是服务层面的了,和nginx的负载均衡还在http请求层面完全不同。至于二者哪个优秀,当然没办法直接比较,要硬要选一个的话就是nginx了。 111涉及到负载均衡就涉及到你的业务,根据业务来选择才是最适合的。
拓展:
1、Nginx ("engine x") 是一个高性能的HTTP和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许知陆可证的形式凯码发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。
2、盯猛哪Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。由俄罗斯的程序设计师Igor Sysoev所开发,供俄国大型的入口网站及搜索引擎Rambler(俄文:Рамблер)使用。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、新浪、网易、腾讯、 淘宝等。
关于dubbo负载均衡和dubbo负载均衡 在客户端还是服务端的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。