參考:https://blog.csdn.net/yuanyuan_gugu/article/details/107336264
一、自定義負載均衡算法
自定義負載均衡算法的實現步驟
(1)RestTemplate 注入增加 @LoadBalanced 注解;
(2)繼承 AbstractLoadBalancerRule 類;
(3)重寫 choose 方法;
(4)配置文件配置自定義的負載均衡算法;
二、基於Nacos的負載均衡實現
1、基於Nacos權重
(1)注冊到 nacos 的服務有權重的定義,可以在配置文件中通過 spring.cloud.nacos.discovery.weight=0.1 定義權重,默認為1;
將Nacos中微服務實例調整權重數值(0-1之間,越大權重越高)。基於權重的負載均衡可以結合微服實例部署的環境,更合理的進行負載均衡,例如部署環境服務器配置較高,可用資源較為寬裕,可調高權重參數,反之可以調低。
(2)我們可以根據權重去作為負載均衡的算法,例如我們同一個服務部署了3個實例,它們的性能都是不一樣的,我們想讓性能最好的那個優先去被選擇;我們可以設置性能最好的設置服務權重設置最大。
權重負載均衡的算法實現,如下:
@Slf4j
public class CustomWeightRibbonRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
log.info("------ key: {}", o);
// 獲取負載均衡的對象
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 獲取當前調用的微服務的名稱
String serviceName = baseLoadBalancer.getName();
// 獲取Nocas服務發現的相關組件API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
// 獲取一個基於 nacos client 實現權重的負載均衡算法
Instance instance = namingService.selectOneHealthyInstance(serviceName);
// 返回一個nacos的server
return new NacosServer(instance);
} catch (NacosException e) {
log.error("error: ", e);
return null;
}
}
}
(3)配置文件,如下:
product-center: ribbon: NFLoadBalancerRuleClassName: com.yufeng.custom.CustomWeightRibbonRule
2、支持Nacos同一集群優先調用
實際項目中,微服務的部署為了確保高可用和容災性,常常使用異地機房的多套部署。如何保證微服務交互時總是優先使用就近機房的實例?
使用Nacos的集群配置Cluster可以實現同集群優先調用。
(1)nacos 可以通過配置文件配置 spring.cloud.nacos.discovery.cluster-name=NJ-CLUSTER 集群名稱;
(2)例如我們有兩個服務 order-center,product-center, 在南京機房和北京機房都部署了一套 order-center,product-center;我們在服務調用的時候要優先調用同一個集群的服務。
同集群優先調用負載均衡算法的實現,如下:
@Slf4j
public class CustomClusterRibbonRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
log.info("-------key: {}", o);
// 獲取當前服務所在的集群
String currentClusterName = nacosDiscoveryProperties.getClusterName();
// 獲取當前調用的微服務的名稱
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
String serviceName = baseLoadBalancer.getName();
// 獲取nacos client的服務注冊發現組件的api
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
// 獲取多有的服務實例
List<Instance> allInstances = namingService.getAllInstances(serviceName, true);
// 獲取同一集群下的所有被調用的服務
List<Instance> sameClusterNameInstList = allInstances.stream()
.filter(instance -> StringUtils.equalsIgnoreCase(instance.getClusterName(), currentClusterName))
.collect(Collectors.toList());
Instance chooseInstance;
if(sameClusterNameInstList.isEmpty()) {
// 根據權重隨機選擇一個
chooseInstance = ExtendBalancer.getHostByRandomWeightCopy(allInstances);
log.info("發生跨集群調用--->當前微服務所在集群:{},被調用微服務所在集群:{},Host:{},Port:{}",
currentClusterName, chooseInstance.getClusterName(), chooseInstance.getIp(), chooseInstance.getPort());
}
else {
chooseInstance = ExtendBalancer.getHostByRandomWeightCopy(sameClusterNameInstList);
log.info("同集群調用--->當前微服務所在集群:{},被調用微服務所在集群:{},Host:{},Port:{}",
currentClusterName, chooseInstance.getClusterName(), chooseInstance.getIp(), chooseInstance.getPort());
}
return new NacosServer(chooseInstance);
} catch (NacosException e) {
log.error("error: ", e);
return null;
}
}
}
class ExtendBalancer extends Balancer {
/**
* 根據權重選擇隨機選擇一個
*/
public static Instance getHostByRandomWeightCopy(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}
此時調用Nacos的基於權重的負載均衡算法時,已經不能再使用 namingService.selectOneHealthyInstance(name),因為我們不是僅僅基於服務名稱做全集群的帶權重的負載均衡選擇實例,而是要基於部分實例列表來進行。
3、基於Nacos元數據的版本控制
實際項目,我們可能還會有這樣的需求:
一個微服務在線上可能多版本共存,且多個版本的微服務並不兼容。使用Nacos的自定義元數據,可以實現微服務的版本控制。
配置文件的格式: spring.cloud.nacos.discovery.metadata.{key}={value}
當前配置的版本: spring.cloud.nacos.discovery.metadata.version=V1
必須同版本,優先同集群調用
@Slf4j
public class ClusterMetaDataRibbonRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
log.info("-------key: {}", o);
// 獲取當前服務的集群名稱
String currentClusterName = nacosDiscoveryProperties.getClusterName();
// 獲取當前版本
String currentVersion = nacosDiscoveryProperties.getMetadata().get("version");
// 獲取被調用的服務的名稱
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();
String serviceName = baseLoadBalancer.getName();
// 獲取nacos clinet的服務注冊發現組件的api
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
// 獲取所有被調用服務
List<Instance> allInstances = namingService.getAllInstances(serviceName);
// 過濾出相同版本且相同集群下的所有服務
List<Instance> sameVersionAndClusterInstances = allInstances.stream()
.filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get("version"), currentVersion)
&& StringUtils.equalsIgnoreCase(x.getClusterName(), currentClusterName)
).collect(Collectors.toList());
Instance chooseInstance;
if(sameVersionAndClusterInstances.isEmpty()) {
// 過濾出所有相同版本的服務
List<Instance> sameVersionInstances = allInstances.stream()
.filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get("version"), currentVersion))
.collect(Collectors.toList());
if(sameVersionInstances.isEmpty()) {
log.info("跨集群調用找不到對應合適的版本當前版本為:currentVersion:{}",currentVersion);
throw new RuntimeException("找不到相同版本的微服務實例");
}
else {
// 隨機權重
chooseInstance = ExtendBalancer.getHostByRandomWeightCopy(sameVersionInstances);
log.info("跨集群同版本調用--->當前微服務所在集群:{},被調用微服務所在集群:{},當前微服務的版本:{},被調用微服務版本:{},Host:{},Port:{}",
currentClusterName, chooseInstance.getClusterName(), chooseInstance.getMetadata().get("current-version"),
chooseInstance.getMetadata().get("current-version"), chooseInstance.getIp(), chooseInstance.getPort());
}
}
else {
chooseInstance = ExtendBalancer.getHostByRandomWeightCopy(sameVersionAndClusterInstances);
log.info("同集群同版本調用--->當前微服務所在集群:{},被調用微服務所在集群:{},當前微服務的版本:{},被調用微服務版本:{},Host:{},Port:{}",
currentClusterName, chooseInstance.getClusterName(), chooseInstance.getMetadata().get("version"),
chooseInstance.getMetadata().get("current-version"), chooseInstance.getIp(), chooseInstance.getPort());
}
return new NacosServer(chooseInstance);
} catch (NacosException e) {
log.error("error,", e);
return null;
}
}
}