1.Ribbon是什麼
Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端,一套基於HTTP和TCP的客戶端負載均衡工具。
Ribbon是Netflix發佈的開源項目,通過Spring Cloud的封裝,可以輕鬆地將面向服務的REST模版請求自動轉換成客戶端負載均衡的服務調用。主要功能是提供客戶端的軟件負載均衡算法,將Netflix的中間層服務連接在一起。
Ribbon客戶端組件提供一系列完善的配置項,如連接超時,重試等。簡單的說,就是在配置文件中列出Load Balancer(簡稱LB)後面所有的機器,Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機連接等)去連接這些機器。我們也很容易使用Ribbon實現自定義的負載均衡算法。
Spring Cloud Ribbon雖然只是一個工具類框架,它不像服務註冊中心、配置中心、API網關那樣需要獨立部署,但是它幾乎存在於每一個Spring Cloud構建的微服務和基礎設施中。因爲微服務間的調用,API網關的請求轉發等內容,實際上都是通過Ribbon來實現的,包括Feign,它也是基於Ribbon實現的工具。
2.Ribbon的作用(負載均衡LB介紹)
如上所說,微服務間的調用通過Ribbon來實現,可以實現客戶端負載均衡的服務調用,即負載均衡LB(Load Balance)
負載均衡是對系統的高可用、網絡壓力的緩解和處理能力擴容的重要手段之一。通常所說的負載均衡都指的是服務端負載均衡,其中分爲硬件負載均衡和軟件負載均衡。
服務端負載均衡(集中式LB)
服務端負載均衡也可以叫做集中式LB
就是在服務的消費方和提供方之間使用獨立的LB設施(可以是硬件,如F5, 也可以是軟件,如nginx), 由該設施負責把訪問請求通過某種策略轉發至服務的提供方;
- 硬件負載均衡
主要通過在服務器節點之間按照專門用於負載均衡的設備,比如F5
- 軟件負載均衡
通過在服務器上安裝一些用於負載均衡功能或模塊等軟件來完成請求分發工作,比如Nginx
上面所說兩種類型的負載均衡設備都會維護一個下掛可用的服務端清單,通過心跳檢測來剔除故障的服務端節點以保證清單中都是可以正常訪問的服務端節點。當客戶端發送請求到負載均衡設備的時候,該設備按某種算法(比如線性輪詢、按權重負載、按流量負載等)從維護的可用服務端清單中取出一臺服務端端地址,然後進行轉發。
客戶端負載均衡(進程內LB)
客戶端負載均衡也可以叫做進程內LB
將LB邏輯集成到消費方,消費方從服務註冊中心獲知有哪些地址可用,然後自己再從這些地址中選擇出一個合適的服務器。
Ribbon就屬於進程內LB,它只是一個類庫,集成於消費方進程,消費方通過它來獲取到服務提供方的地址。
所以!!
客戶端負載均衡和服務端負載均衡最大的不同是:服務清單所存儲的位置。
在客戶端負載均衡中,所有客戶端節點都維護着自己要訪問的服務端清單,而這些服務端端清單來自於服務註冊中心,即Eureka服務註冊中心或者Zookeper等等。
同服務端負載均衡的架構類似,在客戶端負載均衡中也需要心跳去維護服務端清單的健康性。默認會創建針對各個服務治理框架的Ribbon自動化整合配置。
比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration。
Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration
通過Spring Cloud Ribbon的封裝,我們在微服務架構中使用客戶端負載均衡調用非常簡單,只需要如下兩步:
▪️服務提供者只需要啓動多個服務實例並註冊到一個註冊中心或是多個相關聯的服務註冊中心。
▪️服務消費者直接通過調用被@LoadBalanced註解修飾過的RestTemplate來實現面向服務的接口調用。
這樣,我們就可以將服務提供者的高可用以及服務消費者的負載均衡調用一起實現了。
3.Ribbon的初步實踐
pom.xml
<!-- Ribbon相關 -->
<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>
修改application.yml 追加eureka的服務註冊地址 Ribbon的使用依賴Eureka
與Eureka整合
實現進程內LB下掛可用的服務端清單
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的配置
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;
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
爲了能和Eureka Server服務註冊中心保持心跳維護服務端清單的健康性,要註冊啓動類成爲eurekaClient
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
//在啓動該微服務的時候就能去加載我們的自定義Ribbon配置類,從而使配置生效,對應的是微服務名,即spring.application.name
@RibbonClient(name="MICROSERVICECLOUD-DEPT")
public class DeptConsumer80_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptConsumer80_App.class, args);
}
}
開始時間客戶端訪問類
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class DeptController_Consumer
{
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/dept/add")
public boolean add(Dept dept)
{
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add", dept, Boolean.class);
}
@RequestMapping(value="/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id)
{
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id, Dept.class);
}
@SuppressWarnings("unchecked")
@RequestMapping(value="/consumer/dept/list")
public List<Dept> list()
{
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list", List.class);
}
}
GET請求
在RestTemplate中,對GET請求可以通過兩個方法進行調用實現。
- getForEntity函數。
該方法返回的是ResponseEntity,該對象是Spring對HTTP請求響應的封裝,其中主要存儲了HTTP的幾個重要元素,比如HTTP請求狀態碼的枚舉對象HttpStatus(也就是我們常說的404、500這些錯誤碼)、在它的父類HttpEntity中還存儲着HTTP請求的頭信息對象HttpHeaders以及泛型類型的請求體對象
getForEntity(String url, Class responseType,Object... urlVariables);
getForEntity(String url, Class responseType, Map urlVariables);
getForEntity(URI url, Class responseType)
- getForObject函數。
該方法可以理解爲對getForEntity的進一步封裝,它通過HttpMessageConverterExtractor對HTTP的請求響應體body內容進行對象轉換,實現請求直接返回包裝好的對象內容。 當不需要關注請求響應除body外的其他內容時,可以使用該函數,可以少一個從Response中獲取body的步驟。
▪️getForObject(String url, Class responseType, Object ... urlVariables)
▪️getForObject(String url, Class responseType, Map urlVariables)
▪️getForObject(URI url, Class responseType)
POST 請求
RestTemplate中,對POST請求時可以通過三個方法調用實現。
- postForEntity函數
該方法同GET請求中的getForEntity類似,會在調用後返回ResponseEntity對象,其中T爲請求響應的body類型。
postForEntity(String url, Object request, Class responseType, Object... uriVariables)
postForEntity(String url, Object request, Class responseType, Map uriVariables)
postForEntity(URI url, Object request, Class responseType)
-
postForObject函數。
該方法也跟getForObject的類型類似,它的作用就是簡化postForEntity的後續處理。通過直接將請求響應的body內容包裝成對象來返回使用,
postForObject(String url, Object request, Class responseType, Object... uriVariables)
postForObject(String url, Object request, Class responseType, Map uriVariables)
postForObject(URI url, Object request, Class responseType)
- postForLocation函數。
該方法實現了以POST請求提交資源,並返回新的資源的URI。
▪️postForLocation(String url, Object request, Object... uriVariables)
▪️postForLocation(String url, Object request, Map uriVariables)
▪️postForLocation(URI url, Object request)
由於postForLocation函數會返回新資源的URI,該URI就相當於指定了返回類型
Ribbon負載均衡
Ribbon負載均衡要求服務提供者提供集羣搭建才能看到效果,而Ribbon只要註冊在了Eureka,服務提供者使用統一的spring.application.name。即能看到效果。
@LoadBalanced
該註解用來給RestTemplate標記,以使用負載均衡的客戶端(LoadBalancerClient)來配置它。
使用添加@LoadBalanced註解後的RestTemplate調用服務提供者的接口時,可以使用虛擬IP替代真實IP地址。
Ribbon和Eureka整合後Consumer可以直接調用服務而不用再關心ip地址和端口號
Ribbon自帶負載均衡策略比較
Ribbon提供的主要負載均衡策略介紹,其中Ribbon默認使用簡單輪詢的負載均衡策略
1:簡單輪詢負載均衡(RoundRobin)
以輪詢的方式依次將請求調度不同的服務器,即每次調度執行i = (i + 1) mod n,並選出第i臺服務器。
2:隨機負載均衡 (Random)
隨機選擇狀態爲UP的Server
3:加權響應時間負載均衡 (WeightedResponseTime)
根據相應時間分配一個weight,相應時間越長,weight越小,被選中的可能性越低。
4:區域感知輪詢負載均衡(ZoneAvoidanceRule)
複合判斷server所在區域的性能和server的可用性選擇server
策略名 | 策略聲明 | 策略描述 | 實現說明 |
---|---|---|---|
BestAvailableRule | public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule | 選擇一個最小的併發請求的server | 逐個考察Server,如果Server被tripped了,則忽略,在選擇其中ActiveRequestsCount最小的server |
AvailabilityFilteringRule | public class AvailabilityFilteringRule extends PredicateBasedRule | 過濾掉那些因爲一直連接失敗的被標記爲circuit tripped的後端server,並過濾掉那些高併發的的後端server(active connections 超過配置的閾值) | 使用一個AvailabilityPredicate來包含過濾server的邏輯,其實就就是檢查status裏記錄的各個server的運行狀態 |
WeightedResponseTimeRule | public class WeightedResponseTimeRule extends RoundRobinRule | 根據相應時間分配一個weight,相應時間越長,weight越小,被選中的可能性越低。 | 一 個後臺線程定期的從status裏面讀取評價響應時間,爲每個server計算一個weight。Weight的計算也比較簡單responsetime 減去每個server自己平均的responsetime是server的權重。當剛開始運行,沒有形成statas時,使用roubine策略選擇 server。 |
RetryRule | public class RetryRule extends AbstractLoadBalancerRule | 對選定的負載均衡策略機上重試機制。 | 在一個配置時間段內當選擇server不成功,則一直嘗試使用subRule的方式選擇一個可用的server |
RoundRobinRule | public class RoundRobinRule extends AbstractLoadBalancerRule | roundRobin方式輪詢選擇server | 輪詢index,選擇index對應位置的server |
RandomRule | public class RandomRule extends AbstractLoadBalancerRule | 隨機選擇一個server | 在index上隨機,選擇index對應位置的server |
ZoneAvoidanceRule | public class ZoneAvoidanceRule extends PredicateBasedRule | 複合判斷server所在區域的性能和server的可用性選擇server | 使 用ZoneAvoidancePredicate和AvailabilityPredicate來判斷是否選擇某個server,前一個判斷判定一個 zone的運行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用於過濾掉連接數過多的 Server。 |
切換Ribbon的負載均衡機制
首先要知道Ribbon的IRule是一個接口類,是Ribbon負載均衡器負責選擇怎樣的負載均衡算法
它的實現類有下面至少這些
那麼怎麼切換負載均衡算法呢,只需要在配置文件裏面配置好IRule已經實現好的負債均衡算法,就可以切換算法。
格式如下
{服務提供者名稱}:
ribbon:
NFLoadBalancerRuleClassName:{IRule的實現類}
當然,你也可以自定義一個負載均衡算法,指定調用某個服務的時候使用這種算法,具體可看此文章link