spring boot 源码解析44-PrefixMetricReader,PrefixMetricWriter,MultiMetricRepository

前言

本文我们来分析PrefixMetricReader,PrefixMetricWriter,MultiMetricRepository的实现.类图如下:

类图

PrefixMetricReader

PrefixMetricReader–>对metrics进行分组–> 相同前缀的为1组.代码如下:

public interface PrefixMetricReader {

    // 获得Metric的名字是给定的prefix 开头的
    Iterable<Metric<?>> findAll(String prefix);

}

BufferMetricReader

BufferMetricReader–> 使用CounterBuffers,GaugeBuffers 来实现MetricReader.

  1. 字段,构造器如下:

    private static final Predicate<String> ALL = Pattern.compile(".*").asPredicate();
    
        private final CounterBuffers counterBuffers;
    
        private final GaugeBuffers gaugeBuffers;
    
        public BufferMetricReader(CounterBuffers counterBuffers, GaugeBuffers gaugeBuffers) {
            this.counterBuffers = counterBuffers;
            this.gaugeBuffers = gaugeBuffers;
    }
  2. 方法实现如下:

    1. findOne,代码如下:

      public Metric<?> findOne(final String name) {
          // 1. 从CounterBuffers中查找,如果不存在,则在gaugeBuffers中查找
          Buffer<?> buffer = this.counterBuffers.find(name);
          if (buffer == null) {
              buffer = this.gaugeBuffers.find(name);
          }
          // 2.如果找到了,则将其转换为Metric
          return (buffer == null ? null : asMetric(name, buffer));
      }
      1. 从CounterBuffers中查找,如果不存在,则在gaugeBuffers中查找
      2. 如果找到了,则将其转换为Metric.代码如下:

        private <T extends Number> Metric<T> asMetric(final String name, Buffer<T> buffer) {
            return new Metric<T>(name, buffer.getValue(), new Date(buffer.getTimestamp()));
        }
    2. findAll,findAll(String prefix) 代码如下:

      public Iterable<Metric<?>> findAll() {
          return findAll(BufferMetricReader.ALL);
      }
      public Iterable<Metric<?>> findAll(String prefix) {
          return findAll(Pattern.compile(prefix + ".*").asPredicate());
      }

      它们的区别就是传入的正则不同而已,最终都会调用如下代码:

      private Iterable<Metric<?>> findAll(Predicate<String> predicate) {
          final List<Metric<?>> metrics = new ArrayList<Metric<?>>();
          collectMetrics(this.gaugeBuffers, predicate, metrics);
          collectMetrics(this.counterBuffers, predicate, metrics);
          return metrics;
      }
      1. 从GaugeBuffers中查找名字会指定前缀的Buffer,最终将其转换为Metric加入到结果集中
      2. 从CounterBuffers中查找名字会指定前缀的Buffer,最终将其转换为Metric加入到结果集中

      collectMetrics,代码如下:

      private <T extends Number, B extends Buffer<T>> void collectMetrics(
              Buffers<B> buffers, Predicate<String> predicate,
              final List<Metric<?>> metrics) {
          buffers.forEach(predicate, new BiConsumer<String, B>() {
      
              @Override
              public void accept(String name, B value) {
                  // 如果符合给定的前缀则进行添加
                  metrics.add(asMetric(name, value));
              }
      
          });
      }
    3. count–>返回CounterBuffers和GaugeBuffers的总数.代码如下:

      public long count() {
          return this.counterBuffers.count() + this.gaugeBuffers.count();
      }
  3. 自动装配:

    声明在FastMetricServicesConfiguration中,代码如下:

    @Bean
    @ExportMetricReader
    @ConditionalOnMissingBean
    public BufferMetricReader actuatorMetricReader(CounterBuffers counters,
            GaugeBuffers gauges) {
        return new BufferMetricReader(counters, gauges);
    }

    满足以下条件时生效:

    1. 在JDK1.8及以上的环境中运行
    2. BeanFactory中不存在BufferMetricReader类型的bean时生效

PrefixMetricWriter

PrefixMetricWriter–> 允许有效的存储以 前缀分组后的metrics.代码如下:

public interface PrefixMetricWriter {

