此文章很大部分轉載https://www.mrhelloworld.com/,博主均測試通過
什么是 Ribbon
Ribbon 是一個基於 HTTP 和 TCP 的 客服端負載均衡工具,它是基於 Netflix Ribbon 實現的。
它不像 Spring Cloud 服務注冊中心、配置中心、API 網關那樣獨立部署,但是它幾乎存在於每個 Spring Cloud 微服務中。包括 Feign 提供的聲明式服務調用也是基於該 Ribbon 實現的。
Ribbon 默認提供很多種負載均衡算法,例如輪詢、隨機等等。甚至包含自定義的負載均衡算法。
Ribbon 解決了什么問題
Ribbon 提供了一套微服務的負載均衡解決方案。
負載均衡不同方案的區別
目前業界主流的負載均衡方案可分成兩類:
- 集中式負載均衡(服務器負載均衡),即在 consumer 和 provider 之間使用獨立的負載均衡設施(可以是硬件,如 F5,也可以是軟件,如 nginx),由該設施負責把訪問請求通過某種策略轉發至 provider;
- 進程內負載均衡(客戶端負載均衡),將負載均衡邏輯集成到 consumer,consumer 從服務注冊中心獲知有哪些地址可用,然后自己再從這些地址中選擇出一個合適的 provider。Ribbon 屬於后者,它只是一個類庫,集成於 consumer 進程,consumer 通過它來獲取 provider 的地址。
集中式負載均衡
進程內負載均衡
Ribbon 負載均衡策略
輪詢策略(默認)
策略對應類名:RoundRobinRule
實現原理:輪詢策略表示每次都順序取下一個 provider,比如一共有 5 個 provider,第 1 次取第 1 個,第 2 次取第 2 個,第 3 次取第 3 個,以此類推。
權重輪詢策略
策略對應類名:WeightedResponseTimeRule
實現原理:
- 根據每個 provider 的響應時間分配一個權重,響應時間越長,權重越小,被選中的可能性越低。
- 原理:一開始為輪詢策略,並開啟一個計時器,每 30 秒收集一次每個 provider 的平均響應時間,當信息足夠時,給每個 provider 附上一個權重,並按權重隨機選擇 provider,高權越重的 provider 會被高概率選中。
隨機策略
策略對應類名:RandomRule
實現原理:從 provider 列表中隨機選擇一個。
最少並發數策略
策略對應類名:BestAvailableRule
實現原理:選擇正在請求中的並發數最小的 provider,除非這個 provider 在熔斷中。
重試策略
策略對應類名:RetryRule
實現原理:其實就是輪詢策略的增強版,輪詢策略服務不可用時不做處理,重試策略服務不可用時會重新嘗試集群中的其他節點。
可用性敏感策略
策略對應類名:AvailabilityFilteringRule
實現原理:過濾性能差的 provider
- 第一種:過濾掉在 Eureka 中處於一直連接失敗的 provider。
- 第二種:過濾掉高並發(繁忙)的 provider。
區域敏感性策略
策略對應類名:ZoneAvoidanceRule
實現原理:
- 以一個區域為單位考察可用性,對於不可用的區域整個丟棄,從剩下區域中選可用的 provider。
- 如果這個 ip 區域內有一個或多個實例不可達或響應變慢,都會降低該 ip 區域內其他 ip 被選中的權 重。
Ribbon 入門案例
eureka-demo
聚合工程。SpringBoot 2.2.4.RELEASE
、Spring Cloud Hoxton.SR1
。
Ribbon 中對於集群的服務采用的負載均衡策略默認是輪詢。
創建項目
使用學習 Eureka 時的 eureka-demo
項目,在該項目中創建子項目 service-provider02
添加依賴
<?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.example</groupId> <artifactId>service-provider02</artifactId> <version>1.0-SNAPSHOT</version> <!-- 繼承父依賴 --> <parent> <groupId>com.example</groupId> <artifactId>eureka-demo</artifactId> <version>1.0-SNAPSHOT</version> </parent> <!-- 項目依賴 --> <dependencies> <!-- netflix eureka client 依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- spring boot web 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- lombok 依賴 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!-- spring boot actuator 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- spring boot test 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </project>
配置文件application.yml
server:
port: 7071 # 端口
spring:
application:
name: service-provider # 應用名稱(集群下相同)
# 配置 Eureka Server 注冊中心
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址注冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
client:
service-url: # 設置服務注冊中心地址
defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/
# 度量指標監控與健康檢查
management:
endpoints:
web:
exposure:
include: shutdown # 開啟 shutdown 端點訪問
endpoint:
shutdown:
enabled: true # 開啟 shutdown 實現優雅停服
服務及啟動類
將所有代碼復制粘貼一份至 server-provider02,修改啟動類名稱即可.為了更直觀的看到負載均衡的效果,我們在 service-consumer
項目中將服務地址打印至控制台。
注冊中心
運行完整的 Eureka 環境,訪問:http://localhost:8761/ 可以看到現在已經有兩個服務提供者。
消費服務
多次訪問:http://localhost:9090/order/1 可以看到默認使用的是輪詢策略。
Ribbon 負載均衡策略設置
這里是ribbon負載均衡默認的實現, 由於是筆記的關系,這里不好測試,只能你們自己去測試一下了, 具體怎么 使用呢?
全局
在啟動類或配置類中注入負載均衡策略對象。所有服務請求均使用該策略。
@Bean
public IRule iRule(){
return new RoundRobinRule();
}
多次訪問:http://localhost:9090/order/1 結果如下:
局部
修改配置文件指定服務的負載均衡策略。格式:服務應用名.ribbon.NFLoadBalancerRuleClassName
# 負載均衡策略 # service-provider 為調用的服務的名稱 service-provider: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
自定義負載均衡策略:
我們剛剛講過,只要實現了IRule就可以完成自定義負載均衡,至於具體怎么來,我們先看看他默認的實現
/* * * Copyright 2013 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.netflix.loadbalancer; import com.netflix.client.config.IClientConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * The most well known and basic load balancing strategy, i.e. Round Robin Rule. * * @author stonse * @author Nikos Michalakis <nikos@netflix.com> * */ public class RoundRobinRule extends AbstractLoadBalancerRule { private AtomicInteger nextServerCyclicCounter; private static final boolean AVAILABLE_ONLY_SERVERS = true; private static final boolean ALL_SERVERS = false; private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class); public RoundRobinRule() { nextServerCyclicCounter = new AtomicInteger(0); } public RoundRobinRule(ILoadBalancer lb) { this(); setLoadBalancer(lb); } public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { log.warn("no load balancer"); return null; } Server server = null; int count = 0; while (server == null && count++ < 10) { List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if ((upCount == 0) || (serverCount == 0)) { log.warn("No up servers available from load balancer: " + lb); return null; } int nextServerIndex = incrementAndGetModulo(serverCount); server = allServers.get(nextServerIndex); if (server == null) { /* Transient. */ Thread.yield(); continue; } if (server.isAlive() && (server.isReadyToServe())) { return (server); } // Next. server = null; } if (count >= 10) { log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; } /** * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}. * * @param modulo The modulo to bound the value of the counter. * @return The next value. */ private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextServerCyclicCounter.get(); int next = (current + 1) % modulo; if (nextServerCyclicCounter.compareAndSet(current, next)) return next; } } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } }
我們來看看這個類AbstractLoadBalancerRule
/** * Class that provides a default implementation for setting and getting load balancer * @author stonse * */ public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware { private ILoadBalancer lb; @Override public void setLoadBalancer(ILoadBalancer lb){ this.lb = lb; } @Override public ILoadBalancer getLoadBalancer(){ return lb; } }
這里我們能發現,還是我們上面所說過的 實現了IRule就能夠自定義負載均衡即使是他默認的策略也實現了IRule 我們可以直接把代碼copy過來改動一點
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.Random; public class LubanRule extends AbstractLoadBalancerRule { //原來是純隨機策略 我們現在改為。 如果一個下標已經被隨機到了2次了,第三次還是同樣的下標的話,那就再隨機一次 Random rand; private int lastIndex = -1; private int nowIndex = -1; private int skipIndex = -1; public LubanRule() { rand = new Random(); } /** * Randomly choose from all living servers */ 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); System.out.println("當前下標為:"+index); if (skipIndex>=0&&index == skipIndex) { System.out.println("跳過"); index = rand.nextInt(serverCount); System.out.println("跳過后的下標:"+index); } skipIndex=-1; nowIndex = index; if (nowIndex == lastIndex) { System.out.println("下一次需要跳過的下標"+nowIndex); skipIndex = nowIndex; } lastIndex = nowIndex; 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; } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } }
這里我們就把自己寫的Rule給new出來交給spring 就好了
@Bean public IRule iRule(){ return new LubanRule(); }
具體測試的話就不測試了, 那個效果放在筆記上不太明顯,可以自己把代碼copy過去測試一下
Ribbon 點對點直連
點對點直連是指繞過注冊中心,直接連接服務提供者獲取服務,一般在測試階段使用比較多。
添加依賴
在調用方 pom 文件中引入 ribbon
依賴,需要注意的是如果 pom 中有 Eureka 的依賴,則需要去除 Eureka 的依賴。
<!-- netflix ribbon 依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
配置文件
配置文件中關閉 Eureka,添加直連的服務地址。如果不設置負載均衡策略默認使用輪詢策略。
# 負載均衡策略
# service-provider 為調用的服務的名稱
service-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
# 點對點直連模式,指定具體的 Provider 服務列表,多個用逗號隔開
listOfServers: http://localhost:7070,http://localhost:7071
# 關閉 Eureka 實現 Ribbon 點對點直連
ribbon:
eureka:
enabled: false # false:關閉,true:開啟
訪問
關閉 Eureka 注冊中心,服務提供者由於無法連接至注冊中心所以會報連接異常。但是服務是可以正常可消費的,所以目前使用的是點對點的方式來進行調用的。
多次訪問:http://localhost:9090/order/1 結果如下:
至此 Ribbon 負載均衡所有的知識點就講解結束了。