本文基于以下环境完成
- spring-boot 2.3.2.RELEASE
- spring-cloud Hoxton.SR9
- spring-cloud-alibaba 2.2.6.RELEASE
- spring-cloud-starter-gateway 2.2.6.RELEASE
- spring-cloud-starter-loadbalancer 2.2.6.RELEASE
- nacos 2.0.3
一、思路
实现思路:
前端项目在请求后端接口时,携带一个版本号version,网关gateway在接收到这个version后,根据version的值去选择对应的微服务实例,服务之间的调用openfeign也通过版本号去选择对应的服务实例
举例:
每次更新项目时版本号递增,比如目前在使用的后端项目版本为1.0.0,那么前端携带的版本也是1.0.0,
当我们要更新项目时,后端新的版本为2.0.0 前端项目的版本也为2.0.0,如果用户没有刷新页面,那么还是携带旧的版本号1.0.0, 那么请求都会转发到后端1.0.0, 如果用户刷新页面,那么就会携带新版本号2.0.0,此时请求被转发后端2.0.0版本
实现方式:
排除自带的ribbon依赖
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
</exclusion>
</exclusions>
引入spring-cloud-loadbalancer依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
spring-cloud-loadbalancer文档地址:点我跳转
配置微服务版本号
spring:
cloud:
nacos:
discovery:
metadata:
version: 2.0.0
后续可以在nacos中修改
二 、重写网关的负载策略
2.1 重写轮训负载均衡
spring-cloud-loadbalancer默认的负载策略是轮训,实现类为org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer,我们主要看里面的choose和getInstanceResponse方法,在getInstanceResponse方法中传入ServiceInstance集合,然后从这个集合中选择一个合适的instance实例,那么我们只要在重写这个方法在里面添加版本的筛选
那么我们再看一下这个类是如何实例化的,主要是org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration
注意@ConditionalOnMissingBean这个注解,只有当spring容器中没有ReactorLoadBalancer类型的bean时才会实例化。
我们新建一个VersionLoadBalancer类,跟RoundRobinLoadBalancer一样去实现ReactorServiceInstanceLoadBalancer接口(其实就是把他的代码copy过来,然后添加我们自定义的筛选条件)
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.reactive.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
/**
* {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer}
*
* @Author
* @Date 2024/4/18 11:46
* @Description
**/
public class VersionLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(VersionLoadBalancer.class);
private final AtomicInteger position;
@Deprecated
private ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
/**
* @param serviceId id of the service for which to choose an instance
* @param serviceInstanceSupplier a provider of {@link ServiceInstanceSupplier} that
* will be used to get available instances
* @deprecated Use {@link #VersionLoadBalancer(ObjectProvider, String)}} instead.
*/
@Deprecated
public VersionLoadBalancer(String serviceId,
ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier) {
this(serviceId, serviceInstanceSupplier, new Random().nextInt(1000));
}
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
*/
public VersionLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
}
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
* @param seedPosition Round Robin element position marker
*/
public VersionLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}
/**
* @param serviceId id of the service for which to choose an instance
* @param serviceInstanceSupplier a provider of {@link ServiceInstanceSupplier} that
* will be used to get available instances
* @param seedPosition Round Robin element position marker
* @deprecated Use {@link #VersionLoadBalancer(ObjectProvider, String, int)}}
* instead.
*/
@Deprecated
public VersionLoadBalancer(String serviceId,
ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier,
int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceSupplier = serviceInstanceSupplier;
this.position = new AtomicInteger(seedPosition);
}
@SuppressWarnings("all")
@Override
// see original
// https://github.com/Netflix/ocelli/blob/master/ocelli-core/
// src/main/java/netflix/ocelli/loadbalancer/RoundRobinLoadBalancer.java
public Mono<Response<ServiceInstance>> choose(Request request) {
HttpHeaders headers = (HttpHeaders) request.getContext();
String requestVersion = headers.getFirst("version");
// TODO: move supplier to Request?
// Temporary conditional logic till deprecated members are removed.
if (serviceInstanceListSupplierProvider != null) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map(m -> this.getInstanceResponse(m, requestVersion));
}
ServiceInstanceSupplier supplier = this.serviceInstanceSupplier
.getIfAvailable(NoopServiceInstanceSupplier::new);
return supplier.get().collectList().map(m -> this.getInstanceResponse(m, requestVersion));
}
@SuppressWarnings("deprecation")
private Response<ServiceInstance> getInstanceResponse(
List<ServiceInstance> instances, String requestVersion) {
List<ServiceInstance> serviceInstances = this.filterInstance(instances, requestVersion);
if (serviceInstances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId + " ,request version: " + requestVersion);
return new EmptyResponse();
}
// TODO: enforce order?
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = serviceInstances.get(pos % serviceInstances.size());
return new DefaultResponse(instance);
}
/**
* 获取对应的版本
*
* @param instances
* @param requestVersion
* @return
*/
private List<ServiceInstance> filterInstance(List<ServiceInstance> instances, String requestVersion) {
if (StringUtils.isEmpty(requestVersion)) {
return instances;
}
return instances.stream().filter(f -> requestVersion.equals(f.getMetadata().get("version"))).collect(Collectors.toList());
}
}
然后新建一个配置类VersionLoadBalancerConfig,注意不能使用@Configuration
import com.demo.gateway.filter.VersionLoadBalancer;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* 注意: 这里不能使用@Configuration !!!
* 参考 {@link org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration}
*
* @Author
* @Date 2024/4/18 11:50
* @Description
**/
public class VersionLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new VersionLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}
}
官方文档示例
然后在项目的启动类添加@LoadBalancerClients(defaultConfiguration = {VersionLoadBalancerConfig.class})
import com.demo.gateway.config.VersionLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
/**
* @Author
* @Date 2022/10/10 16:38
* @Description
**/
@EnableDiscoveryClient
@SpringBootApplication
@LoadBalancerClients(defaultConfiguration = {VersionLoadBalancerConfig.class})
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
2.2 重写过滤器
此时运行项目发现碰到空指针异常,断点后发现是VersionLoadBalancer.choose方法里面request.getContext()是个null值,那么我们再看一下是在哪里调用了这个方法,主要是org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter
那么我们需要重写这个过滤器,在choose方法里面将我们http请求的header对象传递给VersionLoadBalancer,我们再看一下ReactiveLoadBalancerClientFilter是怎么初始化的,主要是org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration
那么我们就可以新建一个VersionLoadBalancerFilter去覆盖原来的ReactiveLoadBalancerClientFilter(如果直接新增一个过滤器那么原来的过滤器也会执行,还会有过滤器的执行顺序问题)
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
/**
* 参考 {@link ReactiveLoadBalancerClientFilter}
* @Author SYLIANG
* @Date 2024/4/19 10:48
* @Description
**/
public class VersionLoadBalancerFilter extends ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(VersionLoadBalancerFilter.class);
private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10250;
private final LoadBalancerClientFactory clientFactory;
private LoadBalancerProperties properties;
public VersionLoadBalancerFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
super(clientFactory, properties);
this.clientFactory = clientFactory;
this.properties = properties;
}
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
}
return this.choose(exchange).doOnNext((response) -> {
if (!response.hasServer()) {
throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " + url.getHost());
} else {
ServiceInstance retrievedInstance = (ServiceInstance)response.getServer();
URI uri = exchange.getRequest().getURI();
String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance, overrideScheme);
URI requestUrl = this.reconstructURI(serviceInstance, uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
}
}).then(chain.filter(exchange));
} else {
return chain.filter(exchange);
}
}
@Override
protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
}
@SuppressWarnings("deprecation")
private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory
.getInstance(uri.getHost(), VersionLoadBalancer.class);
if (loadBalancer == null) {
throw new NotFoundException("No loadbalancer available for " + uri.getHost());
} else {
return loadBalancer.choose(this.createRequest(exchange));
}
}
@SuppressWarnings("deprecation")
private Request createRequest(ServerWebExchange exchange) {
HttpHeaders headers = exchange.getRequest().getHeaders();
Request<HttpHeaders> request = new DefaultRequest<>(headers);
return request;
}
}
注意choose方法里面的ReactorLoadBalancer loadBalancer = this.clientFactory.getInstance(uri.getHost(), VersionLoadBalancer.class); 这里不能直接new,否则轮训会失效
新建配置类VersionFilterConfiguration
import com.personnel.gateway.filter.VersionLoadBalancerFilter;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;
/**
* ReactiveLoadBalancerClientFilter过滤器的Request没有请求头信息
* 参考 {@link org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration}
*
* @Author
* @Date 2024/4/22 15:56
* @Description
**/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({LoadBalancerClient.class, ReactiveLoadBalancer.class,
LoadBalancerAutoConfiguration.class, DispatcherHandler.class})
@AutoConfigureBefore(GatewayLoadBalancerClientAutoConfiguration.class)
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class VersionFilterConfiguration {
@Bean
public ReactiveLoadBalancerClientFilter versionLoadBalancer(LoadBalancerClientFactory clientFactory,
LoadBalancerProperties properties) {
return new VersionLoadBalancerFilter(clientFactory, properties);
}
}
三、重写openfeign的负载策略
3.1 spring-cloud-loadbalancer负载
我们同样使用spring-cloud-loadbalancer作为负载均衡器,新建自定义负载策略VersionLoadBalancer类,注意这里面的版本号version通过spring-web的ServletRequestAttributes获取
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.reactive.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
*
* 重写负载均衡策略: 通过版本选择服务实例
* {@link RoundRobinLoadBalancer}
*
* @Author
* @Date 2024/4/18 11:46
* @Description
**/
public class VersionLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(VersionLoadBalancer.class);
private final AtomicInteger position;
@Deprecated
private ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
private final String version = "version";
/**
* @param serviceId id of the service for which to choose an instance
* @param serviceInstanceSupplier a provider of {@link ServiceInstanceSupplier} that
* will be used to get available instances
* @deprecated Use {@link #VersionLoadBalancer(ObjectProvider, String)}} instead.
*/
@Deprecated
public VersionLoadBalancer(String serviceId,
ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier) {
this(serviceId, serviceInstanceSupplier, new Random().nextInt(1000));
}
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
*/
public VersionLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
}
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
* @param seedPosition Round Robin element position marker
*/
public VersionLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}
/**
* @param serviceId id of the service for which to choose an instance
* @param serviceInstanceSupplier a provider of {@link ServiceInstanceSupplier} that
* will be used to get available instances
* @param seedPosition Round Robin element position marker
* @deprecated Use {@link #VersionLoadBalancer(ObjectProvider, String, int)}}
* instead.
*/
@Deprecated
public VersionLoadBalancer(String serviceId,
ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier,
int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceSupplier = serviceInstanceSupplier;
this.position = new AtomicInteger(seedPosition);
}
@SuppressWarnings("all")
@Override
// see original
// https://github.com/Netflix/ocelli/blob/master/ocelli-core/
// src/main/java/netflix/ocelli/loadbalancer/RoundRobinLoadBalancer.java
public Mono<Response<ServiceInstance>> choose(Request request) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String requestVersion = requestAttributes.getRequest().getHeader(version);
// TODO: move supplier to Request?
// Temporary conditional logic till deprecated members are removed.
if (serviceInstanceListSupplierProvider != null) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map(m -> this.getInstanceResponse(m, requestVersion));
}
ServiceInstanceSupplier supplier = this.serviceInstanceSupplier
.getIfAvailable(NoopServiceInstanceSupplier::new);
return supplier.get().collectList().map(m -> this.getInstanceResponse(m, requestVersion));
}
@SuppressWarnings("deprecation")
private Response<ServiceInstance> getInstanceResponse(
List<ServiceInstance> instances, String requestVersion) {
List<ServiceInstance> serviceInstances = this.filterInstance(instances, requestVersion);
if (serviceInstances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId + " ,request version: " + requestVersion);
return new EmptyResponse();
}
// TODO: enforce order?
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = serviceInstances.get(pos % serviceInstances.size());
return new DefaultResponse(instance);
}
/**
* 获取对应的版本
*
* @param instances
* @param requestVersion
* @return
*/
private List<ServiceInstance> filterInstance(List<ServiceInstance> instances, String requestVersion) {
if (StringUtils.isEmpty(requestVersion)) {
return instances;
}
return instances.stream().filter(f -> requestVersion.equals(f.getMetadata().get(version))).collect(Collectors.toList());
}
}
新建配置类VersionLoadBalancerConfig
import com.personnel.common.load.VersionLoadBalancer;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* 注意: 这里不能使用@Configuration !!!
* 参考 {@link org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration}
*
* @Author
* @Date 2024/4/18 11:50
* @Description
**/
public class VersionLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new VersionLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}
}
在启动类中添加@LoadBalancerClients(defaultConfiguration = {VersionLoadBalancerConfig.class})
3.2 ribbon负载
如果使用ribbon则使用以下配置
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.google.common.base.Optional;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.ArrayList;
import java.util.List;
/**
* openfeign调用根据版本选择对应的实例
* @Author
* @Date 2024/1/18 14:16
* @Description
**/
@Component
public class VersionReleaseRule extends ZoneAvoidanceRule {
private final String version = "version";
@Override
public Server choose(Object key) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String requestVersion = requestAttributes.getRequest().getHeader(version);
if (!StringUtils.isEmpty(requestVersion)) {
List<Server> serverList = this.getLoadBalancer().getAllServers();
List<Server> versionServers = new ArrayList<>();
for (Server server : serverList) {
NacosServer nacosServer = (NacosServer) server;
if (requestVersion.equals(nacosServer.getMetadata().get(version))) {
versionServers.add(server);
}
}
if (!CollectionUtils.isEmpty(versionServers)) {
Optional<Server> serverOptional = this.getPredicate().chooseRoundRobinAfterFiltering(versionServers, key);
return serverOptional.isPresent() ? serverOptional.get() : null;
}
}
return super.choose(key);
}
}
四、openfeign携带token
在openfeign调用时如果没有携带token则添加以下配置
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* openfeign请求添加token
* @Author SYLIANG
* @Date 2024/4/25 11:22
* @Description
**/
@Configuration
public class FeignClientInterceptorConfig {
private final String authorization = "Authorization";
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
// 从当前请求的 Header 中获取 token
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
String token = attributes.getRequest().getHeader(authorization);
if (token != null && !token.isEmpty()) {
// 添加 token 到请求头部
requestTemplate.header(authorization, token);
}
}
}
};
}
}