zipkin+sleuth基于apollo配置中心动态刷新采样率(十四)

前言

前文我们介绍过,自定义采样率,但是为了满足项目的灵活性,我们最好可以提供在程序运行过程中,可以动态的修改 采样率等信息,如此才能应对项目运行当中碰到的各种问题。

项目运行过程中,我们可能会遇到下面这些问题。

1.某个接口的采样率现在比较高? 我想调低一点怎么办?

2.这个接口太频繁,并且没有追踪的意义,我想禁用掉怎么办?

3.全局采样率的动态调整

默认zipkin是不支持上面的这些功能的,但是我们可以基于分布式配置中心进行扩展,我这边使用的是Apollo配置中心。本次样例中,也是以这个为基础的。

PS : 本文讲的这个动态刷新的做法,都是会放到公共的starter包里面去,供其他服务调用的。

增加配置

添加pom文件

<dependency>
    <groupId>com.ctrip.framework.apollo</groupId>
    <artifactId>apollo-client</artifactId>
    <version>1.0.8</version>
    <scope>provided</scope>
  </dependency>

添加配置

@Bean
    @ConditionalOnClass(value = {ConfigService.class})
    ApolloRefreshListener apolloRefreshListener() {

        return new ApolloRefreshListener();
    }

监听类

package zipkin.core.listener;


@Slf4j
public class ApolloRefreshListener implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private ConfigUtil m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);

    @ApolloConfigChangeListener
    public void onChange(ConfigChangeEvent changeEvent) {
        try {
            Config config = ConfigService.getAppConfig();

            if (m_configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
                Float percentage = null; // 全局采样率
                Map<Integer, UriSampleRefreshProperties> uriSamplePropertiesMap = new HashMap<>();
                boolean isSampleChange = false;
                for (String key : changeEvent.changedKeys()) { // 循环变更的属性key
                    ConfigChange configChange = changeEvent.getChange(key); // 获取当前属性的前后变化的对象,里面有最新值和旧值
                    if (ZipkinConfigKeyEnum.PERCENTAGE.getKey().equals(key)) { // 全局采样率变更
                        // 全局采样率, 需要动态刷新全局采样率
                        isSampleChange = true;
                        if(StringUtils.isBlank(configChange.getNewValue())){ // 当最新的值被置空时,全局采样率默认为0
                            percentage = 0.0f;
                            continue;
                        }
                        percentage = Float.parseFloat(configChange.getNewValue());
                    } else if (ZipkinConfigKeyEnum.SKIP_PATTERN.getKey().equals(key)) {
                        // spring.sleuth.web.skipPattern 发生变更
                        // 禁用的URI,动态刷新
                        TraceLocalFilter traceLocalFilter = (TraceLocalFilter) applicationContext.getBean("traceFilter");
                        // 获取traceFilter, 更新里面的skipPattern对象
                        traceLocalFilter.setSkipPattern(PatternUtil.defaultSkipPattern(configChange.getNewValue()));
                    } else if (key.startsWith(ZipkinConfigKeyEnum.URI_SAMPLE.getKey())) {
                        // 自定义的采样率更新,当添加了接口的自定义采样率之后,需要更新
                        isSampleChange = true;
                        Integer index = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.lastIndexOf("]")));
                        if (!uriSamplePropertiesMap.containsKey(index)) {
                            uriSamplePropertiesMap.put(index, new UriSampleRefreshProperties());
                        }
                        if (key.endsWith(ZipkinConfigKeyEnum.URI_LIST.getKey())) {
                            // uri
                            uriSamplePropertiesMap.get(index).setUriList(configChange.getNewValue());
                        } else if (key.endsWith(ZipkinConfigKeyEnum.URI_PERCENTAGE.getKey()) && null!=configChange.getNewValue()) {
                            // uri采样率
                            uriSamplePropertiesMap.get(index).setUriPercentage(Float.parseFloat(configChange.getNewValue()));
                        }
                    }
                }
                // 采样率是否发生过变化,
                if (isSampleChange) {
                    // 获取采样率对象
                    PercentageLocalSampler percentageLocalSampler = (PercentageLocalSampler) applicationContext.
                      getBean("percentageLocalSampler");
                    //uriSamplePropertiesMap针对的是接口级别的采样率
                    for (Map.Entry<Integer, UriSampleRefreshProperties> entry : uriSamplePropertiesMap.entrySet()) {
                        UriSampleRefreshProperties uriSampleProperties = entry.getValue();
                        //当采样率修改了,uri没有改,则需要从Apollo中读取uri
                        if (StringUtils.isBlank(uriSampleProperties.getUriList()) && null != uriSampleProperties.getUriPercentage()) {
                            String uriListKey = ZipkinConfigKeyEnum.URI_SAMPLE.getKey() + "[" + entry.getKey() + "]." +
                                    ZipkinConfigKeyEnum.URI_LIST.getKey();
                            String value = config.getProperty(uriListKey, "");
                            uriSampleProperties.setUriList(value);
                        }
                        //当uri修改了,uri对应的采样率没有改,则需要从Apollo中读取对应的采样率配置
                        if (StringUtils.isNotBlank(uriSampleProperties.getUriList()) && null == uriSampleProperties.getUriPercentage()) {
                            String uriPercentageKey = ZipkinConfigKeyEnum.URI_SAMPLE.getKey() + "[" + entry.getKey() + "]." +
                                    ZipkinConfigKeyEnum.URI_PERCENTAGE.getKey();
                            float value = config.getFloatProperty(uriPercentageKey, 0.0f);
                            uriSampleProperties.setUriPercentage(value);
                        }
                    }
                    // 重置采样率
                    percentageLocalSampler.initPercentage(percentage,uriSamplePropertiesMap);
                }
            }
        } catch (Exception e) {
            log.error("", e);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

PercentageLocalSampler 这个类可以看我之前写过的那边,自定义采样率,此处仅扩展了一个方法。initPercentage

public void initPercentage(Float percentage, Map<Integer, UriSampleRefreshProperties> uriSamplePropertiesMap) {
        int size = 0;
        // 判断原来是否设置过接口级别的采样率
        if (!uriSamplePropertiesMap.isEmpty() && CollectionUtils.isEmpty(configuration.getUriSample())) {
            configuration.setUriSample(new ArrayList<>(0));
        }else{
            // 获取原来的接口list size
            size = configuration.getUriSample().size();
        }
        // 更新configuration对象的信息
        for (Map.Entry<Integer, UriSampleRefreshProperties> entry : uriSamplePropertiesMap.entrySet()) {
            Integer index = entry.getKey();
            UriSampleProperties uriSampleProperties = null;
            if(index<=(size-1)){
                uriSampleProperties = configuration.getUriSample().get(index);
            }else{
                uriSampleProperties = new UriSampleProperties();
                configuration.getUriSample().add(uriSampleProperties);
            }
            uriSampleProperties.setUriList(entry.getValue().getUriList());
            uriSampleProperties.setUriPercentage(entry.getValue().getUriPercentage());
        }
        if(null!=percentage){
            // 当percentage不为空时,表示全局采样率被修改
            configuration.setPercentage(percentage);
        }
        concurrentUriMap.clear();
        // 刷新采样率
        buildUriSampleData();
    }

至此,当Apollo配置中心的配置有变更的时候,就会被监听类监听到,然后调用刷新方法,动态进行刷新。
zipkin+sleuth基于apollo配置中心动态刷新采样率(十四)