天天看點

微服務用戶端負載均衡元件Ribbon

負載均衡分類

服務端負載均衡

常見的服務端負載均衡有Nginx,用戶端将請求發送給Nginx,Nginx根據負載均衡算法選擇一個伺服器調用,具體調用哪個伺服器由Nginx說了算,用戶端是不知道伺服器的真實IP的。

微服務用戶端負載均衡元件Ribbon

用戶端負載均衡

Spring Cloud Ribbon是基于NetFilix Ribbon實作的一套用戶端負載均衡,Ribbon用戶端元件提供了一系列的完善的配置,例如逾時,重試等。用戶端從注冊中心擷取到伺服器的清單,由用戶端自己根據負載均衡算法選擇将流量分發給哪個伺服器,用戶端是知道伺服器的真實IP的。

微服務用戶端負載均衡元件Ribbon

重寫RestTemplate的doExecute()方法實作負載均衡

通過閱讀RestTemplate源碼得知,不管是POST,GET請求,最終都會調用doExecute()方法,是以我們可以通過繼承RestTemplate類并重寫doExecute()方法來實作負載均衡算法。

package com.tuling.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.client.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Random;
/**
 * 根據RestTemplate特性自己改造
 */
@Slf4j
public class TulingRestTemplate extends RestTemplate {
    private DiscoveryClient discoveryClient;
    public TulingRestTemplate (DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }
    protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
                              @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
        Assert.notNull(url, "URI is required");
        Assert.notNull(method, "HttpMethod is required");
        ClientHttpResponse response = null;
        try {
            log.info("請求的url路徑為:{}",url);
            //把服務名 替換成我們的IP
            url = replaceUrl(url);
            log.info("替換後的路徑:{}",url);
            ClientHttpRequest request = createRequest(url, method);
            if (requestCallback != null) {
                requestCallback.doWithRequest(request);
            }
            response = request.execute();
            handleResponse(url, method, response);
            return (responseExtractor != null ? responseExtractor.extractData(response) : null);
        }
        catch (IOException ex) {
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
            throw new ResourceAccessException("I/O error on " + method.name() +
                    " request for \"" + resource + "\": " + ex.getMessage(), ex);
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }
    /**
     * 方法實作說明:把微服務名稱去注冊中心拉取對應IP進行替換
     * http://product-center/selectProductInfoById/1
     * @param url:請求的url
     * @return: 替換後的url
     * @exception:
     * @date:2020/2/6 13:11
     */
    private URI replaceUrl(URI url){
        //1:從URI中解析調用的調用的serviceName=product-center
        String serviceName = url.getHost();
        log.info("調用微服務的名稱:{}",serviceName);
        //2:解析我們的請求路徑 reqPath= /selectProductInfoById/1
        String reqPath = url.getPath();
        log.info("請求path:{}",reqPath);
        //通過微服務的名稱去nacos服務端擷取 對應的執行個體清單
        List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances(serviceName);
        if(serviceInstanceList.isEmpty()) {
            throw new RuntimeException("沒有可用的微服務執行個體清單:"+serviceName);
        }
        String serviceIp = chooseTargetIp(serviceInstanceList);
        String source = serviceIp+reqPath;
        try {
            return new URI(source);
        } catch (URISyntaxException e) {
            log.error("根據source:{}建構URI異常",source);
        }
        return url;
    }
    /**
     * 方法實作說明:從服務清單中 随機選舉一個ip
     * @param serviceInstanceList 服務清單
     * @return: 調用的ip
     * @exception:
     * @date:2020/2/6 13:15
     */
    private String chooseTargetIp(List<ServiceInstance> serviceInstanceList) {
        //采取随機的擷取一個
        Random random = new Random();
        Integer randomIndex = random.nextInt(serviceInstanceList.size());
        String serviceIp = serviceInstanceList.get(randomIndex).getUri().toString();
        log.info("随機選舉的服務IP:{}",serviceIp);
        return serviceIp;
    }
}      

order服務的Controller:

package com.tuling.controller;
import com.tuling.entity.OrderInfo;
import com.tuling.entity.ProductInfo;
import com.tuling.mapper.OrderInfoMapper;
import com.tuling.vo.OrderVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
 * 自定義負載均衡的服務消費者
 */
