1. 程式人生 > >SpringCloud學習筆記(2):使用Ribbon負載均衡

SpringCloud學習筆記(2):使用Ribbon負載均衡

簡介

Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡工具,在註冊中心對Ribbon客戶端進行註冊後,Ribbon可以基於某種負載均衡演算法,如輪詢(預設)、隨機、加權輪詢、加權隨機等自動幫助服務消費者呼叫介面。

專案介紹

  1. sc-parent,父模組(請參照SpringCloud學習筆記(1):Eureka註冊中心)
  2. sc-eureka,註冊中心(請參照SpringCloud學習筆記(1):Eureka註冊中心)
  3. sc-provider-random,隨機埠的提供者
  4. sc-consumer,使用Ribbon負載均衡的消費者

隨機埠的提供者

1.在父模組下建立子模組專案sc-provider-random,pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.cf</groupId>
    <artifactId>sc-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>sc-provider-random</artifactId>
  
  <dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  </dependencies>
</project>

2.建立啟動類provider.ProviderApplication:

package provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

3.建立Controller:provider.controller.BookController

package provider.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/book")
@RestController
public class BookController {
    
    @GetMapping("/list")
    public String getBookList(){
        System.out.println("------------我被訪問了-----------");
        return "[\"Java入門到放棄\",\"C++入門到放棄\",\"Python入門到放棄\",\"C入門到放棄\"]";
    }
}

4.建立application.yml:

server:
  port: 0 #預設8080,配置隨機埠需要設定為0

spring:
  application:
    name: sc-provider-random
eureka:
  client:
    serviceUrl: 
      defaultZone: http://localhost:8080/eureka/
  instance:
    instance-id: ${spring.application.name}:${random.value} #例項名隨機生成

5.依次啟動註冊中心sc-eureka以及兩個提供者sc-provider-random,提供者sc-provider-random每次啟動埠都不一樣,可以看到sc-provider-random的兩個例項都在註冊中心註冊:

使用Ribbon負載均衡的消費者

1.在父模組下建立子模組專案sc-consumer,pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.cf</groupId>
    <artifactId>sc-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>sc-consumer</artifactId>
  
  <dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    <!-- spring-cloud-starter-netflix-eureka-client依賴中已經包含ribbon -->
    <!-- <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency> -->
  </dependencies>
</project>

2.建立啟動類consumer.ConsumerApplication:

package consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
    
    //為RestTemplate整合Ribbon,使其具備負載均衡的能力
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

3.建立呼叫提供者服務的Controller:consumer.controller.ConsumerController

package consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    
    @GetMapping("/getBookList")
    public String getBookList(){
        //sc-provider-random 為提供者服務名
        return restTemplate.getForObject("http://sc-provider-random/book/list", String.class);
    }
}

注意:在使用Ribbon負載均衡時,服務名稱不能有下劃線,否則會出現valid hostname異常

4.建立application.yml:

server:
  port: 8083

spring:
  application:
    name: sc-consumer
    
eureka:
  client:
    registerWithEureka: false
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/    

5.依次啟動註冊中心sc-eureka、兩個提供者sc-provider-random、消費者sc-consumer,並訪問http://localhost:8083/getBookList:

多次訪問後,通過控制檯的日誌列印可以發現消費者時通過輪詢的方式訪問兩個提供者例項。

更改負載均衡策略

Ribbon預設使用RoundRobinRule(輪詢)來做為負載均衡策略,我們可以實現IRule介面或者使用Ribbon提供的現成的負載均衡策略來替換預設的輪詢策略。如下,使用隨機訪問的策略來替代預設的輪詢,在消費者啟動類中新增:

    @Bean
    public IRule randomRule(){
        return new RandomRule();//使用隨機訪問的策略來替代預設的輪詢
    }

自定義負載均衡策略

一個自定義負載均衡策略小例子,依舊是輪詢訪問策略,只是每個服務例項訪問5次後才會訪問下一個服務例項。

1.建立類consumer.rule.MyRule:

package consumer.rule;

import java.util.List;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

public class MyRule extends AbstractLoadBalancerRule {
    private int currIndex = 0;//當前服務例項索引
    
    private int currCount = 0;//當前服務例項被訪問次數
    
    private static final int MAX_COUNT = 5;//每個服務例項最大訪問次數為5

    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(upList.size());
            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;

    }

    protected synchronized int chooseRandomInt(int serverCount) {
        currCount++;
        if(currCount > MAX_COUNT){
            currIndex++;
            if(currIndex >= serverCount){
                currIndex = 0;
            }
            currCount = 1;
        }
        return currIndex;
    }

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

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

2.修改配置:

    @Bean
    public IRule randomRule(){
        return new MyRule();
    }

依次啟動註冊中心sc-eureka、兩個提供者sc-provider-random、消費者sc-consumer,並訪問http://localhost:8083/getBookList進行測試