目錄
什麼是Ribbon
Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端 負載均衡的工具。
簡單的說,Ribbon是Netflix發佈的開源項目,主要功能是提供客戶端的軟件負載均衡算法,將Netflix的中間層服務連接在一起。
Ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等。簡單的說,就是在配置文件中列出Load Balancer(簡稱LB)後面所有的機器,
Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機連接等)去連接這些機器。我們也很容易使用Ribbon實現自定義的負載均衡算法。
常見的負載均衡軟件有:Nginx,LVS,硬件有F5(一個字貴)等
在深圳很多肯德基/麥當勞,都有客戶端/人工點餐,在多個客戶端使用中,正常(一般)人都會去人少的那一臺機器,這樣效率更高;Ribbon就是負責這個效率的
Ribbon做什麼用的
LB負載均衡 分兩種
集中式LB(偏硬件)
即在服務的消費方和提供方之間使用獨立的LB設施(可以是硬件,如F5, 也可以是軟件,如nginx), 由該設施負責把訪問請求通過某種策略轉發至服務的提供方;
進程內LB(偏軟件)
將LB邏輯集成到消費方,消費方從服務註冊中心獲知有哪些地址可用,然後自己再從這些地址中選擇出一個合適的服務器。
Ribbon就屬於進程內LB,它只是一個類庫,集成於消費方進程,消費方通過它來獲取到服務提供方的地址。
點擊進入 Ribbon在github地址
架構說明:
Ribbon在工作時分成兩步
第一步先選擇 EurekaServer ,它優先選擇在同一個區域內負載較少的server.
第二步再根據用戶指定的策略,在從server取到的服務註冊列表中選擇一個地址。
其中Ribbon提供了多種策略:比如輪詢、隨機和根據響應時間加權。
Ribbon的初步配置
修改springcloud-consumer-dept-80工程/客戶端
完成真正的通過微服務名字從eureka上找到並訪問
需要獲取新的功能/插件 (spring boot項目中)
-
GAV-maven pom.xml文件 jar包導入
-
註解支持 對應的@EnableXXX
③ 編碼 業務
<dependencies>
<dependency><!-- 自己定義的api -->
<groupId>com.jiangjy.springcloud</groupId>
<artifactId>springcloud-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Ribbon相關,詳情見文檔 -->
<!-- eureka客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<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>
<!-- 修改後立即生效,熱部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
修改application.yml 追加eureka的服務註冊地址
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
對ConfigBean進行新註解@LoadBalanced 獲得Rest時加入Ribbon的配置
主啓動類DeptConsumer80_App添加@EnableEurekaClient
修改DeptController_Consumer客戶端訪問類
先啓動3個eureka集羣后,再啓動springcloud-provider-dept-8001並註冊進eureka
啓動springcloud-consumer-dept-80
測試
http://localhost/consumer/dept/getById/1
http://localhost/consumer/dept/getAll
小總結: Ribbon和Eureka整合後Consumer可以直接調用服務而不用再關心地址和端口號
Ribbon負載均衡
- 參考springcloud-provider-dept-8001,新建兩份,分別命名爲8002,8003
- 新建8002/8003數據庫,各自微服務分別連各自的數據庫
- 修改8002/8003各自YML: 端口/數據庫連接
備註:對外暴露的統一服務器實例名:一定不能改
啓動3個eureka集羣配置區
啓動3個Dept微服務啓動並各自測試通過
http://localhost:8001/dept/getAll
http://localhost:8002/dept/getAll
http://localhost:8003/dept/getAll
啓動springcloud-consumer-dept-80
客戶端通過Ribbon完成負載均衡並訪問上一步的Dept微服務
總結:Ribbon其實就是一個軟負載均衡的客戶端組件,
他可以和其他所需請求的客戶端結合使用,和eureka結合只是其中的一個實例。
Ribbon核心組件IRule
Rule:根據特定算法中從服務列表中選取一個要訪問的服務
默認自帶的7種方法:
RoundRobinRule 輪詢
RandomRule 隨機
AvailabilityFilteringRule 會先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,還有併發的連接數量超過閾值的服務,然後對剩餘的服務列表按照輪詢策略進行訪問
WeightedResponseTimeRule 根據平時響應時間計算所有服務的權重,響應時間越快服務權重越大被選中的概率越高。剛啓動時如果統計信息不足,則使用RoundRobinRule策略,等統計信息足夠,會切換到WeightedResponseTimeRule
RetryRule 先按照RoundRobinRule的策略獲取服務,如果獲取服務失敗則在指定時間內進行充實,獲取可用的服務
BestAvailableRule 會先過濾掉由於多次訪問故障而處於熔斷器跳閘狀態的服務,然後選擇一個併發量最小的服務
ZoneAvoidanceRule 默認規則,複合判斷server所在區域的性能和server的可用性選擇服務器
在ConfigBean.java中
@Bean
public IRule myRule() {
return new RoundRobinRule();//輪詢算法
// return new RandomRule();//使用隨機算法替代默認的輪詢算法
// return new RetryRule();
}
Ribbon自定義
修改springcloud-model-consumer-dept-80
主啓動類加@RibbonClient
在啓動該微服務的時候就能去加載我們的自定義Ribbon配置類,從而使配置生效,形如:@RibbonClient(name=”SPRINGCLOUD-MODEL-DEPT”,configuration=MySelfRule.class)
注意配置細節
官方文檔明確給出了警告:這個自定義配置類不能放在@ComponentScan所掃描的當前包下以及子包下,否則我們自定義的這個配置類就會被所有的Ribbon客戶端所共享,也就是說:我們達不到特殊化定製的目的;而@ComponentScan 就在主啓動類的@SpringBootApplication 裏面
步驟
- 新建package com.jiangjy.myrule
新建自定義Ribbon的規則類MySelfRule.java
package com.jiangjy.myrule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
@Configuration //配置spring.xml形式的註解
public class MySelfRult {
@Bean //交給容器管理
public IRule myRule() {
return new RandomRule();//Ribbon默認輪詢,自定義隨機
}
}
修改主啓動類
package com.jiangjy.springcloud;
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;
import com.jiangjy.myrule.MySelfRult;
@SpringBootApplication
@EnableEurekaClient //和eureka整合並使用客戶端
//在啓動微服務的時候就能去加載我們自定義的Ribbon配置類,從而使配置生效
@RibbonClient(name="SPRINGCLOUD-MODEL-DEPT",configuration=MySelfRult.class)
public class DeptConsumer80_APP {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80_APP.class, args);
}
}
圖解
測試及結果
自定義規則深度解析
問題:在輪詢策略的基礎上,添加新的需求,每次服務器要求被調用6次,即以前每臺機器一次,現在是每臺機器6次。
參考源碼修改爲我們需求需要的RandomRule_Jack.java
package com.jiangjy.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;
public class RandomRule_Jack extends AbstractLoadBalancerRule {
//total = 0 //當total==6以後,我們的指針才往下走
//index = 0 //當前對外提供服務的服務器地址
// total 需要重新置爲0,但是達到過一個6次,index = 1
// 分析:我們需要6次,但是微服務只有8001,8002,8003 三臺機器
//所以需要我們每讀取6次, index加1次,到達三次(三臺機器滿足條件)
private int total = 0; //總共被調用的次數,目前要求每臺被調用6次
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 = chooseRandomInt(serverCount);
server = upList.get(index);*/
//修改部分
if (total < 6) {
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;
}
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
}
}
調用
package com.jiangjy.myrule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
@Configuration //配置spring.xml形式的註解
public class MySelfRult {
@Bean //交給容器管理
public IRule myRule() {
// return new RandomRule();//Ribbon默認輪詢,自定義隨機
return new RandomRule_Jack();//自定義每臺機器6次
}
}
測試結果: