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也要背鍋,由於使用服務名來進行服務間調用,是被大小寫是被兼容過的,致使我這邊疏忽大意了,仍是要細心的好。

相關文章
相關標籤/搜索