@RestController
public class OrderInfoController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private OrderInfoMapper orderInfoMapper;
    @RequestMapping("/selectOrderInfoById/{orderNo}")
    public Object selectOrderInfoById(@PathVariable("orderNo") String orderNo) {
        OrderInfo orderInfo = orderInfoMapper.selectOrderInfoById(orderNo);
        if(null == orderInfo) {
            return "根據orderNo:"+orderNo+"查詢沒有該訂單";
        }
        ResponseEntity<ProductInfo> responseEntity = restTemplate.getForEntity("http://product-center/selectProductInfoById/"+orderInfo.getProductNo(), ProductInfo.class);
        ProductInfo productInfo = responseEntity.getBody();
        if(productInfo == null) {
            return "沒有對應的商品";
        }
        OrderVo orderVo = new OrderVo();
        orderVo.setOrderNo(orderInfo.getOrderNo());
        orderVo.setUserName(orderInfo.getUserName());
        orderVo.setProductName(productInfo.getProductName());
        orderVo.setProductNum(orderInfo.getProductCount());
        return orderVo;
    }
}      

在後端并行啟動兩個product執行個體:我們用 8081啟動一個服務後,然後修改端口為8082,如下圖所示勾選Allow parallel run,就可以同一個工程啟動兩個執行個體。

spring:
  datasource:
      druid:
        username: root
        password: 123456
        jdbcUrl: jdbc:mysql://192.168.1.14:3306/tuling-ms-alibaba?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
        driverClassName: com.mysql.jdbc.Driver
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        filters: stat,wall #\u914D\u7F6E\u8FC7\u6EE4\u5668
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  application:
    name: product-center
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.1.5:8848
server:
  port: 8081
  #port: 8082      
微服務用戶端負載均衡元件Ribbon
微服務用戶端負載均衡元件Ribbon
微服務用戶端負載均衡元件Ribbon
微服務用戶端負載均衡元件Ribbon

檢視日志可以看到我們重寫的負載均衡方法先從注冊中心擷取到product服務的清單,并且随機選擇一個IP替換url。

2021-03-02 22:26:14.098  INFO 8449 --- [nio-8080-exec-9] com.tuling.config.TulingRestTemplate     : 請求的url路徑為:http://product-center/selectProductInfoById/1
2021-03-02 22:26:14.098  INFO 8449 --- [nio-8080-exec-9] com.tuling.config.TulingRestTemplate     : 調用微服務的名稱:product-center
2021-03-02 22:26:14.098  INFO 8449 --- [nio-8080-exec-9] com.tuling.config.TulingRestTemplate     : 請求path:/selectProductInfoById/1
2021-03-02 22:26:14.098  INFO 8449 --- [nio-8080-exec-9] com.tuling.config.TulingRestTemplate     : 随機選舉的服務IP:http://192.168.1.136:8081
2021-03-02 22:26:14.098  INFO 8449 --- [nio-8080-exec-9] com.tuling.config.TulingRestTemplate     : 替換後的路徑:http://192.168.1.136:8081/selectProductInfoById/1
2021-03-02 22:26:18.477  INFO 8449 --- [io-8080-exec-10] com.tuling.config.TulingRestTemplate     : 請求的url路徑為:http://product-center/selectProductInfoById/1
2021-03-02 22:26:18.477  INFO 8449 --- [io-8080-exec-10] com.tuling.config.TulingRestTemplate     : 調用微服務的名稱:product-center
2021-03-02 22:26:18.477  INFO 8449 --- [io-8080-exec-10] com.tuling.config.TulingRestTemplate     : 請求path:/selectProductInfoById/1
2021-03-02 22:26:18.478  INFO 8449 --- [io-8080-exec-10] com.tuling.config.TulingRestTemplate     : 随機選舉的服務IP:http://192.168.1.136:8082
2021-03-02 22:26:18.478  INFO 8449 --- [io-8080-exec-10] com.tuling.config.TulingRestTemplate     : 替換後的路徑:http://192.168.1.136:8082/selectProductInfoById/1      

Ribbon元件使用

第一步:加入依賴(nacos-client和ribbon)

<!--加入nocas-client-->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!--加入ribbon-->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>      

第二步:在RestTemplate上加上@LoadBalanced注冊

package com.tuling.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate( ) {
        return new RestTemplate();
    }
}      

第三步:寫配置檔案(現在暫時用預設的ribbon負載均衡方法,配置檔案隻寫nacos的就行)

