Implementación de lanzamiento en escala de grises basado en Spring Cloud Gateway + Nacos (versión reactiva)

¿Qué es la publicación en escala de grises?

La liberación en escala de grises (también conocida como liberación canaria) se refiere a un método de liberación que puede realizar una transición suave entre el blanco y el negro. Se pueden realizar pruebas A / B en él, es decir, algunos usuarios continúan utilizando la función A del producto y algunos usuarios comienzan a utilizar la función B del producto. Si los usuarios no tienen objeciones a B, amplíe gradualmente el alcance y migre todos los usuarios a B Ven. La liberación de grises puede garantizar la estabilidad del sistema en general, y los problemas se pueden encontrar y ajustar en el nivel de gris inicial para garantizar su impacto.

Este artículo usa springcloud gateway + nacos para demostrar cómo lograr la publicación en escala de grises. Si no está familiarizado con springcloud gateway y nacos, puede leer primero el siguiente artículo y luego leer este artículo.

Introducción oficial de springcloud gateway

Introducción oficial de Nacos

La idea general de realización:

  • Escribir enrutamiento en escala de grises con pesos
  • Escribe un filtro personalizado
  • La configuración del servicio Nacos requiere la información de metadatos y el peso del servicio publicado en escala de grises.
  • El enrutamiento en escala de grises extrae información de metadatos y pesos del servicio nacos y luego devuelve la instancia de servicio que cumple con los requisitos al filtro personalizado de acuerdo con el algoritmo de ponderación.
  • La configuración del archivo de configuración de la puerta de enlace requiere servicios de enrutamiento en escala de grises (porque el código de este artículo no tiene una puerta de enlace para implementar el enrutamiento dinámico; de lo contrario, el enrutamiento en escala de grises puede configurarse en el centro de configuración y extraerse del centro de configuración)
  • Filter transmite de forma transparente las instancias de servicio a otros filtros como NettyRoutingFilter a través del modo de cadena de responsabilidad

Ingrese al combate real a continuación

texto

1. La versión de desarrollo utilizada

    <jdk.version>1.8</jdk.version>
    <!-- spring cloud -->
    <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
    <spring-boot.version>2.2.5.RELEASE</spring-boot.version>
    <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>

2. Introducción a Pom.xml

   <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

    </dependencies>

ps: el tarro de Nacos debe sacarse de la dependencia de la cinta, de lo contrario, el equilibrador de carga no tendrá efecto

3. Escriba enrutamiento ponderado

 public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private static final Log log = LogFactory.getLog(GrayLoadBalancer.class);
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private  String serviceId;




    public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }


    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        HttpHeaders headers = (HttpHeaders) request.getContext();
        if (this.serviceInstanceListSupplierProvider != null) {
            ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
            return ((Flux)supplier.get()).next().map(list->getInstanceResponse((List<ServiceInstance>)list,headers));
        }

        return null;


    }



    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,HttpHeaders headers) {
        if (instances.isEmpty()) {
            return getServiceInstanceEmptyResponse();
        } else {
            return getServiceInstanceResponseWithWeight(instances);
        }
    }

    /**
     * 根据版本进行分发
     * @param instances
     * @param headers
     * @return
     */
    private Response<ServiceInstance> getServiceInstanceResponseByVersion(List<ServiceInstance> instances, HttpHeaders headers) {
        String versionNo = headers.getFirst("version");
        System.out.println(versionNo);
        Map<String,String> versionMap = new HashMap<>();
        versionMap.put("version",versionNo);
        final Set<Map.Entry<String,String>> attributes =
                Collections.unmodifiableSet(versionMap.entrySet());
        ServiceInstance serviceInstance = null;
        for (ServiceInstance instance : instances) {
            Map<String,String> metadata = instance.getMetadata();
            if(metadata.entrySet().containsAll(attributes)){
                serviceInstance = instance;
                break;
            }
        }

        if(ObjectUtils.isEmpty(serviceInstance)){
            return getServiceInstanceEmptyResponse();
        }
        return new DefaultResponse(serviceInstance);
    }

    /**
     *
     * 根据在nacos中配置的权重值,进行分发
     * @param instances
     *
     * @return
     */
    private Response<ServiceInstance> getServiceInstanceResponseWithWeight(List<ServiceInstance> instances) {
        Map<ServiceInstance,Integer> weightMap = new HashMap<>();
        for (ServiceInstance instance : instances) {
            Map<String,String> metadata = instance.getMetadata();
            System.out.println(metadata.get("version")+"-->weight:"+metadata.get("weight"));
            if(metadata.containsKey("weight")){
                weightMap.put(instance,Integer.valueOf(metadata.get("weight")));
            }
        }
        WeightMeta<ServiceInstance> weightMeta = WeightRandomUtils.buildWeightMeta(weightMap);
        if(ObjectUtils.isEmpty(weightMeta)){
            return getServiceInstanceEmptyResponse();
        }
        ServiceInstance serviceInstance = weightMeta.random();
        if(ObjectUtils.isEmpty(serviceInstance)){
            return getServiceInstanceEmptyResponse();
        }
        System.out.println(serviceInstance.getMetadata().get("version"));
        return new DefaultResponse(serviceInstance);
    }

    private Response<ServiceInstance> getServiceInstanceEmptyResponse() {
        log.warn("No servers available for service: " + this.serviceId);
        return new EmptyResponse();
    }

4. Filtro personalizado

public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {

    private static final Log log = LogFactory.getLog(ReactiveLoadBalancerClientFilter.class);
    private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
    private final LoadBalancerClientFactory clientFactory;
    private LoadBalancerProperties properties;

    public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties 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 && ("grayLb".equals(url.getScheme()) || "grayLb".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 {
                    URI uri = exchange.getRequest().getURI();
                    String overrideScheme = null;
                    if (schemePrefix != null) {
                        overrideScheme = url.getScheme();
                    }

                    DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance((ServiceInstance)response.getServer(), 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);
        }
    }

    protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
        return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
    }

    private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
        URI uri = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        GrayLoadBalancer loadBalancer = new GrayLoadBalancer(clientFactory.getLazyProvider(uri.getHost(), ServiceInstanceListSupplier.class), uri.getHost());
        if (loadBalancer == null) {
            throw new NotFoundException("No loadbalancer available for " + uri.getHost());
        } else {
            return loadBalancer.choose(this.createRequest(exchange));
        }
    }

    private Request createRequest(ServerWebExchange exchange) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        Request<HttpHeaders> request = new DefaultRequest<>(headers);
        return request;
    }
}

5. Configure un filtro personalizado para la gestión de primavera

@Configuration
public class GrayGatewayReactiveLoadBalancerClientAutoConfiguration {
    public GrayGatewayReactiveLoadBalancerClientAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean({GrayReactiveLoadBalancerClientFilter.class})
    public GrayReactiveLoadBalancerClientFilter grayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        return new GrayReactiveLoadBalancerClientFilter(clientFactory, properties);
    }

}

6. Escriba la configuración del gateway application.yml

server:
  port: 9082
# 配置输出日志
logging:
  level:
    org.springframework.cloud.gateway: TRACE
    org.springframework.http.server.reactive: DEBUG
    org.springframework.web.reactive: DEBUG
    reactor.ipc.netty: DEBUG

#开启端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
spring:
  application:
    name: gateway-reactor-gray
  cloud:
     nacos:
       discovery:
        server-addr: localhost:8848
     gateway:
       discovery:
         locator:
           enabled: true
           lower-case-service-id: true
       routes:
         - id: hello-consumer
           uri: grayLb://hello-consumer
           predicates:
              - Path=/hello/**

La configuración grayLb en uri indica que el servicio debe publicarse en escala de grises

7. Configure la versión del servicio y el valor de peso de la versión gris en el registro nacos.

 

el peso representa el peso, la versión representa la versión

para resumir

Lo anterior es el proceso para lograr la publicación en escala de grises. Hay muchas formas de lograr la publicación en escala de grises. El artículo solo proporciona una idea. Aunque springcloud recomienda oficialmente usar el equilibrador de carga en lugar de la cinta. Debido a que la cinta está bloqueando, pero desde el algoritmo oficial de equilibrio de carga del balanceador de carga, el balanceador de carga actual solo admite el algoritmo de sondeo de forma predeterminada, y otros algoritmos deben ampliarse por sí mismos, mientras que la cinta admite 7 algoritmos de forma predeterminada. El algoritmo predeterminado es básicamente Puede satisfacer nuestras necesidades. En segundo lugar, la cinta admite el procesamiento de carga diferida, el tiempo de espera y el reintento, la integración hystrix del disyuntor y otras configuraciones. Loadbalancer actualmente admite el reintento. Entonces, si el entorno formal desea implementar la versión en escala de grises por su cuenta, considere extender la cinta. La implementación de este artículo es solo como un complemento de extensión, después de todo, springcloud recomienda el balanceador de carga, simplemente escriba una demostración para lograrlo.

Finalmente, la realización del lanzamiento en escala de grises, también hay una realización de código abierto en la industria-Discovery, los amigos interesados ​​pueden verlo a través del siguiente enlace

https://github.com/Nepxion/Discovery

enlace de demostración

https://github.com/lyb-geek/gateway

Original: https://cloud.tencent.com/developer/article/1620795

Supongo que te gusta

Origin blog.csdn.net/a1036645146/article/details/109464732
Recomendado
Clasificación