    // 保存一些metric的值并且与组名进行管理
    void set(String group, Collection<Metric<?>> values);

    // 增加给定的测量值(或者减少,如果给定的delta为负数的话).增长的metric 名字为 group.delta.name
    void increment(String group, Delta<?> delta);

    // 重置指定组所对应的所有metrics的值.实现类可能会选择直接丢弃旧的值
    void reset(String group);

}

MultiMetricRepository

MultiMetricRepository–>1个Metric的仓库–>允许有效的存储和检索拥有共同名称前缀的Metric.代码如下:

public interface MultiMetricRepository extends PrefixMetricReader, PrefixMetricWriter {

    // 返回该repository中所有的组名
    Iterable<String> groups();

    // 返回该repository 中的组的数量
    long countGroups();

}

InMemoryMultiMetricRepository

InMemoryMultiMetricRepository–> 通过InMemoryMetricRepository 来实现MultiMetricRepository.

  1. 字段,构造器如下:

    private final InMemoryMetricRepository repository;
    
    // 保存着 group 名
    private final Collection<String> groups = new HashSet<String>();
    
    
    public InMemoryMultiMetricRepository() {
        this(new InMemoryMetricRepository());
    }
    
    
    public InMemoryMultiMetricRepository(InMemoryMetricRepository repository) {
        Assert.notNull(repository, "Repository must not be null");
        this.repository = repository;
    }
  2. 方法实现如下:

    1. set,代码如下:

      public void set(String group, Collection<Metric<?>> values) {
          // 1. 对给定的组名加上.(如果没有的话)
          String prefix = group;
          if (!prefix.endsWith(".")) {
              prefix = prefix + ".";
          }
          // 2. 遍历values,如果metric的名称不是以指定前缀开头的,则实例化1个Metric,其值为前缀+原metric名称,其它值都是复制的,
          // 然后加入到repository中
          for (Metric<?> metric : values) {
              if (!metric.getName().startsWith(prefix)) {
                  metric = new Metric<Number>(prefix + metric.getName(), metric.getValue(),
                          metric.getTimestamp());
              }
              this.repository.set(metric);
          }
          // 3. 将组名加入到groups 中
          this.groups.add(group);
      }
      1. 对给定的组名加上.(如果没有的话)
      2. 遍历values,如果metric的名称不是以指定前缀开头的,则实例化1个Metric,其值为前缀+原metric名称,其它值都是复制的,然后加入到repository中
      3. 将组名加入到groups 中
    2. increment,代码如下:

      public void increment(String group, Delta<?> delta) {
          // 1. 对给定的组名加上.(如果没有的话)
          String prefix = group;
          if (!prefix.endsWith(".")) {
              prefix = prefix + ".";
          }
          // 2. 如果指定的Delta的名字不是以指定的group开头的,则实例化1个Delta,名字为group+原Delta的名字
          // ,其余直接复制
          if (!delta.getName().startsWith(prefix)) {
              delta = new Delta<Number>(prefix + delta.getName(), delta.getValue(),
                      delta.getTimestamp());
          }
          // 3. 如果InMemoryMetricRepository中存在指定名字的Delta,则直接增长给定的幅度
          // 否则,直接加入到InMemoryMetricRepository中
          this.repository.increment(delta);
          // 4. 将group加入到groups中
          this.groups.add(group);
      }
      1. 对给定的组名加上.(如果没有的话)
      2. 如果指定的Delta的名字不是以指定的group开头的,则实例化1个Delta,名字为group+原Delta的名字,其余直接复制
      3. 如果InMemoryMetricRepository中存在指定名字的Delta,则直接增长给定的幅度.否则,直接加入到InMemoryMetricRepository中
      4. 将group加入到groups中
    3. groups,如下:

      public Iterable<String> groups() {
          return Collections.unmodifiableCollection(this.groups);
      }
    4. countGroups,如下:

      public long countGroups() {
          return this.groups.size();
      }
    5. reset,代码如下:

      public void reset(String group) {
          // 1. 获取指定前缀的Metric,依次进行删除
          for (Metric<?> metric : findAll(group)) {
              this.repository.reset(metric.getName());
          }
          // 2. 从groups中删除
          this.groups.remove(group);
      }
    6. findAll,代码如下:

      public Iterable<Metric<?>> findAll(String metricNamePrefix) {
          return this.repository.findAllWithPrefix(metricNamePrefix);
      }
  3. 自动装配:

    在LegacyMetricRepositoryConfiguration中进行了装配,代码如下:

    @Configuration
    @ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
    @ConditionalOnMissingBean(name = "actuatorMetricRepository")
    static class LegacyMetricRepositoryConfiguration {
    
        ...
    
        @Bean
        @ExportMetricReader
        @ActuatorMetricWriter
        public InMemoryMultiMetricRepository actuatorMultiMetricRepository(
                InMemoryMetricRepository actuatorMetricRepository) {
            return new InMemoryMultiMetricRepository(actuatorMetricRepository);
        }
    
    }

    关于这个我们在spring boot 源码解析40-CounterService,GaugeService默认自动装配解析中有介绍,这里就不赘述了

