Dubbo负载均衡

Dubbo负载均衡,第1张

在分布式集群架构下,负载均衡很重要。集群本来就是为了分担压力,负载均衡做的不好,就会失去了集群的意义。

1按照权重随机分配

按照权重随机分配,即是不均等随机事件。比如一块不均匀的硬币,字面30%概率,花面70%概率。这种就是不均等的随机事件。

从数学上看,即是一个区间0-10,然后均等随机产生0-10的随机数。然后在这个区间上划分,0-3,3-6,6-10分别把这个三个区间看做三个随机事件,那么这个三个随机事件的概率即是30%,30%,40%。

列子:

权重分别为[2,4,8],权重和为14,那么前面三个权重为2,4,8的三个事件就对应[0,2,6,14]这个三个区间。比如6-14表示权重为8的随机事件的概率为8/14。所以当产生一个随机数时,通过遍历权重数组,减等,当小于0时,他就落在那个权重事件上。比如:随机数5落在2-6之间,2-6对应的是权重为4这个事件,所以他属于权重为4的这个随机事件。

2轮询

当多线程出现时,使用原子类的整数去取莫轮询节点。

注意:sequences是成员变量,每次调用函数所有的权重都回归最初。

3Hash方式

使用某种hash算法,同一请求总是会hash到同一台机子上。

传统的hash算法,存在当hash区间变化时,同样的值hash后的位置不一样了。而一致性hash算法把请求,节点都hash后,放到一个圆环上,按照顺时针转动到的第一个节点为结果。这样就减少了结果的变化。还可以通过增加虚拟节点的方式均衡hash后的概率问题,当然增加节点需要交叉增加。

1怎么保证服务器少的情况下,hash的结果变化不大。

把消费者,提供者都去hash,hash的结果映射到一个环上。然后,要判断的那个消费者访问那个提供者的时候,进行顺时针的转动。遇到的第一个提供者节点就是。

2怎么保证概率的问题

交叉的防止虚拟节点,只要节点够多,那就近似是想等的。

详见: 一致性hash详解释

4最少访问原则

如果有多台机子的最少活跃数相同,在这几个中使用第一种按权重随机的方式

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

比如:同样是进行了10个请求,在一分钟内,A只处理了两个,B处理了5个。那么A的机会就更少,那么就会保证的系统整体的速度。

项目发布后,发现生产上面dubbo的线程池迅速被耗尽,查看日志看到如下信息:

RejectedExecutionException是dubbo provide线程池的拒绝策略(详情见:引入dubbo的实战记录),默认200大小的线程池被占满了,随后我们在这段日志的上面发现了另外一段日志:

发现这个>

在前面的一篇中分析了Dubbo是如何降级的,除了降级,有时限流也是一种很有效的解决高并发的性能问题,那在本篇中开始分析Dubbo是如何限流的。我们知道限流主要是通过控制连接数来实现的,防止某一片段内请求处理过大,导致重要服务的失效。

服务端连接控制

限制当前提供者在使用dubbo协议最多接受10个消费者链接

或者

并发控制

限制 comfooBarService 的每个方法,服务端并发执行(或占用线程池线程数)不能超过10个:

限制 comfooBarService 的 sayHello 方法,服务器并发执行(或占用线程池线程数)不能超过10个。

actives限流

该限流方式与前两种不同,其可以设置在提供端,也可以设置在消费者端。可以设置为接口级别,也可以设置为方法级别。

根据消费者与提供者建立的连接类型,其意义也不同。

长连接 : 表示当前的长连接最多可以处理的请求个数。与长连接的数量没有问题。

短连接 :表示当前服务可以同时处理的短连接数量。

类级别

方法级别

connections限流

可以设置在提供端,也可以设置在消费者端。限定连接的个数。对于短连接,和actives相同。但对于长连接,表示长连接的个数。

一般情况下,会使connections与actives联用,让connections限制长连接的个数,让actives限制长连接中可以处理的请求个数。

限制客户端服务使用连接不能超过10个

如果 <dubbo:service> 和 <dubbo:reference> 都配置了connections, <dubbo:reference> 优先。

延迟连接

延迟连接仅可以设置在消费者端,并且不能设置为方法级别。仅作用于Dubbo服务暴露协议。将长连接的建立推迟到消费者真正调用提供者时。 可以减少长连接的数量。

我们已经讲解了如何设置控制链接数的,那么它们底层是如何实现的呢?

实际上上面的逻辑都是一个个Filter,所有的Filter会连接成一个过滤器链,每次请求都会经过整个链路中的每一个Filter。那它是在什么时候构造成一个过滤器链的呢。

在服务暴露的时候会调用 buildInvokerChain , 将真正执行的 invoker 放到过滤链的尾部,再执行 protocolexpert(buildInvokerChain(invoker, )) 方法来进行服务暴露。

在服务引用的时候会调用 protocolrefer() 方法先生成 Invoker ,再调用 buildInvokerChain(protocolrefer(type, url), ) 来生成消费类型的调用链。

ExecuteLimitFilter

