負載均衡的兩種方式
如何實現負載均衡
目前已經實現讓A總能找到B,如何實現負載均衡
負載均衡的兩種方式
- 服務器端負載均衡
- 客戶端負載均衡
使用Ribbo實現負載均衡
Ribbon是什么
Netflix開源的客戶端側負載均衡器
引入Ribbon后的架構演進
整合Ribbon實現負載均衡
// 在spring容器中,創建一個對象,類型RestTemplate;名稱/ID是:restTemplate
// <bean id="restTemplate" class="xxx.RestTemplate"/>
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
return template;
}
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test-rest-template-sentinel/{userId}")
public UserDTO test(@PathVariable Integer userId) {
return this.restTemplate
.getForObject(
"http://user-center/users/{userId}",
UserDTO.class, userId);
}
Ribbon的組成
Ribbon內置的負載均衡規則
細粒度配置自定義
- Java代碼配置
- 用配置屬性配置
- 最佳實踐總結
Java代碼配置
@Configuration
@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new NacosSameClusterWeightedRule();
}
}
用配置屬性配置
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
兩種方式對比
Ribbon全局配置
@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new NacosSameClusterWeightedRule();
}
}
Ribbon飢餓加載配置方式
ribbon:
eager-load:
enabled: true
clients: user-center
擴展Ribbon-支持Nacos權重
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.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 讀取配置文件,並初始化NacosWeightedRule
}
@Override
public Server choose(Object key) {
try {
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// log.info("lb = {}", loadBalancer);
// 想要請求的微服務的名稱
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) {
return null;
}
}
}
// spring cloud commons --> 定義了標准
// spring cloud loadbalancer --> 沒有權重
擴展Ribbon-同一集群優先調用
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.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.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
try {
// 拿到配置文件中的集群名稱 BJ
String clusterName = nacosDiscoveryProperties.getClusterName();
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 想要請求的微服務的名稱
String name = loadBalancer.getName();
// 拿到服務發現的相關API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 1. 找到指定服務的所有實例 A
List<Instance> instances = namingService.selectInstances(name, true);
// 2. 過濾出相同集群下的所有實例 B
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
.collect(Collectors.toList());
// 3. 如果B是空,就用A
List<Instance> instancesToBeChosen = new ArrayList<>();
if (CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToBeChosen = instances;
log.warn("發生跨集群的調用, name = {}, clusterName = {}, instances = {}",
name,
clusterName,
instances
);
} else {
instancesToBeChosen = sameClusterInstances;
}
// 4. 基於權重的負載均衡算法,返回1個實例
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
log.info("選擇的實例是 port = {}, instance = {}", instance.getPort(), instance);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("發生異常了", e);
return null;
}
}
}
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}
擴展Ribbon-基於元數據的版本控制
元數據
元數據就是一堆的描述信息,以map存儲。舉個例子:
spring:
cloud:
nacos:
metadata:
# 自己這個實例的版本
version: v1
# 允許調用的提供者版本
target-version: v1
代碼實現
@Slf4j
public class NacosFinalRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public Server choose(Object key) {
// 負載均衡規則:優先選擇同集群下,符合metadata的實例
// 如果沒有,就選擇所有集群下,符合metadata的實例
// 1. 查詢所有實例 A
// 2. 篩選元數據匹配的實例 B
// 3. 篩選出同cluster下元數據匹配的實例 C
// 4. 如果C為空,就用B
// 5. 隨機選擇實例
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version");
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
// 所有實例
List<Instance> instances = namingService.selectInstances(name, true);
List<Instance> metadataMatchInstances = instances;
// 如果配置了版本映射,那么只調用元數據匹配的實例
if (StringUtils.isNotBlank(targetVersion)) {
metadataMatchInstances = instances.stream()
.filter(instance -> Objects.equals(targetVersion, instance.getMetadata().get("version")))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(metadataMatchInstances)) {
log.warn("未找到元數據匹配的目標實例!請檢查配置。targetVersion = {}, instance = {}", targetVersion, instances);
return null;
}
}
List<Instance> clusterMetadataMatchInstances = metadataMatchInstances;
// 如果配置了集群名稱,需篩選同集群下元數據匹配的實例
if (StringUtils.isNotBlank(clusterName)) {
clusterMetadataMatchInstances = metadataMatchInstances.stream()
.filter(instance -> Objects.equals(clusterName, instance.getClusterName()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
clusterMetadataMatchInstances = metadataMatchInstances;
log.warn("發生跨集群調用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
}
}
Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
return new NacosServer(instance);
} catch (Exception e) {
log.warn("發生異常", e);
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
public class ExtendBalancer extends Balancer {
/**
* 根據權重,隨機選擇實例
*
* @param instances 實例列表
* @return 選擇的實例
*/
public static Instance getHostByRandomWeight2(List<Instance> instances) {
return getHostByRandomWeight(instances);
}
}
深入理解Nacos的Namespace
服務調用不能跨namespace