This is how grayscale publishing of microservices should be designed

If there is a change in demand in actual production, the online service will not be updated directly. The most common way is to cut out a small part of the online traffic for experience testing. After the test, if there is no problem, it will be fully launched.

The advantage of doing this is also very obvious. Once a BUG occurs, it can guarantee the normal use of most clients.

To achieve this smooth transition, you need to use the full-link grayscale release introduced in this article.

What is Grayscale Publishing?

Grayscale release (also known as canary release) refers to a release method that can smoothly transition between black and white. A/B testing can be carried out on it, that is, let some users continue to use product feature A, and some users start to use product feature B. If users have no objection to B, then gradually expand the scope and migrate all users to B Come. Grayscale publishing can ensure the stability of the overall system, and problems can be found and adjusted at the initial grayscale to ensure their impact.

Why is the full link gray scale release?

In the previous article, we introduced the implementation of the gray-scale publishing of the gateway, which only realized the gray-scale publishing of the routing and forwarding of the gateway, as shown in the following figure:

​As shown in the figure above, the grayscale release of the gateway realizes that the gateway routes to the article service B (grayscale service) through the grayscale tag. As for the internal call from the article service B to the comment service, the grayscale tag grayTag cannot be implemented by default. Transparent transmission, so the article service B finally calls the comment service A, not the comment service B.

What needs to be implemented for full-link grayscale publishing is:

  1. The gateway forwards part of the traffic to article service B through grayscale marking

  2. Article service B can realize the transparent transmission of the gray tag grayTag, and finally call the comment service B

After the above analysis, full-link gray release needs to achieve two points:

  1. Gateway routing and forwarding to achieve gray release

  2. The service implements grayscale publishing through openFeign calls internally (transparent grayscale tag grayTag).

Grayscale routing and forwarding at the gateway layer

This article will use Ribbon+Spring Cloud Gateway to transform the load balancing strategy to achieve grayscale release.

The implementation idea is as follows:

  1. In the global filter of the gateway, mark the traffic in grayscale according to the business rules

  2. Put the gray mark into the request header and pass it to the downstream service

  3. Transform the Ribbon load balancing strategy to obtain grayscale services from the registration center according to traffic marks

  4. Request routing forwarding

The first question: According to what conditions are marked with gray scale?

This needs to be based on actual business needs, such as the region where the user is located, the type of client used, random interception of traffic...

Here I will directly use a tag grayTag, as long as this parameter is carried in the client request header and set to true, the grayscale release logic will go.

" Carry in the request header: grayTag=true "

The second question: Why do you want to add a grayscale tag to the request header and pass it to the downstream service?

This step is very critical. It is the key to transparently transmit the grayscale mark to the downstream service. Put the grayscale mark in the request header, and the downstream service only needs to obtain the grayscale mark from the request header to know whether it is a grayscale release. This and order Cards relay a principle.

The third question: How does the grayscale label request isolation?

Each request in Spring MVC starts a thread for processing, so the gray mark can be placed in ThreadLocal for thread isolation.

The fourth question: How to know which service of the registration center is a grayscale service?

Nacos supports configuring some metadata in the service, and you can configure grayscale tags in the metadata, so that you can distinguish which are grayscale services and which are normal services.

The fifth question: How to release in gray scale for a specific service?

For example, a call link involved in my "Spring Cloud Alibaba Actual Combat" is as follows:

"Requirement: Now only the article service and comment service are released in gray scale, and other services still use online running services"

At this time, the call relationship becomes the following figure:

We know that there are many services configured in the gateway route, how can we only publish the articles in grayscale?

It's very simple: you only need to make the custom Ribbon grayscale publishing rules only take effect for the article service.

This involves an annotation in Ribbon: @RibbonClients , you only need to specify the service name that needs to take effect in the value attribute, then the configuration in the gateway at this time is as follows:

@RibbonClients(value ={
        //只对文章服务进行灰度发布
        @RibbonClient(value = "article-server",configuration = GrayRuleConfig.class)
} )
@SpringBootApplication
public class GatewayApplication {
   
}

