在微服务架构中,服务通常会部署在不同的集群中。每个集群可能位于不同的物理位置或数据中心,以提高可用性和容错性。同一服务的多个实例可能分布在不同的集群中。在负载均衡调用时,需要减少跨集群的网络延迟和资源开销,提高服务之间的通信效率。还要考虑到不同的服务实例可能具有不同的性能和资源配置,有些实例可能更强大,可以处理更多的请求,而有些可能资源有限。要考虑这些差异,以便更合理地分配负载。
本文就是基于Nacos实现了两个主要特性:同集群优先调用和基于权重的负载均衡 实现的自定义算法。
同集群优先调用是指在负载均衡时,优先选择在相同集群中注册的服务实例。这样做的目的是减少跨集群的网络延迟和资源开销,提高服务之间的通信效率。应该通过筛选出属于相同集群的服务实例,然后再进行负载均衡选择,以确保首选选择来自同一集群的实例。
基于权重的负载均衡考虑了服务器配置的差异,以便更合理地分配负载。每个服务实例都分配了一个权重,表示其处理能力的相对大小。在负载均衡过程中,具有较高权重的实例将获得更多的请求,而具有较低权重的实例将获得较少的请求。
这个特性主要用于在多个集群中进行负载均衡和实例选择,以减少跨集群的网络延迟。如果只有一个集群,那么选择集群的问题就不存在了。在这种情况下,只用考虑权重负载均衡。
微服务实例注册进Nacos时自定义元数据(instanceId)
import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration;
import com.alibaba.cloud.nacos.discovery.NacosWatch;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({SimpleDiscoveryClientAutoConfiguration.class})
@AutoConfigureAfter({NacosDiscoveryAutoConfiguration.class})
public class NacosDiscoveryClientConfig {
public NacosDiscoveryClientConfig() {
}
@Value("${server.port}")
private String port;
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(
value = {"spring.cloud.nacos.discovery.watch.enabled"},
matchIfMissing = true
)
public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) {
// 在服务注册Nacos时增加元数据 instanceId格式:ip#port#cluster#group@@service
Map<String, String> metadata = nacosDiscoveryProperties.getMetadata();
metadata.put("nacos.instanceId", getNacosInstanceId(nacosDiscoveryProperties));
return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
}
public String getNacosInstanceId(NacosDiscoveryProperties nacosDiscoveryProperties) {
StringBuilder builder = new StringBuilder();
String separator1 = "#";
String separator2 = "@";
return builder.append(nacosDiscoveryProperties.getIp())
.append(separator1)
.append(port)
.append(separator1)
.append(nacosDiscoveryProperties.getClusterName())
.append(separator1)
.append(nacosDiscoveryProperties.getGroup())
.append(separator2)
.append(separator2)
.append(nacosDiscoveryProperties.getService())
.toString();
}
}
自定义负载均衡策略
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.core.Balancer;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
// 自定义负载均衡需要实现 ReactorServiceInstanceLoadBalancer 接口
public class NacosSameClusterWeightedRule implements ReactorServiceInstanceLoadBalancer {
// Nacos配置属性
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
// 当前服务名称
private String serviceId;
// 服务实例列表
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceList;
public NacosSameClusterWeightedRule(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceList, String serviceId) {
this.serviceId = serviceId;
this.serviceInstanceList = serviceInstanceList;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = this.serviceInstanceList.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map(serviceInstances -> this.processInstanceResponse(supplier, serviceInstances));
}
/**
* 处理所选的服务实例并执行必要的回调
* @param supplier
* @param serviceInstances
* @return
*/
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback)supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
/**
* 根据自定义的逻辑选择适当的服务实例
* @param serviceInstances
* @return
*/
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {
if (CollectionUtils.isEmpty(serviceInstances)) {
return new EmptyResponse();
}
// 获取当前服务所在的集群名称
String clusterName = nacosDiscoveryProperties.getClusterName();
// 过滤在同一集群下注册的服务
List<ServiceInstance> instanceList = serviceInstances.stream().filter(instance -> clusterName.equals(instance.getMetadata().get("nacos.cluster"))).collect(Collectors.toList());
ServiceInstance sameClusterNameInst = CollectionUtils.isEmpty(instanceList) ? getHostByRandomWeight(serviceInstances) : getHostByRandomWeight(instanceList);
return new DefaultResponse(sameClusterNameInst);
}
/**
* 基于自定义权重计算选择适当的服务实例
* @param serviceInstances
* @return
*/
private ServiceInstance getHostByRandomWeight(List<ServiceInstance> serviceInstances) {
List<Instance> instances = Lists.newArrayList();
Map<String, ServiceInstance> dataMap = Maps.newHashMap();
serviceInstances.forEach(serviceInstance -> {
Instance instance = new Instance();
Map<String, String> metadata = serviceInstance.getMetadata();
instance.setInstanceId(metadata.get("nacos.instanceId"));
instance.setWeight(Double.parseDouble(metadata.get("nacos.weight")));
instance.setClusterName(metadata.get("nacos.cluster"));
instance.setEphemeral(Boolean.parseBoolean(metadata.get("nacos.ephemeral")));
instance.setHealthy(Boolean.parseBoolean(metadata.get("nacos.healthy")));
instance.setPort(serviceInstance.getPort());
instance.setIp(serviceInstance.getHost());
instance.setServiceName(serviceInstance.getServiceId());
instance.setMetadata(metadata);
instances.add(instance);
// key为服务ID,值为服务对象
dataMap.put(instance.getInstanceId(), serviceInstance);
});
// 调用Nacos官方提供的负载均衡权重算法
return dataMap.get(ExtendBalancer.getHostByRandomWeightCopy(instances).getInstanceId());
}
}
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeightCopy(List<Instance> instances) {
return getHostByRandomWeight(instances);
}
}
注入自定义负载均衡算法
@Configuration
public class CustomLoadBalancerConfiguration {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new NacosSameClusterWeightedRule(
loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
启动类
import com.demo.common.ApplicationConst;
import com.demo.config.CustomLoadBalancerConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@LoadBalancerClients(
@LoadBalancerClient(value = ApplicationConst.PROVIDER_SERVER_NAME, configuration = CustomLoadBalancerConfiguration.class)
)
@EnableFeignClients(basePackages = {"com.demo.remote"})
public class ConsumerServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerServerApplication.class, args);
}
}