spring:
  datasource:
      druid:
        username: root
        password: 123456
        jdbcUrl: jdbc:mysql://192.168.1.14:3306/tuling-ms-alibaba?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
        driverClassName: com.mysql.jdbc.Driver
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        filters: stat,wall #\u914D\u7F6E\u8FC7\u6EE4\u5668
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.1.5:8848
  application:
    name: order-center
server:
  port: 8080      

第四步:寫Controller

package com.tuling.controller;
import com.tuling.entity.OrderInfo;
import com.tuling.entity.ProductInfo;
import com.tuling.mapper.OrderInfoMapper;
import com.tuling.vo.OrderVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
 * 基于ribbon負載均衡
 */
@RestController
public class OrderInfoController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private OrderInfoMapper orderInfoMapper;
    @RequestMapping("/selectOrderInfoById/{orderNo}")
    public Object selectOrderInfoById(@PathVariable("orderNo") String orderNo) {
        OrderInfo orderInfo = orderInfoMapper.selectOrderInfoById(orderNo);
        if(null == orderInfo) {
            return "根據orderNo:"+orderNo+"查詢沒有該訂單";
        }
        ResponseEntity<ProductInfo> responseEntity = null;
        try {
            responseEntity = restTemplate.getForEntity("http://product-center/selectProductInfoById/"+orderInfo.getProductNo(), ProductInfo.class);
        }catch (Exception e) {
            System.out.println(e.getStackTrace());
        }
        ProductInfo productInfo = responseEntity.getBody();
        if(productInfo == null) {
            return "沒有對應的商品";
        }
        OrderVo orderVo = new OrderVo();
        orderVo.setOrderNo(orderInfo.getOrderNo());
        orderVo.setUserName(orderInfo.getUserName());
        orderVo.setProductName(productInfo.getProductName());
        orderVo.setProductNum(orderInfo.getProductCount());
        return orderVo;
    }
}      

和前面一樣并行起2個product服務:

微服務用戶端負載均衡元件Ribbon
微服務用戶端負載均衡元件Ribbon

Ribbon細粒度自定義配置

場景:order服務需要采用随機算法調用product服務,使用輪詢算法調用pay服務,其他服務使用随機算法調用。

在order服務的配置檔案中指定即可:

spring:
  datasource:
      druid:
        username: root
        password: 123456
        jdbcUrl: jdbc:mysql://192.168.1.14:3306/tuling-ms-alibaba?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
        driverClassName: com.mysql.jdbc.Driver
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        filters: stat,wall #\u914D\u7F6E\u8FC7\u6EE4\u5668
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.1.5:8848
        #自定義負載均衡時使用
        cluster-name: NJ-CLUSTER
        metadata:
          current-version: V1
  application:
    name: order-center
#開啟ribbon饑餓加載,解決微服務調用第一次很慢的情況下
ribbon:
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  eager-load:
    enabled: true
    #可以指定多個微服務用逗号分隔
    clients: product-center,pay-center
#
##自定義Ribbon的細粒度配置
product-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
pay-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
server:
  port: 8080      

自定義負載均衡政策

order服務有V1一個版本,在NJ-Cluster(1個執行個體);product服務有V1,V2兩個版本分别在BJ-Cluster和NJ-Cluster(4個執行個體)。

要求order服務按照以下優先級調用product服務,跨版本不允許調用:

  • 同叢集同版本。
  • 同版本不同叢集。
    微服務用戶端負載均衡元件Ribbon
    微服務用戶端負載均衡元件Ribbon
package com.ribbonconfig;
import com.netflix.loadbalancer.IRule;
import com.tuling.myrule.TheSameClusterPriorityWithVersionRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GlobalRibbonConfig {
    @Bean
    public IRule theSameClusterPriorityRule() {
        return new TheSameClusterPriorityWithVersionRule();  //自定義負載均衡政策
    }
}      

編寫自定義負載均衡政策TheSameClusterPriorityWithVersionRule,繼承AbstractLoadBalancerRule并重寫choose()方法:

package com.tuling.myrule;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
 * 自定義負載均衡,同叢集同版本優先調用,然後是跨叢集同版本
 */
