天天看点

基于nacos netflix loadbalancer灰度发布策略

一,实现原理

利用nacos元数据,给服务发起方和被调用方定义标签。相同标签的优先调用。

二,实现场景

假设 A,B两个服务,各存在4个节点,且版本都是V1

A1(V1)  B1(V1)

A2(V1)  B2(V1)

A3(V1)  B3(V1)

A4(V1)  B4(V1)

更新版本后

A1(V2)  B1(V2)

A2(V1)  B2(V1)

A3(V1)  B3(V1)

A4(V1)  B4(V1)

更新后V2的相互调用,V1的相互调用。

三,关键代码

参考NacosRule类 ,更复杂的情况可以依例扩展

基于nacos netflix loadbalancer灰度发布策略

pom.xml

<dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>      

GrayConstant.java

package com.rutron.canary;

/**
 * @author liwenchao
 */
public class GrayConstant {

    public static final String GRAY_TAG = "version";

}      

CanaryBalancerRule.java

package com.rutron.canary;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.ribbon.ExtendBalancer;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author liwenchao
 */
@Slf4j
public class CanaryBalancerRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Autowired
    private NacosServiceManager nacosServiceManager;

    public CanaryBalancerRule() {

    }

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object key) {
        try {
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            String group = this.nacosDiscoveryProperties.getGroup();
            Map<String, String> ribbonAttributes = this.nacosDiscoveryProperties.getMetadata();
            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
            String serviceName = loadBalancer.getName();
            NamingService namingService = this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties());
            List<Instance> allInstances = namingService.selectInstances(serviceName, group, true);
            List<Instance> grayInstances = new ArrayList<>();
            List<Instance> noneGrayInstances = new ArrayList<>();
            Instance toBeChooseInstance;
            if (CollectionUtils.isEmpty(allInstances)) {
                log.warn("no instance in service {}", serviceName);
                return null;
            } else {
                if (StringUtils.isNotBlank(clusterName)) {
                    for (Instance instance : allInstances) {
                        Map<String, String> metadata = instance.getMetadata();
                        //当前服务的灰度标签和被调用的服务的灰度标签比,相同则灰色
                        if (!ribbonAttributes.containsKey(GrayConstant.GRAY_TAG) || !metadata.containsKey(GrayConstant.GRAY_TAG)) {
                            noneGrayInstances.add(instance);
                        } else if (ribbonAttributes.get(GrayConstant.GRAY_TAG).trim().equalsIgnoreCase(metadata.get(GrayConstant.GRAY_TAG).trim())) {
                            grayInstances.add(instance);
                        } else if (!StringUtils.isBlank(metadata.get(GrayConstant.GRAY_TAG))) {
                            noneGrayInstances.add(instance);
                        }
                    }
                }
                if (grayInstances.size() > 0) {
                    toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(grayInstances);
                    return new NacosServer(toBeChooseInstance);
                }
                if (noneGrayInstances.size() > 0) {
                    toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(noneGrayInstances);
                } else {
                    toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(allInstances);
                }
                return new NacosServer(toBeChooseInstance);
            }
        } catch (Exception e) {
            log.warn("NacosRule error", e);
            return null;
        }
    }
}      

RibbonAutoConfiguration.java

package com.rutron.canary;

import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;

/**
 * @author liwenchao
 */
public class RibbonAutoConfiguration {

    /**
     * 全局配置指定负载均衡策略
     */
    @Bean
    public IRule canaryBalancerRule() {
        return new CanaryBalancerRule();
    }
}      
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.rutron.canary.RibbonAutoConfiguration      
{
    "preserved.register.source": "SPRING_CLOUD",
    "version":"v2"
}