Ribbon和@LoadBalanced


Ribbon概念

Ribbon是Netflix開發的客戶端負載均衡器,為Ribbon配置服務提供者地址列表后,Ribbon就可以基於某種負載均衡策略算法,自動地幫助服務消費者去請求提供者

Ribbon默認為我們提供了很多負載均衡算法,例如輪詢、隨機等,我們也可以實現自定義負載均衡算法

Ribbon作為Spring Cloud的負載均衡機制的實現,

  • Ribbon可以單獨使用,作為一個獨立的負載均衡組件,需要手動配置服務地址列表。
  • Ribbon與Eureka配合使用時,Ribbon可自動從Eureka Server獲取服務提供者地址列表(DiscoveryClient),並基於負載均衡算法,請求其中一個服務提供者實例
  • Ribbon與OpenFeign和RestTemplate進行無縫對接,讓二者具有負載均衡的能力,OpenFeign默認集成了ribbon

Ribbon組成

github地址:https://github.com/Netflix/ribbon

  • ribbon-core: 核心的通用性代碼。api一些配置。
  • ribbon-eureka:基於eureka封裝的模塊,能快速集成eureka。
  • ribbon-examples:學習示例。
  • ribbon-httpclient:基於apache httpClient封裝的rest客戶端,集成了負載均衡模塊,可以直接在項目中使用。
  • ribbon-loadbalancer:負載均衡模塊。
  • ribbon-transport:基於netty實現多協議的支持。比如http,tcp,udp等

編碼及測試

利用Eureka手寫負載均衡

在api-driver:ShortMsgServiceImpl中

調用方:調用服務,通過loadBalance(我們自定義的方法)選出一個服務

手寫ribbon調用

ServiceInstance instance = loadBalance(serviceName);
url = http + instance.getHost()+":"+instance.getPort()+uri;
ResponseEntity<ResponseResult> resultEntity = restTemplate.postForEntity(url, smsSendRequest, ResponseResult.class);
ResponseResult result = resultEntity.getBody();

負載均衡方法loadBalance

import org.springframework.cloud.client.discovery.DiscoveryClient;

@Autowired
DiscoveryClient discoveryClient;

private ServiceInstance loadBalance(String serviceName) {
    List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
    ServiceInstance instance = instances.get(new Random().nextInt(instances.size()));
    log.info("負載均衡 選出來的ip:"+instance.getHost()+",端口:"+instance.getPort());
    return instance;
}

引入RestTemplate

/**
* 手寫簡單ribbon
* @return
*/
@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

便於理解,下面是基於:RandomRule,基於Ribbon做選擇

ribbon loadbalance

yapi:api-driver:學習:根據serviceName獲取服務端信息

進入方法

@GetMapping("/choseServiceName")
    public ResponseResult choseServiceName() {
    String serviceName = "service-sms";
    ServiceInstance si = loadBalancerClient.choose(serviceName);
    System.out.println("sms節點信息:url:"+si.getHost()+",port:"+si.getPort());

    return ResponseResult.success("");
}

loadBalancerClient

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient的choose方法

@Override
    public ServiceInstance choose(String serviceId) {
    return choose(serviceId, null);
}

進入choose

public ServiceInstance choose(String serviceId, Object hint) {
    Server server = getServer(getLoadBalancer(serviceId), hint);
    if (server == null) {
        return null;
    }
    return new RibbonServer(serviceId, server, isSecure(server, serviceId),
    serverIntrospector(serviceId).getMetadata(server));
}

進入getLoadBalancer

protected ILoadBalancer getLoadBalancer(String serviceId) {
    return this.clientFactory.getLoadBalancer(serviceId);
}

再進入org.springframework.cloud.netflix.ribbon.SpringClientFactory,此時類換了。

public ILoadBalancer getLoadBalancer(String name) {
    return getInstance(name, ILoadBalancer.class);
}

進入getInstance

@Override
public <C> C getInstance(String name, Class<C> type) {
    C instance = super.getInstance(name, type);
    if (instance != null) {
        return instance;
    }
    IClientConfig config = getInstance(name, IClientConfig.class);
    return instantiateWithConfig(getContext(name), type, config);
}

進入 super.getInstance,此方法獲取到:ILoadBalancer,從spring ioc容器中來

public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
        return context.getBean(type);
    }
    return null;
}

全量拉取和增量拉取

F7跳下一個斷點

Server server = getServer(getLoadBalancer(serviceId), hint);

進入getServer

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
    if (loadBalancer == null) {
        return null;
    }
    // Use 'default' on a null hint, or just pass it on?
    return loadBalancer.chooseServer(hint != null ? hint : "default");
}