它用于限制每个服务中每个方法的最大并发数,有接口级别和方法级别的配置方式。

其基本原理:在框架中使用一个ConcurrentMap缓存了并发数的计数器,为每个请求URL生成一个IdentityString,并以此为key;再将每个IdentityString生成一个RpcStatus对象,将此作为value。RpcStatus对象用于记录对应的并发数。在调用开始之前,会通过URL获得RpcStatus对象,把对象中的并发数计数器原子+1,在finally中再将原子减1。只要在计数器+1的时候,发现当前计数器比设置的并发数大时,就会抛出异常。

TpsLimitFilter

TpsLimitFilter的限流是基于令牌的,即一段时间内只分配N个令牌,每次请求都会消耗一个令牌,耗完为止,后面再来的请求都会被拒绝。

具体的逻辑是在 DefaultTPSLimiter#isAllowable ,会用这个方法判断是否触发限流。

在DefaultTPSLimiter内部用一个ConcurrentHashMap缓存每个接口的令牌数,key是interface+group+version,value是一个StatItem对象,它包装了令牌刷新时间间隔、每次发放的令牌数等。首先判断当前时间减去上次发放令牌的时间是否超过了时间间隔,超过了就重新发放令牌,之前剩余的令牌会被直接覆盖掉。然后,通过CAS的方式减去1令牌,减掉后小于0就会触发限流。

ActiveLimitFilter

和服务提供者的 ExecuteLimitFilter 相似,它是消费者端的过滤器,限制的是客户端的并发量。

但是它与 ExecuteLimitFilter 有所不同,它不会直接抛出异常。而是当到达阈值的时候,会先加锁抢占当前接口的RpcStatus对象,然后通过wait方法进行等待,等待是有时间的,因为请求是有 timeout 属性的。然后如果某个Invoker在调用结束后,并发把计数器减-1并触发一个notify,此时会有一个在wait状态的线程被唤醒并继续执行,判断现在是否超时,如果超时则抛出异常。如果当前并发数仍然超出阈值,则继续执行wait方法;如果没有超出阈值在,则跳出循环,CAS+1,并调用invoke方法,调用结束后CAS-1,最后通过notify唤醒另外一个线程。

参考文章:

Dubbo之限流TpsLimitFilter源码分析

Dubbo服务限流

Dubbo源码分析----过滤器之ActiveLimitFilter

一、Dubbo整体架构

1、Dubbo与Spring的整合

Dubbo在使用上可以做到非常简单,不管是Provider还是Consumer都可以通过Spring的配置文件进行配置,配置完之后,就可以像使用spring

bean一样进行服务暴露和调用了,完全看不到dubbo

api的存在。这是因为dubbo使用了spring提供的可扩展Schema自定义配置支持。在spring配置文件中,可以像、这样进行配置。META-INF下的springhandlers文件中指定了dubbo的xml解析类:DubboNamespaceHandler。像前面的被解析成ServiceConfig,被解析成ReferenceConfig等等。

2、jdk spi扩展

由于Dubbo是开源框架,必须要提供很多的可扩展点。Dubbo是通过扩展jdk

spi机制来实现可扩展的。具体来说,就是在META-INF目录下,放置文件名为接口全称,文件中为key、value键值对,value为具体实现类的全类名,key为标志值。由于dubbo使用了url总线的设计,即很多参数通过URL对象来传递,在实际中,具体要用到哪个值,可以通过url中的参数值来指定。

Dubbo对spi的扩展是通过ExtensionLoader来实现的,查看ExtensionLoader的源码,可以看到Dubbo对jdk spi做了三个方面的扩展:

(1)jdk spi仅仅通过接口类名获取所有实现,而ExtensionLoader则通过接口类名和key值获取一个实现;

(2)Adaptive实现,就是生成一个代理类,这样就可以根据实际调用时的一些参数动态决定要调用的类了。

(3)自动包装实现,这种实现的类一般是自动激活的,常用于包装类,比如Protocol的两个实现类:ProtocolFilterWrapper、ProtocolListenerWrapper。

3、url总线设计

Dubbo为了使得各层解耦,采用了url总线的设计。我们通常的设计会把层与层之间的交互参数做成Model,这样层与层之间沟通成本比较大,扩展起来也比较麻烦。因此,Dubbo把各层之间的通信都采用url的形式。比如,注册中心启动时,参数的url为:

registry://0000:9090codec=registry&transporter=netty

这就表示当前是注册中心,绑定到所有ip,端口是9090,解析器类型是registry,使用的底层网络通信框架是netty。

二、Dubbo启动过程

Dubbo分为注册中心、服务提供者(provider)、服务消费者(consumer)三个部分。

1、注册中心启动过程

注册中心的启动过程,主要看两个类:RegistrySynchronizer、RegistryReceiver,两个类的初始化方法都是start。

RegistrySynchronizer的start方法:

(1)把所有配置信息load到内存;

(2)把当前注册中心信息保存到数据库;

(3)启动5个定时器。

5个定时器的功能是:

