【Spring Cloud系列】- Ribbon詳解與實戰(zhàn)
在前面的文章 Eureka詳解與實戰(zhàn)、Eureka Client應用、RestTemplate詳解及其負載均衡幾篇文章中,已經介紹了Spring Cloud基本應用,本文將從講解在進程層面的負載均衡,在Spring Cloud中如何使用Ribbon做系統(tǒng)應用層面的負載均衡使用。
一、什么是Ribbon
Ribbon是netflix 公司開源的基于客戶端的負載均衡組件,是Spring Cloud大家庭中非常重要的一個模塊;Ribbon應該也是整個大家庭中相對而言比較復雜的模塊,直接影響到服務調度的質量和性能。全面掌握Ribbon可以幫助我們了解在分布式微服務集群工作模式下,服務調度應該考慮到的每個環(huán)節(jié)。
Ribbon內部提供了一個接口叫做ILoadBalance的接口代表負載均衡器的操作,這個接口包含添加服務器操作、選擇服務器操作、獲取所有的服務器列表、獲取可用服務器列表等功能。
下圖展示Ribbon的架構圖:
二、Spring Cloud中Ribbon應用
Ribbon使用步驟如下
-
在Eureka Client中添加bom.xml 依賴
<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>
-
RestTemplate配置類添加@LoadBalanced注解
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
-
啟動類添加注解@EnableEurekaClient和@RibbonClient(name = “Eureka中spring.application.name”)
@SpringBootApplication @EnableEurekaClient @RibbonClient(name="GoyeerRibbonDemo") public class GoyeerCloudRibbonAppliction { public static void main(String[] args){ SpringApplication.run(GoyeerCloudRibbonAppliction.class); } }
-
application.yml配置內容
server: port: 8081 eureka: client: service-url: defaultZone: http://server30000:30000/eureka/,http://server30001:30001/eureka/,http://server30002:30002/eureka/ register-with-eureka: false spring: application: name: GoyeerRibbon profiles: active: ribbonClient
-
編寫控制器
@Autowired private RestTemplateConfig restTemplateConfig; @RequestMapping(value = "/") public String index(){ String url="http://GOYEERBOM"; //注冊Eureka中的服務名 RestTemplate restTemplate=restTemplateConfig.getRestTemplate(); String str=restTemplate.getForObject(url,String.class); return str; }
-
服務端效果
-
Eureka服務器端
-
Ribbon調用后結構
-
三、Ribbon負載均衡策略設置
3.1 全局策略設置
/**
* @Author:Goyeer
* @Description:Ribbon 全局的負載均衡策略配置類
* @CreateDate:2023-07-11
*/
@Configuration
public class RibbonGlobalLoadBalancingConfiguration {
/**
* 隨機規(guī)則
*/
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
3.2 基于注解的針對單個服務的 Ribbon 負載均衡策略
3.2.1 注解方式
/**
* @Author:Goyeer
* @Description:Ribbon 隨機負載均衡策略配置類哪個服務引用就作用在那個服務上面在啟動類上方使用 @RibbonClient 引用
* @CreateDate:2023-07-11
*/
@Configuration
@AvoidScan
public class RibbonRandomLoadBalancingConfiguration {
@Resource
IClientConfig clientConfig;
@Bean
public IRule ribbonRule(IClientConfig clientConfig) {
return new RandomRule();
}
}
3.2.2 IClientConfig針對客戶端的配置管理器
/** 配置針對單個服務的 Ribbon 負載均衡策略 **/
@RibbonClient(
name = "goyeer-balance-ribbon", configuration = RibbonRandomLoadBalancingConfiguration.class
)
/** 此處配置根據標識 @AvoidScan 過濾掉需要單獨配置的 Ribbon 負載均衡策略,不然就會作用于全局,啟動就會報錯 */
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = AvoidScan.class)
)
3.3 配置文件方式
### 針對單個服務的 Ribbon 配置
goyeer-balance-ribbon:
ribbon:
# 基于配置文件形式的 針對單個服務的 Ribbon 負載均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
四、如何自定義Ribbon負載均衡策略
4.1 自定義算法
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import org.springframework.stereotype.Component;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class RandomRule_ZY extends AbstractLoadBalancerRule {
//定義一個原子類,以保證原子性
private AtomicInteger atomicInteger =new AtomicInteger(0);
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null){
return null;
}
//用于統(tǒng)計獲取次數(shù),當達到一定數(shù)量就不再去嘗試
int count = 0;
Server server = null;
//當服務還沒獲取到,并且嘗試沒有超過8次
while (server == null && count++ < 8){
//獲取服務
List<Server> allServers = lb.getAllServers();
List<Server> reachableServers = lb.getReachableServers();
int allServersSize = allServers.size();
int reachableServersSize = reachableServers.size();
//如果獲取的服務list都為0就返回null
if(allServersSize == 0 || reachableServersSize == 0){
return null;
}
//獲取服務下標
int next = getServerIndex(allServersSize);
//獲取服務
server = reachableServers.get(next);
//如果服務為空直接跳過下面的
if (server == null){
continue;
}
//如果獲取到的這個服務是活著的就返回
if (server.isAlive()){
return server;
}
//如果獲取到的服務不是空,但是不是存活狀態(tài),需要重新獲取
server=null;
}
//最后這里可能會返回null
return server;
}
//獲取服務下標,為了保證原子性,使用了CAS
public int getServerIndex(int allServersSize){
//自旋鎖
for (;;) {
//獲取當前值
int current = this.atomicInteger.get();
//設置期望值
int next = (current + 1) % allServersSize;
//調用Native方法compareAndSet,執(zhí)行CAS操作
if (this.atomicInteger.compareAndSet(current, next))
//成功后才會返回期望值,否則無線循環(huán)
return next;
}
}
public Server choose(Object key) {
return choose(getLoadBalancer(),key);
}
}
4.2 自定義配置類
@Configuration
//針對全局修改
//@RibbonClients(defaultConfiguration = MyRuleConfig.class)
//針對某個服務修改
@RibbonClient(name = "nacos-app-a", configuration = MyRuleConfig.class)
public class MyRuleConfig {
@Bean
public IRule rule() {
//返回上面自定義的規(guī)則類
return new RandomRule_ZY();
}
}
4.3 設置加載自定義Ribbon配置類
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "Client",configuration = MyRuleConfig.class)
public class GoyeerCloudRibbonAppliction {
public static void main(String[] args){
SpringApplication.run(GoyeerCloudRibbonAppliction.class);
}
}
五、LoadBalancer–負載均衡器的核心
Ribbon實現(xiàn)負載均衡是通過LoadBalancer注解來給RestTemplate標記,來實現(xiàn)負載均衡。LoadBalancer是如何實現(xiàn)負載均衡哪?查看源碼我們可以看到有一個接口LoadBalancerClinet來實現(xiàn)的。
5.1 什么是LoadBalancerClient
LoadBalancerClient 是 SpringCloud 提供的一種負載均衡客戶端,Ribbon 負載均衡組件內部也是集成了 LoadBalancerClient 來實現(xiàn)負載均衡。
5.2 LoadBalancerClient原理
LoadBalancerClinet在初始化時會通過Euraka Clinet向Eureka服務端獲取所有的服務實例的注冊信息并緩存到本地,并且每10秒向EurakaClinet發(fā)送“Ping”請求,來判斷服務的可用性。如果服務的可用性發(fā)生了改變或者服務數(shù)量和之前的不一致,則更新或重新拉取最新到本地。在得到最新服務注冊信息后,ILoadBalancer根據IRule的策略進行負載均衡(默認策略為輪詢)。
當使用LoadBalancerClient進行遠程調用的負載均衡時,LoadBalancerClient先通過目標服務名在本地服務注冊清單中獲取服務提供方的某個實例,如多個服務器節(jié)點,LoadBalancerClient會通過choose()方法獲取到多個節(jié)點中一個服務,拿到服務的信息之后取出服務IP信息,就可以得到完整的想要訪問的IP地址和端口號,最后通過RestTempate訪問具體的服務信息。
5.3 LoadBabancerClient源碼解析
5.3.1 LoadBalancerClient 類圖
LoadBalancerClient 是 Spring Cloud 提供的一個非常重要的接口,它繼承ServiceInstanceChooser 接口,該接口的實現(xiàn)類是 RibbonLoadBalanceClient,它們之間的關系如下圖所示:
5.3.2 LoadBalancerClient 接口源碼
public interface LoadBalancerClient extends ServiceInstanceChooser
{
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
可以發(fā)現(xiàn) LoadBalancerClient 接口繼承了 ServiceInstanceChooser 接口,包含兩個方法:execute和reconstructURI
- execute() 用來執(zhí)行Request請求
- reconstructURI() 用來重構URL
5.3.3 ServiceInstanceChooser 接口源碼
public interface ServiceInstanceChooser
{
ServiceInstance choose(String serviceId);
}
ServiceInstanceChooser 接口中的主要方法為 choose(),該方法用于根據服務的名稱 serviceId 來選擇其中一個服務實例,即根據 serviceId 獲取ServiceInstance。
5.3.4 RibbonLoadBalanceClient 實現(xiàn)類源碼
LoadBalancerClient 的實現(xiàn)類 RibbonLoadBalanceClient,它用來執(zhí)行最終的負載均衡請求。其中,RibbonLoadBalanceClient 的一個 choose() 方法用于選擇具體的服務實例,其內部是通過 getServer() 方法交給 ILoadBalancer 完成的。
-
choose(),用來選擇具體的服務實例
@Override public ServiceInstance choose(String serviceId) { return choose(serviceId, null); } /** * New: Select a server using a 'key'. * @param serviceId of the service to choose an instance for * @param hint to specify the service instance * @return the selected {@link ServiceInstance} */ public ServiceInstance choose(String serviceId, Object hint) { Server server = getServer(getLoadBalancer(serviceId), hint); if (server == null) { return null; } return new RibbonServer(serviceId, server, isSecure(server, serviceId),serverIntrospector(serviceId).getMetadata(server)); }
-
getServer(),獲取實例
protected Server getServer(ILoadBalancer loadBalancer) { return getServer(loadBalancer, null); } protected Server getServer(ILoadBalancer loadBalancer, Object hint) { if (loadBalancer == null) { return null; } // 最終通過 loadBalancer 去做服務實例的選擇。 // Use 'default' on a null hint, or just pass it on? return loadBalancer.chooseServer(hint != null ? hint : "default"); }
5.3.5 BaseLoadBalancer 源碼
5.3.6 IRule 接口源碼
public interface IRule{
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
IRule 接口定義了3個方法,分別是:choose、setLoadBalancer和getLoadBalancer
- choose() 是用來選擇實例的
- setLoadBalancer()用來設置負載均衡規(guī)則
- getLoadBalancer()獲取負載均衡規(guī)則
實現(xiàn)IRule有個類,分別定義不同的負載均衡規(guī)則:
- 隨機策略 RandomRule
- 輪詢策略 RoundRobinRule
- 重試策略 RetryRule
- 可用過濾策略 PredicateBaseRule
- 響應時間權重策略 WeightedRespinseTimeRule
- 并發(fā)量最小可用策略 BestAvailableRule
- 區(qū)域權重策略 ZoneAvoidanceRule
5.3.7 ILoadBalancer 源碼
ILoadBalancer 是一個接口,該接口定義了一系列實現(xiàn)負載均衡的方法,LoadBalancerClient 的實現(xiàn)類 RibbonLoadBalanceClient 也將負載均衡的具體實現(xiàn)交給了 ILoadBalancer 來處理,ILoadBalancer 通過配置 IRule、IPing 等,向 EurekaClient 獲取注冊列表信息,默認每10秒向 EurekaClient 發(fā)送一次 “ping”,進而檢查是否需要更新服務的注冊列表信息。最后,在得到服務注冊列表信息后,ILoadBalancer 根據 IRule 的策略進行負載均衡。
查看 BaseLoadBalancer 和 DynamicServerListLoadBalancer 源碼,默認情況下實現(xiàn)了以下配置:
- IClientConfig clientConfig:用于配置負載均衡客戶端,默認實現(xiàn)類是 DefaultClientConfigImpl。
- IRule rule:用于配置負載均衡的策略,默認使用的是 RoundRobinRule 輪詢策略。
- IPing ping:用于檢查當前服務是否有響應,從而判斷當前服務是否可用,默認實現(xiàn)類是 DummyPing,該實現(xiàn)類的 isAlive() 方法返回值是 true,默認所有服務實例都是可用的。
- ServerList serverList:用于獲取所有 Server 注冊列表信息。通過跟蹤源碼會發(fā)現(xiàn),ServerList 的實現(xiàn)類是 DiscoveryEnabledNIWSServerList,該類定義的 obtainServersViaDiscovery() 方法是根據 eurekaClientProvider.get() 方法獲取 EurekaClient,再根據 EurekaClient 獲取服務注冊列表信息。EurekaClient 的實現(xiàn)類是DiscoveryClient,DiscoveryClient 具有服務注冊、獲取服務注冊列表等功能。
- ServerListFilter filter:定義了根據配置過濾或者動態(tài)獲取符合條件的服務列表,默認實現(xiàn)類是 ZonePreferenceServerListFilter,該策略能夠優(yōu)先過濾出與請求調用方處于同區(qū)域的服務實例。
六、Ribbon的配置參數(shù)
控制參數(shù) | 說明 | 默認值 |
---|---|---|
<service-name> .ribbon.NFLoadBalancerPingInterval |
Ping定時任務周期 | 30s |
service-name> .ribbon.NFLoadBalancerMaxTotalPingTime |
Ping超時時間 | 2s |
<service-name> .ribbon.NFLoadBalancerRuleClassName |
IRule實現(xiàn)類 | RoundRobinRule,基于輪詢調度算法規(guī)則選擇服務實例 |
<service-name> .ribbon.NFLoadBalancerPingClassName |
IPing實現(xiàn)類 | DummyPing,直接返回true |
<service-name> .ribbon.NFLoadBalancerClassName |
負載均衡器實現(xiàn)類 | 2s |
<service-name> .ribbon.NIWSServerListClassName |
ServerList實現(xiàn)類 | ConfigurationBasedServerList,基于配置的服務列表 |
<service-name> .ribbon.ServerListUpdaterClassName |
服務列表更新類 | PollingServerListUpdater |
<service-name> .ribbon.NIWSServerListFilterClassName |
服務實例過濾器 | 2s |
<service-name> .ribbon.NIWSServerListFilterClassName |
服務實例過濾器 | 2s |
<service-name> .ribbon.ServerListRefreshInterval |
服務列表刷新頻率 | 2s |
<service-name> .ribbon.NFLoadBalancerClassName |
自定義負載均衡器實現(xiàn)類 | 2s |
七、總結
Ribbon是Spring cloud的核心,負載微服務內負載調用;Ribbon可以脫離Spring Cloud的單獨使用。文章來源:http://www.zghlxwxcb.cn/news/detail-553994.html
Ribbon是微服務整個微服務組件最復雜的一環(huán),控制流程上為保證服務的高可用性,有比較多的細節(jié)參數(shù)控制,在使用的過程中需要深入理清每個環(huán)節(jié)的處理機制,使之發(fā)揮穩(wěn)定且高效的作用。文章來源地址http://www.zghlxwxcb.cn/news/detail-553994.html
到了這里,關于【Spring Cloud系列】- Ribbon詳解與實戰(zhàn)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!