鼠標放到loadBalancer,看看里面內容。主要看看它的rule屬性。

進入loadBalancer.chooseServer

public Server chooseServer(Object key) {
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
        logger.debug("Zone aware logic disabled or there is only one zone");
        return super.chooseServer(key);
    }
}

進入super.chooseServer(key)

public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            return rule.choose(key);
        } catch (Exception e) {
            logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
            return null;
        }
    }
}

 

走到return rule.choose(key);
com.netflix.loadbalancer.RandomRule@1b73fec7,是因為我們在外面配置了它是隨機規則

進入choose

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

再進入:choose

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) {
            /*
             * No servers. End regardless of pass, because subsequent passes
             * only get more restrictive.
             */
            return null;
        }

        int index = chooseRandomInt(serverCount);
        server = upList.get(index);

        if (server == null) {
            /*
            * The only time this should happen is if the server list were
            * somehow trimmed. This is a transient condition. Retry after
            * yielding.
            */
            Thread.yield();
            continue;
        }

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

        // Shouldn't actually happen.. but must be transient or a bug.
        server = null;
        Thread.yield();
    }
    return server;
}

重點

int index = chooseRandomInt(serverCount);
server = upList.get(index);

進去

protected int chooseRandomInt(int serverCount) {
    return ThreadLocalRandom.current().nextInt(serverCount);
}

最后獲取到服務。

上面是選擇服務的過程。和我們前面手寫過比較:都是隨機數選出一個服務

將yml中service-sms的配置 隨機規則去掉,則ILoadBalancer的 rule就變了

核心類:ILoadBalancer

里面包括了所有的服務提供者集群 的:ip和端口。service-sms:8002,8003

每個服務都有一個ILoadBalancer,ILoadBalancer里面有該服務列表。

每個服務

Map<服務名,ILoadBalancer>

服務列表來源

打開:com.netflix.loadbalancer.ILoadBalancer

它是定義負載均衡操作過程的接口

通過SpringClientFactory的getLoadBalancer方法獲取(前面跟蹤源碼看到的)

ILoadBalancer的實例實在RibbonClientConfiguration中配置的

通過下面兩種方式:1.默認RibbonClientConfiguration(下面) 2自定義。

org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
    IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
    if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
        return this.propertiesFactory.get(ILoadBalancer.class, config, name);
    }
    return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
    serverListFilter, serverListUpdater);
}

ILoadBalancer的默認的實現類是:ZoneAwareLoadBalancer

Rule默認:com.netflix.loadbalancer.ZoneAvoidanceRule@34b82630

配置說明

| Bean 類型                |             說明            |
| ---------------- | ---------------- |
| ILoadBalancer          |     負載均衡器的抽象  |
| IClientConfig            |        client配置類      |
| IRule                       |       負載均衡策略      |
| IPing                       |     服務可用性檢測     |
| ServerList                |       服務列表獲取      |
| ServerListFilter         |      服務列表過濾       |

ILoadBalance的接口代表負載均衡器的操作,比如有添加服務器操作、選擇服務器操作、獲取所有的服務器列表、獲取可用的服務器列表等等

// ILoadbalancer
// 添加所有該服務的服務列表
Initial list of servers.
public void addServers(List<Server> newServers);

// 得到可以訪問的服務列表
public List<Server> getReachableServers();

Choose a server from load balancer.(和負載均衡算法關聯)
// 選擇一個可以調用的server
public Server chooseServer(Object key);

上面方法:實現了:

  • 列出所有可用服務public List<Server> getReachableServers();
  • 然后選一個服務出來chooseServer(Object key);

飢餓模式

com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList

方法: private List<DiscoveryEnabledServer> obtainServersViaDiscovery() 中:

List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);

得到所有服務,對應的服務列表,借助eurekaClient

服務列表:DynamicServerListLoadBalancer

初始化下面的類時,執行了服務列表拉取

com.netflix.loadbalancer.DynamicServerListLoadBalancer

@Override
public void setServersList(List lsrv) {
    super.setServersList(lsrv);
    List<T> serverList = (List<T>) lsrv;
    Map<String, List<Server>> serversInZones = new HashMap<String, List<Server>>();
    for (Server server : serverList) {
        // coding
    }
}

最終會存儲到

com.netflix.loadbalancer.LoadBalancerStats的
volatile Map<String, List<? extends Server>> upServerListZoneMap。

處理無用的服務

更新機制,更新最新的服務

DynamicServerListLoadBalancer

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
    @Override
    public void doUpdate() {
        updateListOfServers();
    }
}

ping機制,試試服務好不好

public List<Server> getAllServers();