RedisMultiMetricRepository

RedisMultiMetricRepository–>使用redis来实现.Metric的值被存储到zset中,时间戳存储到string 中,其key都是前缀(默认是spring.groups.)+组名.在zset中对应的集合名默认是由keys.+前缀组成的

注意,此类是不会进行自动装配的

  1. 字段如下:

    private static final String DEFAULT_METRICS_PREFIX = "spring.groups.";
    
    // 前缀默认是spring.groups.
    private final String prefix;
    
    // zSet 对应的集合名,默认为keys.spring.groups
    private final String keys;
    
    private final BoundZSetOperations<String, String> zSetOperations;
    
    private final RedisOperations<String, String> redisOperations;
  2. 构造器如下:

    public RedisMultiMetricRepository(RedisConnectionFactory redisConnectionFactory) {
        this(redisConnectionFactory, DEFAULT_METRICS_PREFIX);
    }
    
    public RedisMultiMetricRepository(RedisConnectionFactory redisConnectionFactory,
                String prefix) {
            Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null");
            // 1. 实例化RedisOperations
            this.redisOperations = RedisUtils.stringTemplate(redisConnectionFactory);
            // 2.如果指定的前置不是.结尾的,则为其加上. 并将其赋值给prefix
            if (!prefix.endsWith(".")) {
                prefix = prefix + ".";
            }
            this.prefix = prefix;
            // 3. 在指定的prefix加上keys.前缀生成keys
            this.keys = "keys." + this.prefix.substring(0, prefix.length() - 1);
            // 4. 获得以keys为zset集合名称对应的BoundZSetOperations
            this.zSetOperations = this.redisOperations.boundZSetOps(this.keys);
        }
  3. 实现方法如下:

    1. set,代码如下:

      public void set(String group, Collection<Metric<?>> values) {
          // 1. 生成groupkey-->对group 加上前缀
          String groupKey = keyFor(group);
          // 2.存入groupKey对应的zset中,key-->groupKey,值为-->0.0D
          trackMembership(groupKey);
          // 3. 获得groupKey对应的BoundZSetOperations 对象
          BoundZSetOperations<String, String> zSetOperations = this.redisOperations
                  .boundZSetOps(groupKey);
          // 3. 依次遍历Metrics
          for (Metric<?> metric : values) {
              // 3.1 将Metric对应的时间戳变为字符串格式
              String raw = serialize(metric);
              // 3.2 将metric的名字加上前缀以生成key
              String key = keyFor(metric.getName());
              // 3.3 添加到groupKey 对应的zset中,key-->metric的名字加上前缀以生成key,value-->测量值
              zSetOperations.add(key, metric.getValue().doubleValue());
              // 3.4 添加到String结构中,key--> metric的名字加上前缀以生成key,value-->Metric对应的时间戳变为字符串格式
              this.redisOperations.opsForValue().set(key, raw);
          }
      }
      1. 生成groupkey–>对group 加上前缀.代码如下:

        private String keyFor(String name) {
            return this.prefix + name;
        }
      2. 存入groupKey对应的zset中,key–>groupKey,值为–>0.0D.代码如下:

        private void trackMembership(String redisKey) {
            this.zSetOperations.incrementScore(redisKey, 0.0D);
        }
      3. 获得groupKey对应的BoundZSetOperations 对象
      4. 依次遍历Metrics

        1. 将Metric对应的时间戳变为字符串格式.代码如下:

          private String serialize(Metric<?> entity) {
              return String.valueOf(entity.getTimestamp().getTime());
          }
        2. 将metric的名字加上前缀以生成key
        3. 添加到groupKey 对应的zset中,key–>metric的名字加上前缀以生成key,value–>测量值
        4. 添加到String结构中,key–> metric的名字加上前缀以生成key,value–>Metric对应的时间戳变为字符串格式
    2. increment,代码如下:

      public void increment(String group, Delta<?> delta) {
          // 1. 生成groupkey-->对group 加上前缀
          String groupKey = keyFor(group);
          // 2.存入groupKey对应的zset中,key-->groupKey,值为-->0.0D
          trackMembership(groupKey);
          // 3. 获得groupKey对应的BoundZSetOperations 对象
          BoundZSetOperations<String, String> zSetOperations = this.redisOperations
                  .boundZSetOps(groupKey);
          // 4. 将metric的名字加上前缀以生成key
          String key = keyFor(delta.getName());
          // 5. 向groupKey 对应的zset中,key为metric的名字加上前缀的值进行增加,幅度为Delta的值
          double value = zSetOperations.incrementScore(key, delta.getValue().doubleValue());
          // 6. 将Metric对应的时间戳变为字符串格式
          String raw = serialize(
                  new Metric<Double>(delta.getName(), value, delta.getTimestamp()));
          // 7. 添加到String结构中,key--> metric的名字加上前缀以生成key,value-->Metric对应的时间戳变为字符串格式
          this.redisOperations.opsForValue().set(key, raw);
      }
    3. reset,代码如下:

      public void reset(String group) {
          // 1. 生成groupkey-->对group 加上前缀
          String groupKey = keyFor(group);
          // 2. 如果redis中有对应的key的话
          if (this.redisOperations.hasKey(groupKey)) {
              // 3. 获得zset中集合名为groupKey的操作对象-->BoundZSetOperations
              BoundZSetOperations<String, String> zSetOperations = this.redisOperations
                      .boundZSetOps(groupKey);
              // 4. 获得zset中的所有值,依次从redis中删除
              Set<String> keys = zSetOperations.range(0, -1);
              for (String key : keys) {
                  this.redisOperations.delete(key);
              }
              // 5. 从redis中删除groupKey
              this.redisOperations.delete(groupKey);
          }
          // 6. 删除groupKey所对应的zset集合
          this.zSetOperations.remove(groupKey);
      }
    4. groups,代码如下:

      public Iterable<String> groups() {
          // 1. 获得keys.spring.groups 对应的zset中所有的数据
          Set<String> range = this.zSetOperations.range(0, -1);
          Collection<String> result = new ArrayList<String>();
          // 2. 依次遍历之,去除前缀后加入到result中
          for (String key : range) {
              result.add(key.substring(this.prefix.length()));
          }
          return result;
      }
    5. findAll,代码如下:

      public Iterable<Metric<?>> findAll(String group) {
      
          // 1. 对group加入前缀后获得对应的zset的操作对象-->BoundZSetOperations
          BoundZSetOperations<String, String> zSetOperations = this.redisOperations
                  .boundZSetOps(keyFor(group));
      
          // 2. 获得其zset 存储的所有key,遍历处理之
          Set<String> keys = zSetOperations.range(0, -1);
          Iterator<String> keysIt = keys.iterator();
      
          List<Metric<?>> result = new ArrayList<Metric<?>>(keys.size());
          // 3. 获得redis中key为zset 存储的所有key 所对应的值,遍历之
          List<String> values = this.redisOperations.opsForValue().multiGet(keys);
          for (String v : values) {
              String key = keysIt.next();
              // 4. 序列化后加入到result中
              result.add(deserialize(group, key, v, zSetOperations.score(key)));
          }
          return result;
      }
    6. countGroups,代码如下:

      public long countGroups() {
          return this.zSetOperations.size();
      }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值