天天看點

六、【SpringCloud】Ribbon:負載均衡(基于用戶端)

狂神-SpringCloud筆記目錄

文章目錄

  • ​​六、Ribbon:負載均衡(基于用戶端)​​
  • ​​6.1 負載均衡以及Ribbon​​
  • ​​6.2 內建Ribbon​​
  • ​​6.3 使用Ribbon實作負載均衡​​

六、Ribbon:負載均衡(基于用戶端)

6.1 負載均衡以及Ribbon

Ribbon是什麼?
  • Spring Cloud Ribbon 是基于Netflix Ribbon 實作的一套用戶端負載均衡的工具。
  • 簡單的說,Ribbon 是 Netflix 釋出的開源項目,主要功能是提供用戶端的軟體負載均衡算法,将 Netflix 的中間層服務連接配接在一起。Ribbon 的用戶端元件提供一系列完整的配置項,如:連接配接逾時、重試等。簡單的說,就是在配置檔案中列出 LoadBalancer (簡稱LB:負載均衡) 後面所有的機器,Ribbon 會自動的幫助你基于某種規則 (如簡單輪詢,随機連接配接等等) 去連接配接這些機器。我們也容易使用 Ribbon 實作自定義的負載均衡算法!
Ribbon能幹嘛?
六、【SpringCloud】Ribbon:負載均衡(基于用戶端)
  • LB,即負載均衡 (LoadBalancer) ,在微服務或分布式叢集中經常用的一種應用。
  • 負載均衡簡單的說就是将使用者的請求平攤的配置設定到多個服務上,進而達到系統的HA(高用)。
  • 常見的負載均衡軟體有 Nginx、Lvs 等等。
  • Dubbo、SpringCloud 中均給我們提供了負載均衡,SpringCloud 的負載均衡算法可以自定義。
  • 負載均衡簡單分類:
  • 集中式LB
  • 即在服務的提供方和消費方之間使用獨立的LB設施,如Nginx(反向代理伺服器),由該設施負責把通路請求通過某種政策轉發至服務的提供方!
  • 程序式 LB
  • 将LB邏輯內建到消費方,消費方從服務注冊中心獲知有哪些位址可用,然後自己再從這些位址中選出一個合适的伺服器。
  • Ribbon 就屬于程序内LB,它隻是一個類庫,內建于消費方程序,消費方通過它來擷取到服務提供方的位址!

6.2 內建Ribbon

springcloud-consumer-dept-80向pom.xml中添加Ribbon和Eureka依賴

<!--Ribbon-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>      

在application.yml檔案中配置Eureka

server:
  port: 80

# Eureka配置
eureka:
  client:
    register-with-eureka: false # 不向Eureka注冊自己 ,因為此處是消費者,隻需要拿
    service-url:
      #可以從以下三個地方去取服務
      defaultZone: http://eureka7003.com:7003/eureka/,http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/      

主啟動類加上@EnableEurekaClient注解,開啟Eureka

package com.wlw.springcloud;

import com.wlw.myrule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

//Ribbon 和 Eureka 整合以後,用戶端可以直接調用服務端的方法,不用關心IP位址和端口号
@SpringBootApplication
@EnableEurekaClient //開啟Eureka 用戶端
public class DeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_80.class,args);
    }
}      

自定義Spring配置類:ConfigBean.java 配置 負載均衡 實作RestTemplate

package com.wlw.springcloud.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
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
// @Configuration --- spring applicationContext.xml  配置類
public class ConfigBean {
    @Bean
    @LoadBalanced //配置負載均衡實作RestTemplate ,開啟 Ribbon
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}      

修改conroller:DeptConsumerController.java

//利用Ribbon來做負載均衡時:我們這裡的位址,應該是一個變量,通過服務名來通路(服務提供者在配置檔案中配置的名字)
    //private static final String REST_URL_PREFIX = "http://localhost:8001";
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";      

6.3 使用Ribbon實作負載均衡

流程圖:

六、【SpringCloud】Ribbon:負載均衡(基于用戶端)

(Ribbon有兩個步驟:1.去Eureka叢集中查找可用的服務清單;2.通過負載均衡的算法,從服務提供者中選擇一個看起來比較OK的)

1.建立兩個服務提供者Moudle:springcloud-provider-dept-8003、springcloud-provider-dept-8002

2.參照springcloud-provider-dept-8001 依次為另外兩個Moudle添加pom.xml依賴 、resourece下的mybatis和application.yml配置,Java代碼

3.啟動所有服務測試(根據自身電腦組態決定啟動服務的個數),通路http://eureka7001.com:7001/檢視結果

六、【SpringCloud】Ribbon:負載均衡(基于用戶端)

測試通路http://localhost/consumer/dept/list 這時候随機通路的是服務提供者8003

六、【SpringCloud】Ribbon:負載均衡(基于用戶端)

再次通路http://localhost/consumer/dept/list這時候随機的是服務提供者8001

六、【SpringCloud】Ribbon:負載均衡(基于用戶端)

以上這種每次通路http://localhost/consumer/dept/list随機通路叢集中某個服務提供者,這種情況叫做輪詢,輪詢算法在SpringCloud中可以自定義。

如何切換或者自定義規則呢?

在springcloud-consumer-dept-80子產品下的ConfigBean中進行配置,切換使用不同的規則

@Configurationpublic 
class ConfigBean {
//@Configuration -- spring  applicationContext.xml    
/**     
* IRule:(這個接口是Ribbon實作負載均衡政策的接口,以下是幾個預設的實作類)     
* * RoundRobinRule 輪詢政策     
* * RandomRule 随機政策     
* * AvailabilityFilteringRule : 會先過濾掉,跳閘,通路故障的服務~,對剩下的進行輪詢~     * RetryRule : 會先按照輪詢擷取服務~,如果服務擷取失敗,則會在指定的時間内進行,重試     
* */    
@Bean    
    public IRule myRule() {
        return new RandomRule();//使用随機政策        
        //return new RoundRobinRule();//使用輪詢政策        
        //return new AvailabilityFilteringRule();//使用輪詢政策        
        //return new RetryRule();//使用輪詢政策    
    }
}      

(自定義後,以上代碼會被删除)

也可以自定義規則,在myRule包下自定義一個配置類MyRule.java,注意:該包不要和主啟動類所在的包同級,要跟啟動類所在包同級:

六、【SpringCloud】Ribbon:負載均衡(基于用戶端)

MyRule.java

package com.wlw.myrule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;

/**
 * @auther wlw
 * @date 2021/9/27
 */
public class MyRule {
    /**
     * IRule:(這個接口是Ribbon實作負載均衡政策的接口,以下是幾個預設的實作類)
     * RoundRobinRule 輪詢政策
     * RandomRule 随機政策
     * AvailabilityFilteringRule : 會先過濾掉,跳閘,通路故障的服務~,對剩下的進行輪詢~
     * RetryRule : 會先按照輪詢擷取服務~,如果服務擷取失敗,則會在指定的時間内進行,重試
     */
    @Bean
    public IRule mySelfRule() {
        return new RandomRule();//使用随機政策
        //return new RoundRobinRule();//使用輪詢政策
        //return new AvailabilityFilteringRule();//使用輪詢政策
        //return new RetryRule();//使用輪詢政策
    }
}      
//Ribbon 和 Eureka 整合以後,用戶端可以直接調用服務端的方法,不用關心IP位址和端口号
@SpringBootApplication
@EnableEurekaClient //開啟Eureka 用戶端
//在微服務啟動的時候就能加載自定義的Ribbon類(自定義的規則會覆寫原有預設的規則)
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MyRule.class)//開啟負載均衡,并指定自定義的規則
public class DeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_80.class,args);
    }
}      
package com.wlw.myrule;

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

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @auther wlw
 * @date 2021/9/27
 */
public class MyRandomRule extends AbstractLoadBalancerRule {

    /**
     * 每個服務通路5次則換下一個服務(總共3個服務)
     * <p>
     * total=0,預設=0,如果=5,指向下一個服務節點
     * index=0,預設=0,如果total=5,index+1
     */
    private int total = 0;//被調用的次數
    private int currentIndex = 0;//目前是誰在提供服務

    //@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    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 (total < 5) {
                server = upList.get(currentIndex);
                total++;
            } else {
                total = 0;
                currentIndex++;
                if (currentIndex > upList.size()) {
                    currentIndex = 0;
                }
                server = upList.get(currentIndex);//從活着的服務中,擷取指定的服務來進行操作
            }

            //======================================================

            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 int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

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

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