獲取所有服務。有的已經掛了。怎么辦?

從eureka獲得一系列 server。不知道server掛了沒有。用定時任務,間隔去ping

執行:com.netflix.loadbalancer.IPing

有個實現類:NIWSDiscoveryPing

public boolean isAlive(Server server) {
    boolean isAlive = true;
    if (server!=null && server instanceof DiscoveryEnabledServer){
        DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server; 
        InstanceInfo instanceInfo = dServer.getInstanceInfo();
        if (instanceInfo!=null){ 
            InstanceStatus status = instanceInfo.getStatus();
            if (status!=null){
                isAlive = status.equals(InstanceStatus.UP);
            }
        }
    }
    return isAlive;
}

上兩種機制不能同時發生

選擇算法

IRule,默認

com.netflix.loadbalancer.ZoneAvoidanceRule@505fb311:區域內輪詢。

IRule負載均衡策略:通過實現該接口定義自己的負載均衡策略

它的choose方法就是從一堆服務器列表中按規則選出一個服務器。

所有規則

ZoneAvoidanceRule(區域權衡策略)

  • 復合判斷Server所在區域的性能和Server的可用性,輪詢選擇服務器

BestAvailableRule(最低並發策略)

  • 會先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,然后選擇一個並發量最小的服務
  • 逐個找服務,如果斷路器打開,則忽略

RoundRobinRule(輪詢策略)

  • 以簡單輪詢選擇一個服務器,按順序循環選擇一個server。

RandomRule(隨機策略)

  • 隨機選擇一個服務器

AvailabilityFilteringRule(可用過濾策略)

  • 會先過濾掉多次訪問故障而處於斷路器跳閘狀態的服務和過濾並發的連接數量超過閥值得服務
  • 然后對剩余的服務列表安裝輪詢策略進行訪問

WeightedResponseTimeRule(響應時間加權策略)

  • 據平均響應時間計算所有的服務的權重,響應時間越快服務權重越大,容易被選中的概率就越高
  • 剛啟動時,如果統計信息不中,則使用RoundRobinRule(輪詢)策略,等統計的信息足夠了會自動的切換到WeightedResponseTimeRule
  • 響應時間長,權重低,被選擇的概率低。反之,同樣道理此策略綜合了各種因素(網絡,磁盤,IO等),這些因素直接影響響應時間

RetryRule(重試策略)

  • 先按照RoundRobinRule(輪詢)的策略獲取服務,如果獲取的服務失敗則在指定的時間會進行重試,進行獲取可用的服務
  • 如多次獲取某個服務失敗,就不會再次獲取該服務
  • 主要是在一個時間段內,如果選擇一個服務不成功,就繼續找可用的服務,直到超時

如果要用其他負載均衡策略:只需要更改。

@Bean
public IRule myRule(){
    //return new RoundRobinRule();
    //return new RandomRule();
    return new RetryRule();
}

Iloadbalancer,irule,choose()。

Ribbon負載均衡

Ribbon可以獨立使用,自己寫服務列表,也是一個簡單配置。

<!-- ribbon -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

配置文件

service-sms: 
ribbon: 
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
eureka: 
    enable: false
    listOfServers: localhost:8002,localhost:8003

可以配置輪詢,可以配置隨機,上面是隨機,默認是輪詢 

Ribbon可以和RestTemplate一起使用,也可以集成到Feign中使用 

編碼

Spring Cloud為客戶端負載均衡創建了特定的注解@LoadBalanced

  • 我們只需要使用該注解修飾創建RestTemplate實例的@Bean函數,Spring Cloud就會讓RestTemplate使用相關的負載均衡策略,默認情況下是使用Ribbon
  • 除了@LoadBalanced之外,Ribbon還提供@RibbonClient注解
  • 該注解可以為Ribbon客戶端聲明名稱和自定義配置
  • name屬性可以設置客戶端的名稱,configuration屬性則會設置Ribbon相關的自定義配置類

在eureka-client中使用Ribbon時, 不需要引入jar包,因為erueka-client已經包括ribbon的jar包了

用@LoadBalance修飾RestTemplate可以實現負載均衡

由於RestTemplate的Bean實例化方法restTemplate被@LoadBalanced修飾

所以當調用restTemplate的postForObject方法發送HTTP請求時,會使用Ribbon進行負載均衡

//使用ribbon,添加@LoadBalance,使RestTemplate具備負載均衡能力。
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

@Autowired
private RestTemplate restTemplate;
//serviceName=虛擬主機名。默認情況下,虛擬主機名和服務名一致。
String url = "http://"+serviceName+"/send/alisms-template";
//調用
ResponseEntity<ResponseResult> resultEntity = restTemplate.postForEntity(url, smsSendRequest, ResponseResult.class);

