參考:<<重新定義springcloud>>
各個視頻
什麼是ribbon?它解決了什麼問題
- Ribbon是一個基於HTTP和TCP的客戶端負載均衡工具,他是基於Netflix Ribbon實現的。
- 它不像spring cloud服務註冊中心,配置中心,API網關那樣獨立部署,但是它幾乎在每一個spring cloud微服務中,包括feign提供的聲明式服務調用也是基於Ribbon實現的。
- ribbon提供了很多種負載均衡算法,例如 輪詢、隨機 等等。甚至是包含自定義的負載均衡算法。
- 他解決了微服務的負載均衡的問題
負載均衡解決方案的分類
目前業界主流的負載均衡方案課分爲倆類:
1、集中式負載均衡,就是在consumer和provider之間使用獨立的負載均衡設施(可以是硬件,如F5,也可以是軟件 ,nginx)由這些設施 負責把訪問請求 通過 某種策略轉發至provider
2、進程內負載均衡,將負載均衡邏輯集成到consumer,consumer從註冊中心獲取有哪些服務地址,從中通過某種策略挑選出一個provider
Ribbon 就是屬於後者,他只是一個類庫,集成於consumer進程,consumer通過他來獲取到provider的地址
他們的區別架構圖
Ribbon的負載均衡算法策略
id | 命名 | 策略類 | 描述 |
1 | 輪詢算法 | RoundRibbonRule | 按順序循環選擇server |
2 | 隨機算法 | randomRule | 隨機選擇server |
3 | 重試策略 | retryRule | 在一個配置時間內當選擇的server不成功,則一直嘗試選擇一個可用的server |
4 | 最低併發策略 | BestAvailableRule | 逐個考擦server,如果server斷路器打開,則忽略,選擇請求中併發最小的server, |
5 | 可用過濾策略 | availabilityFilteringRule | 過濾掉一直連接失敗並被標記爲ciruruit tripped的server,過濾掉哪些高併發連接的server(active connections超過配置的閾值) |
6 | 響應時間加權策略 | responseTimeWeightRule | 根據server的響應時間分配權重,響應時間越長,權重越低,響應時間越短,權重越高,被選中的概率就越高,這個策略很貼切,綜合了。網絡 磁盤 IO等因素 |
7 | 區域權衡策略 | zoneavoidanceRule |
綜合判斷server所在區域的性能和server的可用性輪詢選擇server,判斷一個區域是否可用,如果不可用直接將這個區域的sever都丟棄 如:多機房情況 |
使用RIbbon實現的入門實例:
1.先創建一個父工程:只有pom文件 hello-springcloud
<?xml version="1.0" encoding="UTF-8"?> <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> <groupId>com.xiaodao</groupId> <artifactId>hello-springcloud</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>eureka_server</module> <module>client-a</module> <module>client-a</module> <module>ribbon-client</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> </parent> <!-- 利用傳遞依賴,公共部分 --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- springboot web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.創建一個eurekaserver服務:client-a
pom文件:
<?xml version="1.0" encoding="UTF-8"?> <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"> <parent> <artifactId>hello-springcloud</artifactId> <groupId>com.xiaodao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>eureka_server</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
bootstrap.yml 和啓動類
server: port: 8888 eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
/**
* Created by xiaodao
* date: 2019/7/17
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
3.創建一個provider
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
啓動類:
@SpringBootApplication @EnableDiscoveryClient public class ClientAApplication { public static void main(String[] args) { SpringApplication.run(ClientAApplication.class, args); } }
controller:
package com.xiaodao.controller; import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @GetMapping("/add") public String add(Integer a, Integer b, HttpServletRequest request){ return " From Port: "+ request.getServerPort() + ", Result: " + (a + b); } }
3.創建第3個項目:ribbon-client
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
bootstrap.yml
spring: application: name: ribbon-loadbalancer server: port: 7777 eureka: client: serviceUrl: defaultZone: http://localhost:8888/eureka/
#默認是hostname註冊,改成ip註冊
instance: prefer-ip-address: true
啓動類:
@SpringBootApplication @EnableDiscoveryClient public class RibbonLoadbalancerApplication { public static void main(String[] args) { SpringApplication.run(RibbonLoadbalancerApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
testController:
@RestController public class TestController { @Autowired private RestTemplate restTemplate; @GetMapping("/add") public String add(Integer a, Integer b) { String result = restTemplate .getForObject("http://CLIENT-A/add?a=" + a + "&b=" + b, String.class); System.out.println(result); return result; } }
現在我們的項目都創建完成了.我們啓動倆個client-a不過端口不一樣.啓動ribbon-client 啓動 hello-springcloud
當我們請求localhost:7777/add?a=1&b=10 的時候我們可以看到打印的日誌:
From Port: 7070, Result: 11 From Port: 7071, Result: 11 From Port: 7070, Result: 11 From Port: 7071, Result: 11 From Port: 7070, Result: 11 From Port: 7071, Result: 11 From Port: 7070, Result: 11
換一箇中策略來實現
前面提到過那麼多策略模式.我們模式的是輪詢模式,現在來挑選一個最簡單的可以看出來的隨機
我們有配置文件和java倆種配置
@Bean public IRule ribbonRule() { return new RandomRule();//這裏配置策略,和配置文件對應 }
配置文件方式:
spring: application: name: ribbon-loadbalancer server: port: 7777 eureka: client: serviceUrl: defaultZone: http://localhost:8888/eureka/ instance: prefer-ip-address: true CLIENT-A: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
點對點直連,讓ribbon調試更加方便快捷
什麼是點對點直連?
cusumer和provider點對點直連不經過註冊中心,爲什麼呢?因爲開發環境都是有eureka來做註冊中心,開發中大家的idea都是啓動的,你要和對方進行聯調,很難聯調
如果我和張三聯調.負載均衡經常發給李四和王五,這種情況沒法聯調.有人說我要和張三聯調,讓李四和王五先停下來.這怎麼行?
如何實現呢?
1.在consumer就是你要調用對方的自己的服務:
將uereka移除
<!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>-->
@SpringBootApplication //@EnableDiscoveryClient public class RibbonLoadbalancerApplication { public static void main(String[] args) { SpringApplication.run(RibbonLoadbalancerApplication.class, args); } @Bean // @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } /*@Bean public IRule ribbonRule() { return new RandomRule();//這裏配置策略,和配置文件對應 }*/ }
pom文件:
spring: application: name: ribbon-loadbalancer server: port: 8082 #eureka: # client: # serviceUrl: # defaultZone: http://localhost:8888/eureka/ # # instance: # prefer-ip-address: true #禁用eureka ribbon: eureka: enabled: false CLIENT-A: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule listOfServers: http://127.0.0.1:7070,http://127.0.0.1:7071
在調用的時候需要這麼寫:
@RestController public class TestController { @Autowired private RestTemplate restTemplate; @Autowired private LoadBalancerClient loadBalancerClient;//ribbon 負載均衡客戶端 @GetMapping("/add") public String add(Integer a, Integer b) { ServiceInstance si=loadBalancerClient.choose("CLIENT-A"); StringBuffer sb=new StringBuffer(""); sb.append("http://"); sb.append(si.getHost()); sb.append(":"); sb.append(si.getPort()); sb.append("/add?a="+a+"&b="+b); System.out.println(sb.toString()); String result = restTemplate.getForObject(sb.toString(), String.class); // String result = restTemplate // .getForObject("http://CLIENT-A/add?a=" + a + "&b=" + b, String.class); System.out.println(result); return result; } }
這個時候,就可以實現點對點的聯調.
關於@loadBanced的實現.
就是基於實際是通過實現 ClientHttpRequestInterceptor 實現的
LoadBalancerInterceptor
LoadBalancerClient
RibbonLoadBalancerClient
然後經過LoadBalancerAutoConfiguration自動配置將loadbalanceInterceptor變爲resttemplate的的攔截器
@Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } }
這裏不做多講.畢竟在下也是隻知皮毛.