@RibbonClient can specify multiple, this annotation has the following two properties:

  • value : the name of the specified service, the service name configured in the registry

  • configuration : A custom load balancing strategy, here is the grayscale release strategy

@RibbonClients has an attribute defaultConfiguration. Once this attribute is used, the strategy published in Grayscale will take effect for all services configured in the gateway route.

The sixth question: Having said so much, how to achieve it?

First, a global filter needs to be defined in the gateway. The pseudo code is as follows:

public class GlobalGrayFilter implements GlobalFilter{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         //① 解析请求头,查看是否存在灰度发布的请求头信息,如果存在则将其放置在ThreadLocal中
        HttpHeaders headers = exchange.getRequest().getHeaders();
        if (headers.containsKey(GrayConstant.GRAY_HEADER)){
            String gray = headers.getFirst(GrayConstant.GRAY_HEADER);
            if (StrUtil.equals(gray,GrayConstant.GRAY_VALUE)){
                //②设置灰度标记
                GrayRequestContextHolder.setGrayTag(true);
            }
        }
       //③ 将灰度标记放入请求头中
   ServerHttpRequest tokenRequest = exchange.getRequest().mutate()
    //将灰度标记传递过去
    .header(GrayConstant.GRAY_HEADER,GrayRequestContextHolder.getGrayTag().toString())
    .build();
            ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
            return chain.filter(build);
    }
}

The code at ①: Obtain the grayscale mark passed by the client from the request header (here you can change it according to your own business needs), and judge whether it is a grayscale release

The code at ②: GrayRequestContextHolder is a thread isolation tool implemented by a custom ThreadLocal, which is used to store grayscale marks

The code at ③: place the gray mark in the request header and pass it to the downstream microservice, here is the same logic as the token.

"Note: This global filter must be placed before the OAuth2.0 authentication filter, and the priority should be increased"

The gray mark has been marked in the global filter and placed in the GrayRequestContextHolder. Next, you only need to modify the Ribbon's load balancing strategy to go to the registry to select the gray service.

Create GrayRule, the code is as follows:

/**
 * 灰度发布的规则
 */
public class GrayRule extends ZoneAvoidanceRule {

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }

    @Override
    public Server choose(Object key) {
        try {
            //从ThreadLocal中获取灰度标记
            boolean grayTag = GrayRequestContextHolder.getGrayTag().get();
            //获取所有可用服务
            List<Server> serverList = this.getLoadBalancer().getReachableServers();
            //灰度发布的服务
            List<Server> grayServerList = new ArrayList<>();
            //正常的服务
            List<Server> normalServerList = new ArrayList<>();
            for(Server server : serverList) {
                NacosServer nacosServer = (NacosServer) server;
                //从nacos中获取元素剧进行匹配
                if(nacosServer.getMetadata().containsKey(GrayConstant.GRAY_HEADER)
                        && nacosServer.getMetadata().get(GrayConstant.GRAY_HEADER).equals(GrayConstant.GRAY_VALUE)) {
                    grayServerList.add(server);
                } else {
                    normalServerList.add(server);
                }
            }
            //如果被标记为灰度发布,则调用灰度发布的服务
            if(grayTag) {
                return originChoose(grayServerList,key);
            } else {
                return originChoose(normalServerList,key);
            }
        } finally {
            //清除灰度标记
            GrayRequestContextHolder.remove();
        }
    }

    private Server originChoose(List<Server> noMetaServerList, Object key) {
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(noMetaServerList, key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }
    }
}

The logic is simple, as follows:

  1. Get grayscale markers

  2. Obtain grayscale service and normal service from Nacos registration center

  3. Judging according to the grayscale mark, if the grayscale is released, select a specific grayscale service for forwarding

Define a configuration class and inject the modified grayscale strategy GrayRule, as follows:

/**
 * 灰度部署的负载规则配置类
 * 注意:这个类一定不要被Spring Boot 扫描进入IOC容器中,一旦扫描进入则对全部的服务都将生效
 */
