Ribbon實作負載均衡
- 一、實作方式
- 二、手寫用戶端側負載均衡器
- 三、使用Ribbon實作負載均衡
- 四、Ribbon 組成
- 五、Ribbon 配置
- 六、Ribbon 饑餓加載
- 七、Ribbon擴充
- 八、現有架構存在問題
一、實作方式
- 伺服器端負載均衡(單體架構)
- 用戶端側負載均衡(内容中心相對使用者中心,屬于用戶端,可在内容中心叢集中配置負載均衡規則)
二、手寫用戶端側負載均衡器
- 随機負載均衡算法
三、使用Ribbon實作負載均衡
1、定義:Netfix開源的用戶端側負載均衡器,即提供了負載均衡算法
2、架構演進:
3、整合Ribbon
- 加依賴:不需要,spring-cloud-starter-alibaba-nacos-discovery已預設提供
- 加注解
- 寫配置:無配置
PS:代碼改寫,使用http://user-center/users/{userId},Ribbon會自動讀取名稱,進行位址查找并轉換
四、Ribbon 組成
1、内置負載均衡規則
PS:預設為ZoneAvoidanceRule,無Zone環境,等價于RoundRobinRule
五、Ribbon 配置
1、java代碼配置,采用随機算法
- 建立RibbonConfiguration 配置類
package com.hzb2i.contentcenter.configuration.Ribbon;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
- 建立 UserCenterRibbonConfiguration 類
package com.hzb2i.contentcenter.configuration;
import RibbonConfiguration.RibbonConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;
@Configuration
@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}
PS:需要注意UserCenterRibbonConfiguration 需要放到@SpringBootApplication啟動類掃描不到的包下
- @SpringBootApplication注解預設掃描目前的包和子包
- 若将UserCenterRibbonConfiguration 放到啟動類相同路徑下,将會出現@SpringBootApplication 主上下文和UserCenterRibbonConfiguration 類的@Configuration 子上下文出現重複掃描,UserCenterRibbonConfiguration 的配置為全局配置,将會被所有RibbonClient共享,即不管内容中心通路使用者中心還是其他的微服務,都會采用UserCenterRibbonConfiguration 配置(重複掃描帶來的問題,感興趣可看此分享:spring重複掃描事務失敗)
2、用配置屬性配置:application.yml加入以下配置
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
PS:兩種配置方式對比
PS:寫配置方式,線上無需重新打包,采用的是讀取外部配置檔案方式
3、最佳實踐:
- 盡量使用屬性配置,屬性配置實作不了情況再考慮用代碼配置
- 在同一個微服務内盡量保持單一性,比如統一使用屬性配置,不要用兩種方式混用,增加定位代碼的複雜性
4、全局配置:
- 方式一:讓ComponentScan重疊,即将RibbonConfiguration放到啟動類相同路徑(不建議使用,會導緻項目無法啟動)
- 正确途徑 5、Ribbon其他配置項
- 代碼配置:
- 寫配置:
六、Ribbon 饑餓加載
- 問題:Ribbon預設采用懶加載,隻有http://user-center/users/{userId}執行,才會使用Ribbon自動擷取使用者中心位址,導緻第一次通路加載速度慢
- 措施:寫配置,開啟饑餓加載
七、Ribbon擴充
1、支援Nacos權重(根據權重判斷的負載均衡規則):
- 場景:若微服務有衆多執行個體,分别部署到不同伺服器上,可以把在性能好的機器上的執行個體權重設高點,性能差的機器的執行個體權重設低點,這樣權重高的執行個體被調用的幾率就高一些
-
ribbon預設不支援nacos權重,需通過以下方式開發
① 可實作IRule接口
② 繼承AbstractLoadBalancerRule抽象類,并将此負載均衡算法設為全局配置
package com.hzb2i.contentcenter.configuration;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
/*
* ribbon 擴充:支撐nacos權重
*/
@Slf4j
public class NacosWeightRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
//讀取配置,并初始化NacosWeightRule
}
@Override
public Server choose(Object o) {
try {
//入口
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要請求的微服務名稱
String name = loadBalancer.getName();
//實作負載均衡算法
//拿到服務發現的相關API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// nacos client 自動通過基于權重的負責均衡算法,給我們選擇一個執行個體
Instance instance = namingService.selectOneHealthyInstance(name);
log.info("選擇的執行個體:port={},instance={}",instance.getPort(),instance);
return new NacosServer(instance);
} catch (NacosException e) {
e.printStackTrace();
}
return null;
}
}
在nacos控制台修改權重:
啟動使用者中心、内容中心測試:請求基本調用權重高的執行個體
PS:nacos client内置了基于權重的負載均衡算法,spring cloud Alibaba為何還要整合ribbon,直接用nacos client不就行了嗎?主要是為符合spring cloud标準,spring cloud有個子項目spring cloud commons(定義了标準),其下邊的子項目spring cloud loadbalancer(無權重概念),spring cloud Alibaba符合此标準,故未整合權重的算法
PS:其他實作權重負載均衡算法方式請移步 擴充Ribbon支援Nacos權重的三種方式
2、同叢集優先調用
- 場景:如存在北京機房和南京機房,想讓北京機房的内容中心有限調用同機房的使用者中心,在同機房使用者中心不存在的情況下再調用南京機房的使用者中心,可以采用此方式
- 代碼開發,并将此負載均衡算法設為全局配置:
package com.hzb2i.contentcenter.configuration;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.core.Balancer;
import com.alibaba.nacos.client.naming.utils.Chooser;
import com.alibaba.nacos.client.naming.utils.Pair;
import com.alibaba.nacos.client.utils.LogUtils;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class NacosSameClusterWeightRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
try {
String clusterName = nacosDiscoveryProperties.getClusterName();
//入口
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要請求的微服務名稱
String name = loadBalancer.getName();
//實作負載均衡算法
//拿到服務發現的相關API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
//找到指定服務的所有API
List<Instance> instances = namingService.selectInstances(name,true);
//過濾相同叢集的所有執行個體
List<Instance> sameClusterInstances = instances.stream().filter(instance -> {
return Objects.equals(instance.getClusterName(),clusterName);
}).collect(Collectors.toList());
//如果B(sameClusterInstances)是空,否則用A(instances)
List<Instance> instancesToBeChosen = new ArrayList<>();
if(CollectionUtils.isEmpty(sameClusterInstances)){
instancesToBeChosen = instances;
log.info("跨叢集調用:name={},clusterName={},instances={}",name,clusterName,instances);
}else {
instancesToBeChosen = sameClusterInstances;
log.info("同叢集調用:name={},clusterName={},instances={}",name,clusterName,sameClusterInstances);
}
//基于權重的負載均衡算法,傳回一個執行個體(namingService無根據list直接傳回執行個體)
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
return new NacosServer(instance);
} catch (NacosException e) {
e.printStackTrace();
}
return null;
}
}
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(List<Instance> hosts) {
LogUtils.NAMING_LOGGER.debug("entry randomWithWeight");
if (hosts != null && hosts.size() != 0) {
LogUtils.NAMING_LOGGER.debug("new Chooser");
List<Pair<Instance>> hostsWithWeight = new ArrayList();
Iterator var2 = hosts.iterator();
while(var2.hasNext()) {
Instance host = (Instance)var2.next();
if (host.isHealthy()) {
hostsWithWeight.add(new Pair(host, host.getWeight()));
}
}
LogUtils.NAMING_LOGGER.debug("for (Host host : hosts)");
Chooser<String, Instance> vipChooser = new Chooser("www.taobao.com");
vipChooser.refresh(hostsWithWeight);
LogUtils.NAMING_LOGGER.debug("vipChooser.refresh");
return (Instance)vipChooser.randomWithWeight();
} else {
LogUtils.NAMING_LOGGER.debug("hosts == null || hosts.size() == 0");
return null;
}
}
}
啟動測試:優先通路同叢集的端口,同叢集微服務挂了,才進行跨叢集通路
3、基于中繼資料的版本控制:擴充Ribbon支援基于中繼資料的版本管理
PS:深入了解Nacos NameSpace:微服務隻能調用同一空間的執行個體