//測試根據serviceName獲取服務提供者信息。此時不需要@LoadBalance,默認是輪訓。
@Autowired
private LoadBalancerClient loadBalancerClient;
// 不能將choseServiceName和 restTemplate寫在一起,因為后者中已經有前者了。
@GetMapping("/choseServiceName")
public ResponseResult choseServiceName() {
    String serviceName = "service-sms";
    ServiceInstance si = loadBalancerClient.choose(serviceName);
    System.out.println("sms節點信息:url:"+si.getHost()+",port:"+si.getPort());

    return ResponseResult.success("");
}

擴展

默認情況下,虛擬主機名=服務名稱,虛擬主機名最好不要用"_"

虛擬主機名可以配置:

eureka: 
instance: 
virtual-host-name: service-sms

原理

通過前面的例子,我們可知:

  • 攔截請求
  • 獲取url
  • 通過url中 serviceName 獲取 List<ServiceInstance>
  • 通過負載均衡算法選取一個ServiceInstance
  • 替換請求,將原來url中的 serviceName換成ip+port

@LoadBalanced原理源碼

如果用了正常的調用 ribbon,調用的服務名,而沒有加@LoadBalance

會報:java.net.UnknownHostException 就走了攔截器。

RibbonLoadBalancerClient的方法

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    Server server = getServer(loadBalancer, hint);
// coding
}

上面方法

  • 負載均衡選出一個server
  • 給RestTemplate增加了攔截器
  • 在請求之前,將請求的地址進行替換(根據具體的負載策略選擇請求地址,將服務名替換成 ip:port)

然后再進行調用

在ioc容器初始化時

org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

加了個bean

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(
        restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
    };    
}

給restTemplate設置了攔截器

進入攔截器:final LoadBalancerInterceptor loadBalancerInterceptor

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
    final ClientHttpRequestExecution execution) throws IOException {
    final URI originalUri = request.getURI();
    String serviceName = originalUri.getHost();
    Assert.state(serviceName != null,
    "Request URI does not contain a valid hostname: " + originalUri);    
    return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}

此方法,可以類比我們的spring mvc攔截器。每次請求都攔截一下

return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    //此時完成了負載均衡選擇
    Server server = getServer(loadBalancer, hint);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }
    RibbonServer ribbonServer = new RibbonServer(serviceId, server,
    isSecure(server, serviceId),
    serverIntrospector(serviceId).getMetadata(server));

    return execute(serviceId, ribbonServer, request);
}

通過ILoadBalancer,獲取服務地址

再點return execute(serviceId, ribbonServer, request);

T returnVal = request.apply(serviceInstance);

apply處打斷點,其實在getUri

在org.springframework.http.client;InterceptingClientHttpRequest中

@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
    if (this.iterator.hasNext()) {
        ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
        return nextInterceptor.intercept(request, body, this);
    }
    else {
        HttpMethod method = request.getMethod();
        Assert.state(method != null, "No standard HTTP method");
        ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
        request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
        if (body.length > 0) {
            if (delegate instanceof StreamingHttpOutputMessage) {
                StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
                streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
            }else {
                StreamUtils.copy(body, delegate.getBody());
            }
        }
    return delegate.execute();
    }
}
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);

最終

com.netflix.loadbalancer;LoadBalancerContext

public URI reconstructURIWithServer(Server server, URI original) {
    String host = server.getHost();
    int port = server.getPort();
    String scheme = server.getScheme();

    if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) {
        return original;
    }
    if (scheme == null) {
        scheme = original.getScheme();
    }
    if (scheme == null) {
        scheme = deriveSchemeAndPortFromPartialUri(original).first();
    }

    try {
        StringBuilder sb = new StringBuilder();
        sb.append(scheme).append("://");
        if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
            sb.append(original.getRawUserInfo()).append("@");
        }
        sb.append(host);
        if (port >= 0) {
        sb.append(":").append(port);
        }
        sb.append(original.getRawPath());
        if (!Strings.isNullOrEmpty(original.getRawQuery())) {
            sb.append("?").append(original.getRawQuery());
        }
        if (!Strings.isNullOrEmpty(original.getRawFragment())) {
            sb.append("#").append(original.getRawFragment());
        }
        URI newURI = new URI(sb.toString());
        return newURI; 
    } catch (URISyntaxException e) {
        throw new RuntimeException(e);
    }
}

