Spring-Cloud-Ribbon学习笔记(二):自定义负载均衡规则

Ribbon自定义负载均衡策略有两种方式,一是JavaConfig,一是经过配置文件(yml或properties文件)。html

需求

假设我有包含A和B服务在内的多个微服务,它们均注册在一个Eureka上,信息以下:
java

我但愿当访问服务A时候,2个服务(端口分别是8087和8081)每两次一换,好比访问两次8087,再访问两次8081,如此反复。
当访问服务B时,与A相似,不过是3次一换。
当访问其余服务时,采用随机规则,即RandomRule,而不是默认策略1web

JavaConfig

使用这种方式,总共分3步(以服务A的规则为例):spring

  • 新建针对服务A的负载均衡规则类SvcARule,实现抽象类AbstractLoadBalancerRule并重写方法,在类上只须要添加注解@Primary2,重写逻辑需要根据实际需求来定,详见下面的代码
  • 新建配置类LBConfig,添加类型为IRule的Bean,此处咱们选RandomRule
  • 在配置类LBConfig上,经过注解@RibbonClients(只有一种自定义规则,则使用@RibbonClient),添加不适用公共规则的其余自定义规则

实现

SvcARule

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.context.annotation.Primary;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Primary
public class SvcARule extends AbstractLoadBalancerRule {

    private AtomicInteger total = new AtomicInteger();
    private AtomicInteger index = new AtomicInteger();


    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers();
            List<Server> allList = lb.getAllServers();

            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
            if (total.get() < 2) {
                server = upList.get(index.get());
                total.incrementAndGet();
            } else {
                total.set(0);
                index.set(index.incrementAndGet() % upList.size());
            }

            if (server == null) {
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            server = null;
            Thread.yield();
        }
        return server;

    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // TODO Auto-generated method stub
    }
}

SvcBRule

略,只须要把if (total.get() < 2)改为if (total.get() < 3)便可服务器

LBConfig

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.yan.ribbon.rule.SvcARule;
import com.yan.ribbon.rule.SvcBRule;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@RibbonClients(
        value = {
                @RibbonClient(name = "SVCA", configuration = SvcARule.class),
                @RibbonClient(name = "SVCB", configuration = SvcBRule.class)
        },
        defaultConfiguration = LBConfig.class)
public class LBConfig {
    @Bean
    public IRule commonRule() {
        return new RandomRule();
    }
}

测试

经过postman屡次访问服务A、B和C,获得日志以下:并发