(1)AutoRedirectTask,自动重定向定时器。默认1小时运行1次。如果当前注册中心的连接数高于平均值的12倍,则将多出来的连接数重定向到其他注册中心上,以达到注册中心集群的连接数均衡。

(2)DirtyCheckTask,脏数据检查定时器。作用是:分别检查缓存provider、数据库provider、缓存consumer、数据库consumer的数据,清除脏数据;清理不存活的provider和consumer数据;对于缓存中的存在的provider或consumer而数据库不存在,重新注册和订阅。

(3)ChangedClearTask,changes变更表的定时清理任务。作用是读取changes表,清除过期数据。

(4)AlivedCheckTask,注册中心存活状态定时检查,会定时更新registries表的expire字段,用以判断注册中心的存活状态。如果有新的注册中心,发送同步消息,将当前所有注册中心的地址通知到所有客户端。

(5)ChangedCheckTask,变更检查定时器。检查changes表的变更,检查类型包括:参数覆盖变更、路由变更、服务消费者变更、权重变更、负载均衡变更。

RegistryReceiver的start方法:启动注册中心服务。默认使用netty框架,绑定本机的9090端口。最后启动服务的过程是在NettyServer来完成的。接收消息时,抛开dubbo协议的解码器,调用类的顺序是

NettyHandler-》NettyServer-》MultiMessageHandler-》HeartbeatHandler-》AllDispatcher-》

DecodeHandler-》HeaderExchangeHandler-》RegistryReceiver-》RegistryValidator-》RegistryFailover-》RegistryExecutor。

2、provider启动过程

provider的启动过程是从ServiceConfig的export方法开始进行的,具体步骤是:

(1)进行本地jvm的暴露,不开放任何端口,以提供injvm这种形式的调用,这种调用只是本地调用,不涉及进程间通信。

(2)调用RegistryProtocol的export。

(3)调用DubboProtocol的export,默认开启20880端口,用以提供接收consumer的远程调用服务。

(4)通过新建RemoteRegistry来建立与注册中心的连接。

(5)将服务地址注册到注册中心。

(6)去注册中心订阅自己的服务。

3、consumer启动过程

consumer的启动过程是通过ReferenceConfig的get方法进行的,具体步骤是:

(1)通过新建RemoteRegistry来建立与注册中心的连接。

(2)新建RegistryDirectory并向注册中心订阅服务,RegistryDirectory用以维护注册中心获取的服务相关信息。

(3)创建代理类,发起consumer远程调用时,实际调用的是InvokerInvocationHandler。

三、实际调用过程

consumer端发起调用时,实际调用经过的类是:

1、consumer:

InvokerInvocationHandler-》MockClusterInvoker(如果配置了Mock,则直接调用本地Mock类)-》FailoverClusterInvoker(负载均衡,容错机制,默认在发生错误的情况下,进行两次重试)-》RegistryDirectory$InvokerDelegete-》ConsumerContextFilter-》FutureFilter->DubboInvoker

2、provider:

NettyServer-》MultiMessageHandler-》HeartbeatHandler-》AllDispatcher-》DecodeHandler-》HeaderExchangeHandler-》DubboProtocolrequestHandler-》EchoFilter-》ClassLoaderFilter-》GenericFilter-》ContextFilter-》ExceptionFilter-》TimeoutFilter-》MonitorFilter-》TraceFilter-》实际service。

四、Dubbo使用的设计模式

1、工厂模式

ServiceConfig中有个字段,代码是这样的:

private static final Protocol protocol = ExtensionLoadergetExtensionLoader(Protocolclass)getAdaptiveExtension();

Dubbo里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了jdk

spi的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在classpath下增加个文件就可以了,代码零侵入。另外,像上面的Adaptive实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。

2、装饰器模式

Dubbo在启动和调用阶段都大量使用了装饰器模式。以Provider提供的调用链为例,具体的调用链代码是在ProtocolFilterWrapper的buildInvokerChain完成的,具体是将注解中含有group=provider的Filter实现,按照order排序,最后的调用顺序是

EchoFilter-》ClassLoaderFilter-》GenericFilter-》ContextFilter-》ExceptionFilter-》

TimeoutFilter-》MonitorFilter-》TraceFilter。

更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter则只是在主功能上添加了功能,更改当前线程的ClassLoader,这是典型的装饰器模式。

3、观察者模式

Dubbo的provider启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个listener。注册中心会每5秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个notify消息,provider接受到notify消息后,即运行NotifyListener的notify方法,执行监听器方法。

4、动态代理模式

Dubbo扩展jdk

spi的类ExtensionLoader的Adaptive实现是典型的动态代理实现。Dubbo需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是ExtensionLoader的createAdaptiveExtensionClassCode方法。代理类的主要逻辑是,获取URL参数中指定参数的值作为获取实现类的key。

以上就是关于Dubbo负载均衡全部的内容,包括:Dubbo负载均衡、生产环境出现的几次线程池被占满的问题分析、Dubbo之限流分析等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/10086610.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-05
下一篇2023-05-05

发表评论

登录后才能评论

评论列表(0条)

    保存