springcloud按版本发布微服务达到不停机更新的效果

news2025/1/16 16:10:42

本文基于以下环境完成

  • 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);
                    }
                }
            }
        };
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1628447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【优质书籍推荐】AIGC时代的PyTorch 深度学习与企业级项目实战

大家好&#xff0c;我是herosunly。985院校硕士毕业&#xff0c;现担任算法研究员一职&#xff0c;热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名&#xff0c;CCF比赛第二名&#xff0c;科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的…

Facebook的未知力量:数字世界的新引擎

在数字化的时代&#xff0c;社交媒体已经成为了我们日常生活中不可或缺的一部分&#xff0c;而Facebook作为其中的巨头&#xff0c;其影响力远远超出了我们的想象。但是&#xff0c;Facebook背后隐藏的力量和影响远不止于此&#xff0c;它正逐渐成为数字世界的新引擎&#xff0…

设计模式:单例、原型和生成器

在这篇文章中&#xff0c;我们将重点介绍其余的创建模式&#xff1a;Singleton&#xff0c;Builder和Prototype。 在我看来&#xff0c;这些模式不如工厂重要。然而&#xff0c;了解它们仍然很有用。我将提供UML描述&#xff0c;简单的java示例&#xff08;这样即使你不了解jav…

stm32 hid自定义接收发送程序开发过程记录

cubleMX配置如下 修改端点描述符一次传输的数据大小 根据cubelMX标准在这里修改 编译错误 直接修改&#xff08;因为没有使用nodef &#xff09;编译通过 修改报告描述符&#xff08;默认的描述符无法传输数据&#xff09; 参考&#xff1a;USB协议详解第10讲&#xff08;USB描…

基于SpringBoot的“在线BLOG网”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“在线BLOG网”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 在线BLOG网结构功能图 管理员登录功能界面 用户信息…

电脑屏幕监控软件有哪些?大型企业都在用的电脑屏幕监控软件

电脑监控软件&#xff0c;这个东西真的很重要&#xff0c;就像保镖一样&#xff0c;保护我们的电脑和数据安全。它能预防坏事发生&#xff0c;还能在事情发生时及时控制&#xff0c;事后还能帮我们找出问题的根源。对于那些经常担心数据泄密的企业来说&#xff0c;电脑屏幕监控…

2024 XYCTF Web 方向 wp 全解

2024 XYCTF Web 方向 全解 文章目录 2024 XYCTF Web 方向 全解Webezhttp考点:信息泄露基础发包 warm up考点:php黑魔法变量覆盖 ezRCE考点:无字母RCE(bashfuck)shell变量构造RCE ezmd5考点:md5文件强相等 ezunserilze考点&#xff1a;引用绕过强相等php原生类读文件 牢牢记住&a…

【加密周报】中美下周有“大事”发生!准备联手引爆比特币大行情?美国大型养老基金和梅隆银行已持有比特币ETF!

自减半之后&#xff0c;比特币便进入了横盘状态&#xff0c;始终在6-6.5万美元价格区间震荡。4月24日&#xff0c;香港证监会官网正式公示虚拟资产现货ETF获批名单&#xff0c;华夏&#xff08;香港&#xff09;、嘉实国际、博时国际旗下相关产品均在其列&#xff0c;并计划将于…

打印给定数组中每一个数字

如何给定1-10的数字 #include<stdio.h> int main() {int arr[] { 1,2,3,4,5,6,7,8,9,10 };// 0 9//[]--下标引用操作符int i;int sz sizeof(arr) / sizeof(arr[0]);//10个数for (i 0; i < sz; i){printf("%d ", arr[i]);}re…

【项目分享】用 Python 写一个桌面倒计日程序!

事情是这样的&#xff0c;我们班主任想委托我做一个程序&#xff0c;能显示还有几天考试。我立即理解了这个意思&#xff0c;接下了这个项目。 话不多说&#xff0c;来看看这个项目吧—— 项目简介 仓库地址&#xff1a;https://gitee.com/yaoqx/desktop-countdown-day 这是 …

排序 “肆” 之归并排序

1. 归并排序 1.1 原理介绍 归并排序的基本原理是将一个未排序的数组分解为较小的子数组&#xff0c;然后递归地对这些子数组进行排序&#xff0c;最后再将排好序的子数组合并成一个有序数组。其核心操作是将一维数组中前后相邻的两个有序序列归并为一个有序序列。 其主要步骤包…

C++链表操作入门

数据结构基础&#xff1a;链表操作入门 数据结构基础&#xff1a;链表操作入门链表的基本概念链表的基本操作输出链表插入节点删除节点查找值 完整的链表操作示例结语 数据结构基础&#xff1a;链表操作入门 在计算机科学中&#xff0c;数据结构是组织和存储数据的方式&#x…

监控上网行为的软件 五款上网行为监控软件推荐

在这个数字化时代&#xff0c;员工上网行为的管理与监控变得越来越重要。市面上涌现出众多监控员工上网的软件&#xff0c;它们各具特色&#xff0c;各有千秋。接下来&#xff0c;就让我们一起探索一下这些神奇的软件吧&#xff01; 1、安企神点击领取免费试用版 这是一款功能…

LMDeploy量化部署LLMVLM实践-笔记五

本次课程由西北工业大学博士生、书生浦源挑战赛冠军队伍队长、第一期书生浦语大模型实战营优秀学员【安泓郡】讲解【OpenCompass 大模型评测实战】课程 课程视频&#xff1a;https://www.bilibili.com/video/BV1tr421x75B/ 课程文档&#xff1a;https://github.com/InternLM/…

12 内核开发-任务调度之tasklet

12 内核开发-任务调度之tasklet 课程简介&#xff1a; Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础&#xff0c;让他们能够理解和参与到Linux内核的开发过程中。 课…

C++系列-命名空间

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 命名空间 在C/C中&#xff0c;变量&#xff0c;函数和后面要学到的类都是大量存在的&#xff0c;这些变量&#xff0c;函数和类的名称都存在于全局作用域中&#xff0c;可能会导…

【Linux系统化学习】生产者消费者模型(阻塞队列和环形队列)

目录 生产者消费者模型 什么是生产者消费者模型 为什么要使用生产者消费者模型 生产者消费者模型的优点 为什么生产者和生产者要互斥&#xff1f; 为什么消费者和消费者要互斥&#xff1f; 为什么生产者和消费者既是互斥又是同步&#xff1f; 基于BlockingQueue的生产者…

第四百八十一回

文章目录 1. 概念介绍2. 使用方法2.1 固定样式2.2 自定义样式 3. 示例代码4. 内容总结 我们在上一章回中介绍了"GetMaterialApp组件"相关的内容&#xff0c;本章回中将介绍使用get显示SnackBar.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在介…

Pandas 2.2 中文官方教程和指南(十一·一)

原文&#xff1a;pandas.pydata.org/docs/ PyArrow 功能 原文&#xff1a;pandas.pydata.org/docs/user_guide/pyarrow.html pandas 可以利用PyArrow来扩展功能并改善各种 API 的性能。这包括&#xff1a; 与 NumPy 相比&#xff0c;拥有更广泛的数据类型 对所有数据类型支持缺…

matlab新手快速上手3(差分进化算法)

本文用经典差分进化框架模板&#xff0c;对matlab新手友好&#xff0c;快速上手看懂matlab代码&#xff0c;快速应用实践&#xff0c;源代码在文末给出。 差分进化算法定义&#xff1a; 差分进化算法&#xff08;Differential Evolution&#xff0c;简称DE算法&#xff09;是…