@Slf4j
public class TheSameClusterPriorityWithVersionRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties discoveryProperties;
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
    /**
     * 重寫choose方法,自定義負載均衡政策
     * @param key
     * @return
     */
    @Override
    public Server choose(Object key) {
        try {
            //擷取目前的叢集名稱
            String currentClusterName = discoveryProperties.getClusterName();
            //擷取和目前叢集 相同叢集下,相同版本的 所有執行個體
            List<Instance> theSameClusterNameAndTheSameVersionInstList = getTheSameClusterAndTheSameVersionInstances(discoveryProperties);
            //聲明被調用的執行個體
            Instance toBeChooseInstance;
            //判斷同叢集同版本号的微服務執行個體是否為空
            if(theSameClusterNameAndTheSameVersionInstList.isEmpty()) {
                //如果沒有同叢集同版本的服務,跨叢集調用相同的版本
                toBeChooseInstance = crossClusterAndTheSameVersionInovke(discoveryProperties);
            }else {
                toBeChooseInstance = TulingWeightedBalancer.chooseInstanceByRandomWeight(theSameClusterNameAndTheSameVersionInstList);
                log.info("同叢集同版本調用--->目前微服務所在叢集:{},被調用微服務所在叢集:{},目前微服務的版本:{},被調用微服務版本:{},Host:{},Port:{}",
                        currentClusterName,toBeChooseInstance.getClusterName(),discoveryProperties.getMetadata().get("current-version"),
                        toBeChooseInstance.getMetadata().get("current-version"),toBeChooseInstance.getIp(),toBeChooseInstance.getPort());
            }
            return new NacosServer(toBeChooseInstance);
        } catch (NacosException e) {
            log.error("同叢集優先權重負載均衡算法選擇異常:{}",e);
            return null;
        }
    }
    /**
     * 方法實作說明:擷取相同叢集下,相同版本的 所有執行個體
     * @param discoveryProperties nacos的配置
     * @return: List<Instance>
     * @exception: NacosException
     */
    private List<Instance> getTheSameClusterAndTheSameVersionInstances(NacosDiscoveryProperties discoveryProperties) throws NacosException {
        //目前的叢集的名稱
        String currentClusterName = discoveryProperties.getClusterName();
        String currentVersion = discoveryProperties.getMetadata().get("current-version");
        //擷取所有執行個體的資訊(包括不同叢集的)
        List<Instance> allInstance =  getAllInstances(discoveryProperties);
        List<Instance> theSameClusterNameAndTheSameVersionInstList = new ArrayList<>();
        //過濾相同叢集的
        for(Instance instance : allInstance) {
            if(StringUtils.endsWithIgnoreCase(instance.getClusterName(),currentClusterName)&&
               StringUtils.endsWithIgnoreCase(instance.getMetadata().get("current-version"),currentVersion)) {
                theSameClusterNameAndTheSameVersionInstList.add(instance);
            }
        }
        return theSameClusterNameAndTheSameVersionInstList;
    }
    /**
     * 方法實作說明:擷取被調用服務的所有執行個體
     * @param discoveryProperties nacos的配置
     * @return: List<Instance>
     * @exception: NacosException
     */
    private List<Instance> getAllInstances(NacosDiscoveryProperties discoveryProperties) throws NacosException {
        //第1步:擷取一個負載均衡對象
        BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();
        //第2步:擷取目前調用的微服務的名稱
        String invokedSerivceName = baseLoadBalancer.getName();
        //第3步:擷取nacos clinet的服務注冊發現元件的api
        NamingService namingService = discoveryProperties.namingServiceInstance();
        //第4步:擷取所有的服務執行個體
        List<Instance> allInstance =  namingService.getAllInstances(invokedSerivceName);
        return allInstance;
    }
    /**
     * 方法實作說明:跨叢集環境下 相同版本的
     * @param discoveryProperties
     * @return: List<Instance>
     * @exception: NacosException
     */
    private List<Instance> getCrossClusterAndTheSameVersionInstList(NacosDiscoveryProperties discoveryProperties) throws NacosException {
        //版本号
        String currentVersion = discoveryProperties.getMetadata().get("current-version");
        //被調用的所有執行個體
        List<Instance> allInstance = getAllInstances(discoveryProperties);
        List<Instance>  crossClusterAndTheSameVersionInstList = new ArrayList<>();
        //過濾相同版本
        for(Instance instance : allInstance) {
            if(StringUtils.endsWithIgnoreCase(instance.getMetadata().get("current-version"),currentVersion)) {
                crossClusterAndTheSameVersionInstList.add(instance);
            }
        }
        return crossClusterAndTheSameVersionInstList;
    }
    
    private Instance crossClusterAndTheSameVersionInovke(NacosDiscoveryProperties discoveryProperties) throws NacosException {
        //擷取所有叢集下相同版本的執行個體資訊
        List<Instance>  crossClusterAndTheSameVersionInstList = getCrossClusterAndTheSameVersionInstList(discoveryProperties);
        //目前微服務的版本号
        String currentVersion = discoveryProperties.getMetadata().get("current-version");
        //目前微服務的叢集名稱
        String currentClusterName = discoveryProperties.getClusterName();
        //聲明被調用的執行個體
        Instance toBeChooseInstance = null ;
        //沒有對應相同版本的執行個體
        if(crossClusterAndTheSameVersionInstList.isEmpty()) {
            log.info("跨叢集調用找不到對應合适的版本目前版本為:currentVersion:{}",currentVersion);
            throw new RuntimeException("找不到相同版本的微服務執行個體");
        }else {
            toBeChooseInstance = TulingWeightedBalancer.chooseInstanceByRandomWeight(crossClusterAndTheSameVersionInstList);
            log.info("跨叢集同版本調用--->目前微服務所在叢集:{},被調用微服務所在叢集:{},目前微服務的版本:{},被調用微服務版本:{},Host:{},Port:{}",
                    currentClusterName,toBeChooseInstance.getClusterName(),discoveryProperties.getMetadata().get("current-version"),
                    toBeChooseInstance.getMetadata().get("current-version"),toBeChooseInstance.getIp(),toBeChooseInstance.getPort());
        }
        return toBeChooseInstance;
    }
}      

