SpringCloud微服务治理——灰度方案之Ribbon解决本地服务污染注册中心问题

Scroll Down

来源:NanCheung`s Blog
链接:https://blog.nancheung.com /archives/512102
商业转载请联系作者获得授权,非商业转载请注明出处。



在项目的实际开发中,开发人员本地开发时,不希望本地将整个调用链启动,不想启动多个服务,渴望接入测试环境,既能依赖测试环境的上游服务,同时并不希望自己会影响到下游服务。

思路

为了解决整个问题,我们发现开发环境的本地微服务(例如IP地址为172.16.0.8)已经注册到测试环境的服务注册发现中心,那么可以在配置中心维护一个黑/白名单的IP地址过滤(支持全局和局部的过滤)的规则,该本地微服务就不会被其他测试环境的微服务所调用。

那么我们怎么实现呢?我们还得研究Ribbon的负载均衡规则体系。

Ribbon的负载均衡规则

我们都知道SpringCloudGateway中默认使用Ribbon来实现负载均衡。
那么我们看一下Ribbon的负载均衡规则体系:
Ribbon的负载均衡规则UML图

可以发现,其中有一个抽象类 PredicateBasedRule ,我们看这个类的注释:

A rule which delegates the server filtering logic to an instance of {@link AbstractServerPredicate}.
After filtering, a server is returned from filtered list in a round robin fashion.

大意为它是实现AbstractServerPredicate类的服务器过滤逻辑。究竟请求哪个服务器,需要经过它的过滤。

看看如下代码:

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
   
    /**
     * 定义了一个抽象函数来获取AbstractServerPredicate
     */
    public abstract AbstractServerPredicate getPredicate();
        
    /**
     * 通过调用获取服务器AbstractServerPredicate.chooseRandomlyAfterFiltering(java.util.List, Object) 
     * 方法来过滤服务。
     * 该方法的性能为O(n),其中n是要被过滤服务器的数量。
     */
    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
	//通过AbstractServerPredicate的chooseRoundRobinAfterFiltering函数来选出具体的服务实例
        //AbstractServerPredicate的子类实现的Predicate逻辑来过滤一部分服务实例
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }
}

所以我们只要继承 PredicateBasedRule 类,并重写 choose 就可以了。

实现方案

一、定义一个配置文件,配置白名单(优先名单)

/**
 * Ribbon 配置
 *
 * @author NanCheung
 */
@Getter
@Setter
@RefreshScope
@ConfigurationProperties("ribbon.rule")
public class RibbonRuleProperties {
	/**
	 * 是否开启,默认:true
	 */
	private boolean enabled = Boolean.TRUE;
	/**
	 * 优先的ip列表,支持通配符,例如:10.20.0.8*、10.20.0.*
	 * <p>为了解决线上网关的服务池被污染的问题,当匹配不到本地ip时,筛选出此ip列表,一般为各服务器ip</p>
	 */
	private List<String> priorIpPattern = new ArrayList<>();
}

二、继承 PredicateBasedRule,实现自定义路由过滤规则

/**
 * ribbon路由过滤规则 优先本地主机服务
 *
 * @author NanCheung
 */
@Slf4j
public class LocalHostAwareRule extends PredicateBasedRule {
    
    @Autowired
    private RibbonRuleProperties ribbonRuleProperties;
    
    @Override
    public AbstractServerPredicate getPredicate() {
        return AbstractServerPredicate.alwaysTrue();
    }
    
    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        
        List<Server> allServers = lb.getAllServers();
        // 过滤服务列表
        List<Server> serverList = this.filterServers(allServers);
        
        return getPredicate().chooseRoundRobinAfterFiltering(serverList, key).orNull();
    }
    
    private List<Server> filterServers(List<Server> serverList) {
        List<String> priorIpPattern = ribbonRuleProperties.getPriorIpPattern();
        // 1. 查找本机ip,此处我用的是HuTool工具类,可以自己替换成相同功用的方法
        HostInfo hostInfo = SystemUtil.getHostInfo();
        String hostIp = hostInfo.getAddress();
        
        // 优先的 ip 规则
        boolean hasPriorIpPattern = !priorIpPattern.isEmpty();
        String[] priorIpPatterns = priorIpPattern.toArray(new String[0]);
        
        List<Server> priorServerList = new ArrayList<>();
        for (Server server : serverList) {
            String host = server.getHost();
            // 2. 优先本地ip的服务 某些服务使用主机名注册为host,所以要同时判断ip和主机名
            // 此处我用的是HuTool工具类,可以自己替换成相同功用的方法
            if (ObjectUtil.equal(hostIp, host) || ObjectUtil.equal(hostInfo.getName(), host)) {
                log.debug("[{}]请求转发至本地路由[{}]", hostInfo, host);
                return Collections.singletonList(server);
            }
            // 3. 优先的 ip 服务
            if (hasPriorIpPattern && PatternMatchUtils.simpleMatch(priorIpPatterns, host)) {
                priorServerList.add(server);
            }
        }
        
        // 4. 如果优先的有数据直接返回
        if (!priorServerList.isEmpty()) {
            return priorServerList;
        }
        
        return Collections.unmodifiableList(serverList);
    }
}

三、在自动配置器中注册自定义路由规则

/**
 * Ribbon路由规则自动配置器
 *
 * @author NanCheung
 */
@Configuration
@RequiredArgsConstructor
//启用自定义的配置文件
@EnableConfigurationProperties(RibbonRuleProperties.class)
//一定要在ribbon默认的配置器之前加载
@AutoConfigureBefore(RibbonClientConfiguration.class)
//当ribbon.rule.enabled为false时不启用自定义过滤规则
@ConditionalOnProperty(value = "ribbon.rule.enabled", matchIfMissing = true)
public class RibbonRuleAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    //这里一定要配置为多例
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public PredicateBasedRule localHostAwareRule() {
        return new LocalHostAwareRule();
    }
}

五、配置服务器白名单(优先)列表

在项目的application配置文件中加入配置:
将除了本地地址外需要优先请求的地址配置进来,注意线上服务器的ip地址是必须配置的,可以使用通配符。

ribbon:
  rule:
    prior-ip-pattern:
      - 10.1.1.*

完成

image.png