2019-03-19 20:22:09.511  INFO 13372 --- [nio-8004-exec-4] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-03-19 20:22:09.511  INFO 13372 --- [nio-8004-exec-4] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-03-19 20:22:09.519  INFO 13372 --- [nio-8004-exec-4] o.s.web.servlet.DispatcherServlet        : Completed initialization in 8 ms
2019-03-19 20:22:09.693  INFO 13372 --- [nio-8004-exec-4] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-SVCA
2019-03-19 20:22:09.708  INFO 13372 --- [nio-8004-exec-4] c.netflix.loadbalancer.BaseLoadBalancer  : Client: SVCA instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SVCA,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-03-19 20:22:09.712  INFO 13372 --- [nio-8004-exec-4] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
2019-03-19 20:22:09.729  INFO 13372 --- [nio-8004-exec-4] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client SVCA initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SVCA,current list of Servers=[YanWei:8087, YanWei:8081],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;  Instance count:2;   Active connections count: 0;    Circuit breaker tripped count: 0;   Active connections per server: 0.0;]
},Server stats: [[Server:YanWei:8087;   Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
, [Server:YanWei:8081;  Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@7f0246c6
"svcA:8087===>\nname:xixi\ntype:haha"
"svcA:8087===>\nname:xixi\ntype:haha"
"svcA:8081===>\nname:xixi\ntype:haha"
"svcA:8081===>\nname:xixi\ntype:haha"
"svcA:8087===>\nname:xixi\ntype:haha"
"svcA:8087===>\nname:xixi\ntype:haha"
"svcA:8081===>\nname:xixi\ntype:haha"
"svcA:8081===>\nname:xixi\ntype:haha"
"svcA:8087===>\nname:xixi\ntype:haha"
"svcA:8087===>\nname:xixi\ntype:haha"
"svcA:8081===>\nname:xixi\ntype:haha"
"svcA:8081===>\nname:xixi\ntype:haha"
2019-03-19 20:22:37.173  INFO 13372 --- [nio-8004-exec-5] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-SVCB
2019-03-19 20:22:37.174  INFO 13372 --- [nio-8004-exec-5] c.netflix.loadbalancer.BaseLoadBalancer  : Client: SVCB instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SVCB,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-03-19 20:22:37.177  INFO 13372 --- [nio-8004-exec-5] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
2019-03-19 20:22:37.178  INFO 13372 --- [nio-8004-exec-5] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client SVCB initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SVCB,current list of Servers=[YanWei:8086, YanWei:8082],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;  Instance count:2;   Active connections count: 0;    Circuit breaker tripped count: 0;   Active connections per server: 0.0;]
},Server stats: [[Server:YanWei:8082;   Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
, [Server:YanWei:8086;  Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@485ed210
"svcB:8086===>\nname:xixi\ntype:haha"
"svcB:8086===>\nname:xixi\ntype:haha"
"svcB:8086===>\nname:xixi\ntype:haha"
"svcB:8082===>\nname:xixi\ntype:haha"
"svcB:8082===>\nname:xixi\ntype:haha"
"svcB:8082===>\nname:xixi\ntype:haha"
"svcB:8086===>\nname:xixi\ntype:haha"
"svcB:8086===>\nname:xixi\ntype:haha"
"svcB:8086===>\nname:xixi\ntype:haha"
"svcB:8082===>\nname:xixi\ntype:haha"
"svcB:8082===>\nname:xixi\ntype:haha"
"svcB:8082===>\nname:xixi\ntype:haha"
"svcB:8086===>\nname:xixi\ntype:haha"
"svcB:8086===>\nname:xixi\ntype:haha"
2019-03-19 20:23:01.319  INFO 13372 --- [nio-8004-exec-8] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-SVCC
2019-03-19 20:23:01.320  INFO 13372 --- [nio-8004-exec-8] c.netflix.loadbalancer.BaseLoadBalancer  : Client: SVCC instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SVCC,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-03-19 20:23:01.320  INFO 13372 --- [nio-8004-exec-8] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
2019-03-19 20:23:01.322  INFO 13372 --- [nio-8004-exec-8] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client SVCC initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SVCC,current list of Servers=[YanWei:8083, YanWei:8085],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;  Instance count:2;   Active connections count: 0;    Circuit breaker tripped count: 0;   Active connections per server: 0.0;]
},Server stats: [[Server:YanWei:8083;   Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
, [Server:YanWei:8085;  Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@77b35c9
"svcC:8085===>\nname:xixi\ntype:haha"
"svcC:8083===>\nname:xixi\ntype:haha"
"svcC:8085===>\nname:xixi\ntype:haha"
"svcC:8085===>\nname:xixi\ntype:haha"
"svcC:8083===>\nname:xixi\ntype:haha"
"svcC:8085===>\nname:xixi\ntype:haha"
"svcC:8085===>\nname:xixi\ntype:haha"

配置文件方式

配置文件方式需要遵照如下格式:app

<serviceName>.ribbon.<key>=<value>

几个变量解释以下:负载均衡

serviceName就是服务名
key是指Ribbon为实现不一样领域(咱们这里只说IRule,其实还有IPing等)所指定的关联类名,这里我须要自定义IRule,其关联类名,也就是所谓的key,是NFLoadBalancerRuleClassName3
value是我所要指定的自定义规则,好比,SvcB的负载均衡策略SvcBRuledom

所以,在配置文件中添加以下配置:ide

svcB:
  ribbon:
    NFLoadBalancerRuleClassName: com.yan.ribbon.rule.SvcBRule

从新启动Ribbon服务后,发现此配置并无生效,缘由有两个:
一是个人服务名字写的是svcB,理由是个人配置spring.appliaction.name=svcB,可是使用Ribbon调用时使用了http://SVCB/...,SVCB是大写的,因此没有获得指望的效果4
二是我已经有了用来替换默认规则的LBConfig,使得IRule的默认配置没有生效,由于查看源码org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration中对于IRule的定义以下:

@Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, name)) {
            return this.propertiesFactory.get(IRule.class, config, name);
        }
        ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }

因为我自定义的commonRule已经存在了,因此此处不会触发,更不会走到判断那一步,就是说,个人上面的关于SvcB的自定义规则的配置,是没有什么地方去读的,所以,为了方便演示,直接将LBConfig所有注掉,再次重启后,把访问Uri中的大写改为一致的svcB,发现对于服务B的配置生效了,可是没有指定的服务,所有都采用了默认的策略ZoneAvoidanceRule

总结

配置文件方式,比较麻烦,且不能-不必也懒得研究如何-修改默认规则,而JavaConfig能够完美替代,所以我选JavaConfig。

进阶

关于Ribbon中对于IRule的实现,总共有以下几种,其简略说明也附注以下:

  • RoundRobinRule: 轮询
  • RandomRule:随机
  • AvailabilityFilteringRule:会先过滤掉因为屡次访问故障而处于断路器跳闸状态的服务,还有并发的链接数超过阈值的服务,而后对剩余的服务列表按照轮询策略进行访问
  • WeightedResponseTimeRule:根据平均响应时间计算全部服务的权重,响应时间越快服务权重越大,被选中的几率越高。刚启动时若是统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到WeightedResponseTimeRule
  • RetryRule: 先按照RoundRobinRule的策略获取服务,若是获取服务失败,则在指定时间内会进行重试,获取可用服务
  • BestAvailableRule: 会先过滤掉因为屡次访问故障而处于断路器跳闸状态的服务,而后选择一个并发量最小的服务
  • ZoneAvoidanceRule: 默认规则,符合判断server所在区域的性能和server的可用性选择服务器

  1. 对应类为ZoneAvoidanceRule,直译过来是区域回避规则,我也不知道为啥这样叫:D

  2. 若是不加此注解,会报错,内容是找类型为IRule的Bean,可是找到了俩,一个commonRule,一个svcARule。若是你不嫌麻烦,能够选择网上流行的较为复杂的形式,好比springcloud-04-自定义ribbon的配置方式

  3. 能够在类org.springframework.cloud.netflix.ribbon.PropertiesFactory中查看

  4. 这个Eureka也要背锅,由于使用服务名来进行服务间调用,是被大小写是被兼容过的,致使我这边疏忽大意了,仍是要细心的好。

相关文章
相关标签/搜索