天天看點

【SpringCloud】Ribbon實作負載均衡1. 是什麼2. LB 相關知識3. Ribbon中LB的思路4. 核心元件IRule5.實作方式6. 項目實戰

文章目錄

  • 1. 是什麼
  • 2. LB 相關知識
  • 3. Ribbon中LB的思路
  • 4. 核心元件IRule
  • 5.實作方式
    • 5.1 配置檔案方法
    • 5.2 注解方式
  • 6. 項目實戰
    • 6.1 引依賴
    • 6.2 配置檔案
    • 6.3 在消費者(用戶端)的啟動項配置
    • 6.4 service層調用服務

目前Spring Cloud中服務之間通過Rest方式調用有兩種方式:

  1. feign
  2. RestTemplate+Ribbon

feign的方式更優雅,使用更簡單(内部也使用的ribbon做的負載均衡),但是我們這使用的第二種方案,因為Ribbon可以負載均衡并且可以自定義負載均衡算法

1. 是什麼

  是Netflix釋出的開源項目,主要功能是提供用戶端的軟體負載均衡算法,将Netflix的中間層服務連接配接在一起。在配置檔案中列出Load Balancer(簡稱LB)後面的所有的機器,Ribbon後自動幫助你基于某種規則(如簡單輪詢,随機連接配接等)去連接配接這些機器,也可實作自定義的負載均衡算法。

  Dubbo和SpringCloud中均提供了負載均衡,但SpringCloud的負載均衡算法可以自定義

2. LB 相關知識

  LB負載均衡 (Load Balance),在微服務或分布式叢集中經常用到的一種應用,将使用者的請求平攤到多個服務上,進而達到系統的高可用HA(High Available),常用的負載均衡有軟體NGINX,LVS,硬體F5等。

LB分為:

  1. 集中式LB

    即在服務的消費方和提供方之間使用獨立的LB設施(硬體如F5,軟體如nginx),由該設施負責把通路請求通過某種政策轉發至服務提供方

  2. 程序内LB

    即将LB邏輯繼承到服務消費方,消費方從服務注冊中心擷取哪些位址可用,然後自己在從這些位址中選擇出一個合适的伺服器。Ribbon就屬于程序内LB,它隻是一個類庫,內建于消費方程序,消費方通過它來擷取服務提供方的位址

3. Ribbon中LB的思路

【SpringCloud】Ribbon實作負載均衡1. 是什麼2. LB 相關知識3. Ribbon中LB的思路4. 核心元件IRule5.實作方式6. 項目實戰
  1. 先選擇EurekaServer,優先選擇在同一區域負載較少的EurekaServer
  2. 再根據使用者指定的政策,從EurekaServer取到的服務注冊清單中選擇一個位址

4. 核心元件IRule

IRule:根據特定算法中從服務清單中選取一個要通路的服務,提供了7種政策:

  1. RoundRobinRule: 輪詢
  2. RandomRule: 随機
  3. AvailabilityFilteringRule: 會先過濾掉由于多次通路故障而處于斷路器跳閘狀态的服務,還有并發的連接配接數量超過門檻值的服務,然後對剩餘的服務清單按照輪詢政策進行通路
  4. WeightedResponseTimeRule: 根據平均響應時間計算所有服務的權重,響應時間越快服務權重越大被選中的機率越高。剛啟動時如果統計資訊不足,則使用RoundRobinRule政策,等統計資訊足夠時,會切換到WeightedResponseTimeRule、
  5. RetryRule: 先按照RoundRobinRule的政策擷取服務,如果擷取服務失敗則在指定時間内進行重試,擷取可用服務
  6. BestAvailableRule: 會先過濾掉由于多次通路故障而處于短路器跳閘狀态的服務,然後選擇一個并發量最小的服務
  7. ZoneAvoidanceRule: 複合判斷server所在區域的性能和server的可用性選擇伺服器