總結

  • 由於加了@LoadBalanced注解,使用RestTemplateCustomizer對所有標注了@LoadBalanced的RestTemplate Bean添加了一個LoadBalancerInterceptor攔截器
  • 利用RestTempllate的攔截器,spring可以對restTemplate bean進行定制
  • 加入loadbalance攔截器進行ip:port的替換,也就是將請求的地址中的服務邏輯名轉為具體的服務地址

源碼總結

ILoadBalancer 承接 eureka 和 ribbon

獲取服務地址列表,選擇一個

每個服務都有ILoadBalancer

選擇服務用 IRule(負載均衡策略)

自定義Ribbon配置

IRule 

Spring Cloud默認的Ribbon配置類是:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration。

全局:

ribbon:
eager-load:
enabled: true

啟動拉取服務列表

默認false:當服務調用時,采取拉取服務列表

ribbon:
http:
client:
enabled: true 

默認的請求發起是:HttpURLConnection,true:意思是:改成:HttpClinet

okhttp:
enabled: true

true:改成OKHttpClient

單個服務配置 org.springframework.cloud.netflix.ribbon.PropertiesFactory

public PropertiesFactory() {
    classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
    classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
    classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
    classToProperty.put(ServerList.class, "NIWSServerListClassName");
    classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}

相應配置如下

service-sms: 
    ribbon: 
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Java代碼定義

只看標數字的步驟

修改掃描包配置,使不掃描RibbonConfiguration所在的包com.online.taxi.passenger.ribbonconfig。

@ComponentScan({"com.online.taxi.passenger.controller",
"com.online.taxi.passenger.dao",
"com.online.taxi.passenger.service",
"com.online.taxi.passenger.ribbonconfigscan"})

巧妙的辦法,用注解,單獨排除注解修飾的類

@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value=ExcudeRibbonConfig.class)})

定義com.online.taxi.passenger.ribbonconfig.RibbonConfiguration

package com.online.taxi.passenger.ribbonconfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
/**
* 該類不應該在主應用程序的掃描之下,需要修改啟動類的掃描配置。否則將被所有的Ribbon client共享,
* 比如此例中:ribbonRule 對象 會共享。
* @author yueyi2019
*
*/
@Configuration
@ExcudeRibbonConfig
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule() {
        return new RandomRule();
    }
}

創建一個空類,配置 service-sms 對應的 ribbon規則

package com.online.taxi.passenger.ribbonconfigscan;

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;

import com.online.taxi.passenger.ribbonconfig.RibbonConfiguration;

@Configuration
@RibbonClient(name = "service-sms",configuration = RibbonConfiguration.class)
public class TestConfiguration {

}

添加配置注解

@Configuration
@RibbonClient(name = "service-sms",configuration = RibbonConfiguration.class)

此方法只改變service-sms的 負載均衡策略。其他服務名沒有影響。

給所有client設置隨機策略

@RibbonClients(defaultConfiguration = RibbonConfiguration.class)

屬性定義

針對服務定ribbon策略:

service-sms: 
    ribbon: 
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

給所有服務定ribbon策略:

ribbon: 
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

屬性配置方式優先級高於Java代碼

Ribbon脫離Eureka

service-sms:
    ribbon:
        eureka:
            # 將Eureka關閉,則Ribbon無法從Eureka中獲取服務端列表信息
            enabled: false
            # listOfServers可以設置服務端列表
            listOfServers:localhost:8090,localhost:9092,localhost:9999

為service-sms設置 請求的網絡地址列表

Ribbon可以和服務注冊中心Eureka一起工作,從服務注冊中心獲取服務端的地址信息,也可以在配置文件中使用listOfServers字段來設置服務端地址

飢餓加載

ribbon:
    eager-load:
    enabled: true
    clients:
        - SERVICE-SMS

Spring Cloud默認是懶加載,指定名稱的Ribbon Client第一次請求時,對應的上下文才會被加載,所以第一次訪問慢 

改成以上飢餓加載后,將在啟動時加載對應的程序上下文,從而提高首次請求的訪問速度

在private List<DiscoveryEnabledServer> obtainServersViaDiscovery()首行,打斷點。

飢餓進入此代碼

2020-01-21 16:08:03.605 INFO [api-driver,,,] 13400 --- [ main] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client SERVICE-SMS initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SERVICE-SMS,current list of Servers=[30.136.133.11:8002],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:30.136.133.11:8002; 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@5af4328e
2020-01-21 16:08:04.574 INFO [api-driver,,,] 13400 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: SERVICE-SMS.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647

PS:除了和RestTemplate進行配套使用之外,Ribbon還默認被集成到了OpenFeign中

當使用@FeignClient時,OpenFeign默認使用Ribbon來進行網絡請求的負載均衡。

實踐,在api-passenger的yml中,添加 service-sms ribbon NFLoad


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM