1. 程式人生 > >SpringCloud系列之客戶端負載均衡Netflix Ribbon

SpringCloud系列之客戶端負載均衡Netflix Ribbon

### 1. 什麼是負載均衡? 負載均衡是一種基礎的網路服務,它的核心原理是按照指定的負載均衡演算法,將請求分配到後端服務叢集上,從而為系統提供並行處理和高可用的能力。提到負載均衡,你可能想到nginx。對於負載均衡,一般分為服務端負載均衡和客戶端負載均衡 * 服務端負載均衡:在消費者和服務提供方中間使用獨立的代理方式進行負載,有硬體的負載均衡器,比如 F5,也有軟體,比如 Nginx。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200731154204718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) * 客戶端負載均衡:所謂客戶端負載均衡,就是客戶端根據自己的請求情況做負載,本文介紹的Netflix Ribbon就是客戶端負載均衡的元件 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200731161628933.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ### 2. 什麼是Netflix Ribbon? 在[上一章](https://smilenicky.blog.csdn.net/article/details/107567035)的學習中,我們知道了微服務的基本概念,知道怎麼基於Ribbon+restTemplate的方式實現服務呼叫,接著上篇部落格,我們再比較詳細學習客戶端負載均衡Netflix Ribbon,學習本部落格之前請先學習上篇部落格,然後再學習本篇部落格 Ribbon 是由 Netflix 釋出的負載均衡器,它有助於控制 HTTP 和 TCP 的客戶端的行為。Ribbon 屬於客戶端負載均衡。 ### 3. Netflix Ribbon實驗環境準備 環境準備: * JDK 1.8 * SpringBoot2.2.1 * SpringCloud(Hoxton.SR6) * Maven 3.2+ * 開發工具 * IntelliJ IDEA * smartGit 建立一個SpringBoot Initialize專案,詳情可以參考我之前部落格:[SpringBoot系列之快速建立專案教程](https://blog.csdn.net/u014427391/article/details/102870300) 可以引入Eureka Discovery Client,也可以單獨新增Ribbon ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200729134839650.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) Spring Cloud Hoxton.SR6版本不需要引入spring-cloud-starter-netflix-ribbon,已經預設整合 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200728113533472.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 也可以單獨新增Ribbon依賴: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020072913530896.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 本部落格的是基於`spring-cloud-starter-netflix-eureka-client`進行試驗,試驗前要執行eureka服務端,eureka服務提供者,程式碼請參考[上一章部落格](https://smilenicky.blog.csdn.net/article/details/107567035) 補充:IDEA中多例項執行方法 step1:如圖,不要加上勾選 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200728135146506.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) step2:指定不同的server埠和例項id,如圖: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200728141342694.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 啟動成功後,是可以看到多個例項的 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200729163601785.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ### 4. Netflix Ribbon API使用 使用`LoadBalancerClient `: ```java @Autowired LoadBalancerClient loadBalancerClient; @Test void contextLoads() { ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER"); URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort())); System.out.println(uri.toString()); } ``` 構建`BaseLoadBalancer `例項例子: ```java @Test void testLoadBalancer(){ // 服務列表 List serverList = Arrays.asList(new Server("localhost", 8083), new Server("localhost", 8084)); // 構建負載例項 BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList); loadBalancer.setRule(new RandomRule()); for (int i = 0; i < 5; i++) { String result = LoadBalancerCommand.builder().withLoadBalancer(loadBalancer).build() .submit(new ServerOperation() { public Observable call(Server server) { try { String address = "http://" + server.getHost() + ":" + server.getPort()+"/EUREKA-SERVICE-PROVIDER/api/users/mojombo"; System.out.println("呼叫地址:" + address); return Observable.just(""); } catch (Exception e) { return Observable.error(e); } } }).toBlocking().first(); System.out.println("result:" + result); } } ``` ### 5. 負載均衡@LoadBalanced Ribbon負載均衡實現,RestTemplate 要加上`@LoadBalanced` ```java package com.example.springcloud.ribbon.configuration; 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; /** *
 *  RestConfiguration
 * 
* *
 * @author mazq
 * 修改記錄
 *    修改後版本:     修改人:  修改日期: 2020/07/31 09:43  修改內容:
 * 
*/ @Configuration public class RestConfiguration { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } } ``` yaml配置: ```yaml server: port: 8082 spring: application: name: eureka-service-consumer eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ fetch-registry: true register-with-eureka: false healthcheck: enabled: false instance: status-page-url-path: http://localhost:8761/actuator/info health-check-url-path: http://localhost:8761/actuator//health prefer-ip-address: true instance-id: eureka-service-consumer8082 ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200727181930244.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 關鍵點,使用SpringCloud的`@LoadBalanced`,才能調http://EUREKA-SERVICE-PROVIDER/api/users/? 介面的資料,瀏覽器是不能直接調的 ```java import com.example.springcloud.ribbon.bean.User; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import java.net.URI; @SpringBootApplication @EnableEurekaClient @RestController @Slf4j public class SpringcloudRibbonApplication { @Autowired RestTemplate restTemplate; public static void main(String[] args) { SpringApplication.run(SpringcloudRibbonApplication.class, args); } @GetMapping("/findUser/{username}") public User index(@PathVariable("username")String username){ return restTemplate.getForObject("http://EUREKA-SERVICE-PROVIDER/api/users/"+username,User.class); } } ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200729175325517.png) ### 6. 定製Netflix Ribbon client 具體怎麼定製?可以參考官網,`@RibbonClient`指定定製的配置類既可 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200731171500701.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ```java package com.example.springcloud.ribbon.configuration; import com.example.springcloud.ribbon.component.MyRule; import com.netflix.loadbalancer.IPing; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.PingUrl; import org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** *
 *   Ribbon Clients configuration
 * 
* *
 * @author mazq
 * 修改記錄
 *    修改後版本:     修改人:  修改日期: 2020/07/29 14:22  修改內容:
 * 
*/ //@Configuration(proxyBeanMethods = false) //@IgnoreComponentScan public class RibbonClientConfiguration { // @Autowired // IClientConfig config; @Bean public IRule roundRobinRule() { return new MyRule(); } @Bean public ZonePreferenceServerListFilter serverListFilter() { ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); filter.setZone("myTestZone"); return filter; } @Bean public IPing ribbonPing() { return new PingUrl(); } } ``` 在Application類加上`@RibbonClient`,name是為服務名稱,跟bootstrap.yml配置的一樣既可 ```java @RibbonClient(name = "eureka-service-provider",configuration = RibbonClientConfiguration.class) ``` 特別注意:官網這裡特意提醒,這裡的意思是說@RibbonClient指定的配置類必須加`@Configuration`(不過在Hoxton.SR6版本經過我的驗證,其實是可以不加的,加了反而可能報錯),@ComponentScan掃描要排除自定義的配置類,否則,它由所有@RibbonClients共享。如果你使用@ComponentScan(或@SpringBootApplication) ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020073117185037.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 其實就是想讓我們排除這個配置的全域性掃描,所以我們可以進行編碼,寫個註解類`@IgnoreComponentScan `,作用於類,指定`@Target(ElementType.TYPE)` ```java package com.example.springcloud.ribbon.configuration; import java.lang.annotation.*; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface IgnoreComponentScan { } ``` 加上自定義的註解類 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200731172544147.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 任何在Application加上程式碼,避免全域性掃描: ```java @ComponentScan(excludeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value= IgnoreComponentScan.class)}) ``` ### 7. Netflix Ribbon常用元件 ps:介紹Netflix Ribbon的負載策略之前,先介紹Netflix Ribbon常用元件及其作用: | 元件 | 作用 | |--|--| | ILoadBalancer|定義一系列的操作介面,比如選擇服務例項。| | IRule|負載演算法策略,內建演算法策略來為服務例項的選擇提供服務。| | ServerList|負責服務例項資訊的獲取(可以獲取配置檔案中的,也可以從註冊中心獲取。)| | ServerListFilter|過濾掉某些不想要的服務例項資訊。| | ServerListUpdater| 更新本地快取的服務例項資訊。| | IPing| 對已有的服務例項進行可用性檢查,保證選擇的服務都是可用的。| ### 8. 定製Netflix Ribbon策略 因為服務提供者是多例項的,所以再寫個介面測試,呼叫了哪個例項,來看看Netflix Ribbon的負載策略 ```java @Autowired LoadBalancerClient loadBalancerClient; @GetMapping(value = {"/test"}) public String test(){ ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER"); URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort())); System.out.println(uri.toString()); return uri.toString(); } ``` 部署成功,多次呼叫,可以看到每次呼叫的服務例項都不一樣?其實Netflix Ribbon預設是按照輪詢的方式呼叫的 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200729175325517.png) 要定製Netflix Ribbon的負載均衡策略,需要實現AbstractLoadBalancerRule抽象類,下面給出類圖: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200731165944401.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) Netflix Ribbon內建瞭如下的負載均衡策略,引用[https://juejin.im/post/6854573215587500045](https://juejin.im/post/6854573215587500045)的歸納: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200731171002982.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ok,接著我們可以在配置類,修改規則 ```java @Bean public IRule roundRobinRule() { return new BestAvailableRule(); } ``` 測試,基本都是調8083這個例項,因為這個例項效能比較好 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200729170550341.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 顯然,也可以自己寫個策略類,程式碼參考`com.netflix.loadbalancer.RandomRule`,網上也有很多例子,思路是修改RandomRule原來的策略,之前隨機調服務例項一次,現在改成每調5次後,再調其它的服務例項 ```java package com.example.springcloud.ribbon.component; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; public class MyRule extends AbstractLoadBalancerRule { // 總共被呼叫的次數,目前要求每臺被呼叫5次 private int total = 0; // 當前提供服務的機器號 private int index = 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 upList = lb.getReachableServers(); // 獲取所有服務列表 List allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { // 沒有獲取到服務 return null; } //int index = chooseRandomInt(serverCount); //server = upList.get(index); if(total < 5) { server = upList.get(index); total++; }else { total = 0; index++; if(index >= upList.size()) { index = 0; } } if (server == null) { // 釋放執行緒 Thread.yield(); continue; } if (server.isAlive()) { return (server); } 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) { } } ``` 修改IRule ,返回MyRule ```java @Bean public IRule roundRobinRule() { return new MyRule(); } ``` 附錄: ok,本部落格參考官方教程進行實踐,僅僅作為入門的學習參考資料,詳情可以參考Spring Cloud官方文件[https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#customizing-the-ribbon-client](https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#customizing-the-ribbon-client) 程式碼例子下載:[code download](https://github.com/u014427391/springCloudExamples/tree/master/springcloud-ribbon) 優質學習資料參考: * Ribbon負載均衡 -> 原始碼剖析:[Ribbon負載均衡 -> 原始碼剖析 ](https://juejin.im/post/6854573215587500045) * 方誌鵬大佬系列Spring Cloud部落格:[https://www.fangzhipeng.com/spring-cloud.html](https://www.fangzhipeng.com/spring-cloud.html) * 使用Spring Cloud與Docker實戰微服務:[https://eacdy.gitbooks.io/spring-cloud-book/content/](https://eacdy.gitbooks.io/spring-cloud-book/content/) * 程式設計師DD大佬系列Spring Cloud部落格:[http://blog.didispace.com/spring-cloud-learning/](http://blog.didispace.com/spring-cloud-le