也可以自定義政策,步驟為:

  1. 自定義傳回類型為IRule的Bean的配置類
    @Configuration
    public class MySelfRule
    {
    	@Bean
    	public IRule myRule()
    	{
    		//return new RandomRule();// Ribbon預設是輪詢,我自定義為随機
    		//return new RoundRobinRule();// Ribbon預設是輪詢,我自定義為随機
    		
    		return new RandomRule_ZY();// 我自定義為每台機器5次
    	}
    }
    //----------------------自定義路由政策類---------------
    public class RandomRule_ZY extends AbstractLoadBalancerRule
    {
    // total = 0 // 當total==5以後,我們指針才能往下走,
    // index = 0 // 目前對外提供服務的伺服器位址,
    // total需要重新置為零,但是已經達到過一個5次,我們的index = 1
    // 分析:我們5次,但是微服務隻有8001 8002 8003 三台,OK?
    // 
    
    
    private int total = 0; 			// 總共被調用的次數,目前要求每台被調用5次
    private int currentIndex = 0;	// 目前提供服務的機器号
    
    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 = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
    //			server = upList.get(index);
    
    			
    //			private int total = 0; 			// 總共被調用的次數,目前要求每台被調用5次
    //			private int currentIndex = 0;	// 目前提供服務的機器号
                if(total < 5)
                {
    	            server = upList.get(currentIndex);
    	            total++;
                }else {
    	            total = 0;
    	            currentIndex++;
    	            if(currentIndex >= upList.size())
    	            {
    	              currentIndex = 0;
    	            }
                }					
    			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;
    
    }
    
    @Override
    public Server choose(Object key)
    {
    	return choose(getLoadBalancer(), key);
    }
    
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig)
    {
    	// TODO Auto-generated method stub
    
    }
               
    }
  2. 在啟動類上配置自定義的路由政策@RibbonClient,使其啟動時就加載配置
    @SpringBootApplication
    @EnableEurekaClient
    //在啟動該微服務的時候就能去加載我們的自定義Ribbon配置類,進而使配置生效
    //@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)
    @RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)
    public class DeptConsumer80_App
    {
    	public static void main(String[] args)
    	{
    		SpringApplication.run(DeptConsumer80_App.class, args);
    	}
    }
               

注:自定義配置類不能放在@ComponentScan所掃描的目前包下及子包下,否則我們自定義的配置類就會被所有的Ribbon用戶端鎖共享,也就是說達不到特殊化定制的目的了

5.實作方式

5.1 配置檔案方法

配置格式為:

<client>.<nameSpace>.<property>=<value>

  • client為用戶端名稱:服務提供者對外暴露的服務名
  • nameSpace為名稱空間:預設就是ribbon
  • property為屬性名:我們要配置負載均衡政策就是

    NFLoadBalancerRuleClassName cloud-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

### 針對單個服務的 Ribbon 配置
MICROSERVICECLOUD-DEPT:
  ribbon: 
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

           

配置全局負載均衡政策吧前面的服務名去掉就可以

5.2 注解方式

示例如下6
           

6. 項目實戰

6.1 引依賴

<!-- Ribbon相關 -->
<!-- Eureka -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- Ribbon -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!-- Eureka相關 -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
           

6.2 配置檔案

eureka:
  client:
    register-with-eureka: false
    service-url: 
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  
           

6.3 在消費者(用戶端)的啟動項配置

@SpringBootApplication
@EnableEurekaClient
// name:要調用服務(提供者)的名稱,針對單個該服務使用的LB政策,預設是輪詢
@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=RetryRule.class)
public class DeptConsumer80_App
{
	public static void main(String[] args)
	{
		SpringApplication.run(DeptConsumer80_App.class, args);
	}

    @Bean
   	@LoadBalanced//Spring Cloud Ribbon是基于Netflix Ribbon實作的一套用戶端 , 負載均衡的工具。
   	public RestTemplate getRestTemplate()
   	{
   		return new RestTemplate();
   	}

}
           

RestTemplate 也可以寫在統一配置中心上,這樣不用再在每個用戶端都寫了

@Bean
   	@LoadBalanced//Spring Cloud Ribbon是基于Netflix Ribbon實作的一套用戶端 , 負載均衡的工具。
   	public RestTemplate getRestTemplate()
   	{
   		return new RestTemplate();
   	}
           

6.4 service層調用服務

// 注意,這些的是注冊到Eureka上服務的名稱位址,不是通過ip調用的。
// 直接調用服務而不用在關心位址和端口号
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";

	/**
	 * 使用 使用restTemplate通路restful接口非常的簡單粗暴無腦。
	 *  (url, requestMap, ResponseBean.class)這三個參數分别代表 REST請求位址、請求參數、HTTP響應轉換被轉換成的對象類型。
	 */
	@Autowired
	private RestTemplate restTemplate;

	@RequestMapping(value = "/consumer/dept/add")
	public boolean add(Dept dept)
	{
		。。。邏輯代碼
		// 請求位址:服務位址+方法路徑,參數,傳回類型,這樣就實作了調用别的服務
		// 完成真正的通過微服務名字從Eureka薩汗找到服務并通路
		return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
		。。。邏輯代碼
	}
           

繼續閱讀