文章目錄
1.簡介
Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡的工具。
簡單的說,Ribbon是Netflix發布的開源項目,主要功能是提供客戶端的軟件負載均衡算法和服務調用。Ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等。簡單的說,就是在配置文件中列出Load Balancer (簡稱LB)后面所有的機器,Ribbon會自動的幫助你基於某種規則如簡單輪詢,隨機連接等)去連接這些機器。我們很容易使用Ribbon實現自定義的負載均衡算法。
ribbon已經不再維護,但是因為其自身足夠優秀導致ribbon現在依然被大規模使用。
ribbon未來的替代方案
2.負載均衡的介紹
LB負載均衡(Load Balance)是什么
簡單的說就是將用戶的請求平攤的分配到多個服務上,從而達到系統的HA (高可用)。
常見的負載均衡有軟件Nginx, LVS, 硬件F5等。
Ribbon本地負載均衡客戶端VS Nginx服務端負載均衡區別
Nginx是服務器負載均衡,客戶端所有請求都會交給nginx,然后由nginx實現轉發請求。即負載均衡是由服務端實現的。
Ribbon本地負載均衡,在調用微服務接口時候,會在注冊中心上獲取注冊信息服務列表之后緩存到JVM本地,從而在本地實現RPC遠程服務調用技術。
集中式LB
即在服務的消費方和提供方之間使用獨立的LB設施(可以是硬件,如F5, 也可以是軟件,如nginx), 由該設施負責把訪問請求通過某種策略轉發至服務的提供方;
進程內LB
將LB邏輯集成到消費方,消費方從服務注冊中心獲知有哪些地址可用,然后自己再從這些地址中選擇出一個合適的服務器。
Ribbon就屬於進程內LB,它只是一個類庫, 集成於消費方進程,消費方通過它來獲取到服務提供方的地址。
總結
Ribbon就是負載均衡+ RestTemplate調用
3.SpringCloud集成Ribbon
架構說明
Ribbon其實就是一 一個軟負載均衡的客戶端組件,它可以和其他所需請求的客戶端結合使用,和eureka結合只是其中的一個實例。
Ribbon在I作時分成兩步
- 第一步先選擇EurekaServer ,它優先選擇在同-個區域內負載較少的
- 第二步再根據用戶指定的策略,在從server取到的服務注冊列表中選擇一個地址。
其中Ribbon提供了多種策略:比如輪詢、隨機和根據響應時間加權。
如何引入Ribbon
我們使用的是Eureka注冊中心集群來演示Ribbon的使用,因為在引入Eureka時
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
也可以自動引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
故在pom中不自動引入。
RestTemplate簡介
getForObject方法/getForEntity方法
返回對象為響應體中數據轉化成的對象,基本上間以理解為Json
@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
log.info("order執行查詢");
return template.getForObject(path + "/payment/get/"+id,CommonResult.class);
}
返回對象為ResponseEntity對象,包含了響應中的一些重要信息,比如響應頭、響應狀態碼、響應體等
@GetMapping("/consumer/payment2/get/{id}")
public CommonResult getPaymentById2(@PathVariable("id") Long id){
log.info("order執行查詢");
ResponseEntity<CommonResult> entity = template.getForEntity(path + "/payment/get/" + id, CommonResult.class);
System.out.println(entity);
if (entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}else {
return new CommonResult(444,"操作失敗");
}
}
postForObject方法/postForEntity方法兩方法區別與get相同。
4.Ribbon核心組件IRule
作用:根據特定算法從服務中選擇一個要訪問的服務
IRule的幾個重要實現類
- com.netflix.loadbalancer.RoundRobinRule : 輪詢
- com.netflix.loadbalancer.RandomRule: 隨機
- com.netflix.loadbalancer .RetryRule: 先按照RoundRobinRule的策略獲取服務,如果獲取服務失敗則在指定時間內會進行重試,獲取可用的服務
- WeightedResponseTimeRule:對RoundRobinRule的擴展,響應速度越快的實例選擇權重越大,越容易被選擇
- BestAvailableRule:會先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,然后選擇一個並發量最小的服務
- AvailabilityFilteringRule:先過濾掉故障實例,再選擇並發較小的實例
- ZoneAvoidanceRule:默認規則,復合判斷server所在區域的性能和server的可用性選擇服務器
自定義自己的負載均衡算法
官方文檔明確給出了警告:
這個自定義配置類不能放在@ComponentScan所掃描的當前包下以及子包下,
否則我們自定義的這個配置類就會被所有的Ribbon客戶端所共享,達不到特殊化定制的目的了
即不要把自己的負載均衡類寫在啟動類所在目錄下
自定義負載均衡類及使用
寫負載均衡相關類
@Configuration
public class MyselfRule {
@Bean
public IRule getRule(){
return new RandomRule();
}
}
在啟動類中聲明要使用的負載均衡類
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyselfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
就完成了
name = "CLOUD-PAYMENT-SERVICE"是要訪問的集群
configuration = MyselfRule.class自己寫的負載均衡類。
5.Ribbon負載均衡算法
負載均衡輪詢算法的原理
負載均衡算法: rest接口第幾次請求數%服務器集群總數量=實際調用服務器位置下標,每次服務重啟動后rest接口計數從1開始。
List instances = discoveryClient.getInstances(“CLOUD- PAYMENT-SERVICE”);
如:
List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002組合成為集群,它們共計2台機器,集群總數為2,按照輪詢算法原理:
當總請求數為1時: 1 %2 =1對應下標位置為1,則獲得服務地址為127.0.0.1:8001
當總請求數位2時: 2 %2 =0對應下標位置為0,則獲得服務地址為127.0.0.1:8002
當總請求數位3時: 3 %2 =1對應下標位置為1,則獲得服務地址為127.0.0.1:8001
當總請求數位4時: 4 % 2 =0對應下標位置為0,則獲得服務地址為127.0.0.1:8002
如此類推…
Ribbon輪詢算法源碼
RoundRobinRule.java
/*選擇要使用的算法*/
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
/*ILoadBalancer 是一個抽象類,可以通過該接口中的方法獲取所有已近注冊的服務*/
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;
}
//獲取要輪詢到的sever
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
//判斷server是否可用
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;
}
//這個方法通過自旋和CAS來保證線程安全
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
//通過取余來獲取要輪詢到的server的下標
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
手寫一個負載均衡算法
因為負載均衡的是要均衡的是被調用服務的集群,所以自己寫的負載均衡類寫在調用服務的服務中。
先寫一個接口,該接口有一個方法來獲取服務
public interface LoadbanenceI {
public ServiceInstance instance(List<ServiceInstance> instances);
}
用我們自己的負載均衡類繼承上敘的接口
//加注解說明要受到spring的管理
@Component
public class MyLb implements LoadbanenceI {
private AtomicInteger integer = new AtomicInteger(0);
//通過CAS和輪詢的算法來獲取要調用服務的下標
public final int getAndIncrement(){
int cur;
int next;
do {
cur = integer.get();
next = cur >= 2147473647?0:cur+1;
}while (!integer.compareAndSet(cur,next));
System.out.println("----第" + cur + "次訪問----");
return next;
}
@Override
public ServiceInstance instance(List<ServiceInstance> instances) {
int index = getAndIncrement() % instances.size();
return instances.get(index);
}
}
因為要所以自己寫的負載均衡算法,所以關掉之前的負載均衡
@Configuration
public class ApplicationConfig {
@Bean
// @LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
在啟動類中標明要被均衡的服務,及自己寫的負載均衡類。
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyselfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
啟動所以服務后,如果調用服務集群的服務可用運行,且達到預期的均衡結果,說明自己寫的負載均衡類使用成功。