public class GrayRuleConfig {
    @Bean
    public GrayRule grayRule(){
        return new GrayRule();
    }
}

Note: This GrayRuleConfig cannot be scanned into the IOC container, once scanned into it, it will take effect globally

Because not only gateways need to use this grayscale publishing strategy, but also all microservices that involve OpenFeign calls need to be configured with grayscale publishing, so here I define a public gray-starter.

After the above steps, the gray-scale release of the gateway has been configured. At this time, you only need to specify which service gray-scale release corresponds to @RibbonClients.

openFeign transparently transmits grayscale tags

When introducing the grayscale publishing configuration of the gateway above, the grayscale tag (grayTag=true) is placed in the request header, so all that needs to be done in the downstream service is to take out the grayscale tag from the request header, and then Store it in the GrayRequestContextHolder context.

In this way, the GrayRule in the downstream service can obtain the grayscale mark from the GrayRequestContextHolder, and obtain the grayscale service from the registration center to call.

Here comes the question: how to remove the grayscale mark from the request header?

When introducing OAuth2.0 related knowledge, there was an article: actual combat! How does openFeign realize that the full link JWT token information is not lost?

It introduces the solution of token relay, using openFeign's request interceptor to configure request header information.

As shown above: openFeign does not use the original Request when calling, but creates a new Request internally, which copies the requested URL and some information about the request parameters, but the request header is not copied, so the openFeign call will lose the request header information in .

But you can copy the original request header by implementing RequestInterceptor, the code is as follows:

@Component
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        HttpServletRequest httpServletRequest = RequestContextUtils.getRequest();
        Map<String, String> headers = getHeaders(httpServletRequest);
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            //② 设置请求头到新的Request中
            template.header(entry.getKey(), entry.getValue());
        }
    }

    /**
     * 获取原请求头
     */
    private Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> map = new LinkedHashMap<>();
        Enumeration<String> enumeration = request.getHeaderNames();
        if (enumeration != null) {
            while (enumeration.hasMoreElements()) {
                String key = enumeration.nextElement();
                String value = request.getHeader(key);
                //将灰度标记的请求头透传给下个服务
                if (StrUtil.equals(GrayConstant.GRAY_HEADER,key)&&Boolean.TRUE.toString().equals(value)){
                    //① 保存灰度发布的标记
                    GrayRequestContextHolder.setGrayTag(true);
                    map.put(key, value);
                }
            }
        }
        return map;
    }
}

Code at ①: Obtain the grayscale release tag from the request header and set it to the GrayRequestContextHolder context

The code at ②: Set this request header into a new Request and continue to pass it to the downstream service.

In fact, configuring the RequestInterceptor has already been completed. Regarding the grayscale publishing strategy, only the GrayRule of the gateway needs to be reused.

"Note: You also need to use the @RibbonClients annotation to mark which services called by the article service need to be released in grayscale."

code show as below:

@RibbonClients(value = {
        //指定对comments这个服务开启灰度部署
        @RibbonClient(value = "comments",configuration = GrayRuleConfig.class)
})
public class ArticleApplication {}

How to do grayscale marking for services in Nacos

In fact, it is very simple, divided into two types:

1. Specify in the configuration file, as follows:

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          ## 灰度标记
          grayTag: true

2. Dynamically specify the grayscale mark in Nacos

After the configuration is complete, when the client requests, it only needs to carry the request header grayTag=true to call the grayscale service.

Summarize

The full-link grayscale release scheme in microservices is actually very simple, the most important thing is grayscale marking, the overall process is as follows:

  1. The gateway implements grayscale marking through the global filter, and puts the grayscale mark in the request header and passes it to the downstream service

  2. The gateway obtains grayscale services from the registration center through a custom load balancing strategy and forwards them

  3. When calling openFeign, you need to get the gray mark from the request header and put it in the context

  4. The openFeign call also obtains the grayscale service from the registration center according to the custom load balancing strategy, and makes the call

Guess you like

Origin blog.csdn.net/Park33/article/details/128181926