RestTemplate添加@LoadBalanced注解:

package com.tuling.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class WebConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate( ) {
        return new RestTemplate();
    }
}      

order服務Controller方法和之前一樣。

連續在浏覽器請求order服務,在所有product服務都好的情況下,會優先調用同叢集同版本的:

2021-03-04 23:42:20.492  INFO 9478 --- [nio-8080-exec-2] .m.TheSameClusterPriorityWithVersionRule : 同叢集同版本調用--->目前微服務所在叢集:NJ-CLUSTER,被調用微服務所在叢集:NJ-CLUSTER,目前微服務的版本:V1,被調用微服務版本:V1,Host:192.168.1.136,Port:8081
2021-03-04 23:42:32.408  INFO 9478 --- [nio-8080-exec-4] .m.TheSameClusterPriorityWithVersionRule : 同叢集同版本調用--->目前微服務所在叢集:NJ-CLUSTER,被調用微服務所在叢集:NJ-CLUSTER,目前微服務的版本:V1,被調用微服務版本:V1,Host:192.168.1.136,Port:8081
2021-03-04 23:42:33.029  INFO 9478 --- [nio-8080-exec-5] .m.TheSameClusterPriorityWithVersionRule : 同叢集同版本調用--->目前微服務所在叢集:NJ-CLUSTER,被調用微服務所在叢集:NJ-CLUSTER,目前微服務的版本:V1,被調用微服務版本:V1,Host:192.168.1.136,Port:8081      

将NJ-Cluster,V1版本的product服務下線,此時會去調BJ-Cluster的V1版本的服務:

2021-03-04 23:47:10.242  INFO 9478 --- [nio-8080-exec-8] .m.TheSameClusterPriorityWithVersionRule : 跨叢集同版本調用--->目前微服務所在叢集:NJ-CLUSTER,被調用微服務所在叢集:BJ-CLUSTER,目前微服務的版本:V1,被調用微服務版本:V1,Host:192.168.1.136,Port:8083
2021-03-04 23:47:11.627  INFO 9478 --- [nio-8080-exec-9] .m.TheSameClusterPriorityWithVersionRule : 跨叢集同版本調用--->目前微服務所在叢集:NJ-CLUSTER,被調用微服務所在叢集:BJ-CLUSTER,目前微服務的版本:V1,被調用微服務版本:V1,Host:192.168.1.136,Port:8083
2021-03-04 23:47:12.380  INFO 9478 --- [io-8080-exec-10] .m.TheSameClusterPriorityWithVersionRule : 跨叢集同版本調用--->目前微服務所在叢集:NJ-CLUSTER,被調用微服務所在叢集:BJ-CLUSTER,目前微服務的版本:V1,被調用微服務版本:V1,Host:192.168.1.136,Port:8083      

如果将NJ-Cluster,V1版本的product服務也下線,此時由于剩下的隻有V2版本的product服務,将無法調用:

微服務用戶端負載均衡元件Ribbon