spring boot 源码解析37-CounterService详解

前言

从本文开始,我们开始分析org.springframework.boot.actuate.metrics包中的实现.这里面的代码比较多,因此我们采用一步一步的方式来进行分析.本文先分析CounterService相关的类(DropwizardMetricServices这个实现我们后续分析) 类图如下:

《spring boot 源码解析37-CounterService详解》

ps:关于CounterService的自动装配,我们在解析完GaugeService之后再介绍.

解析

CounterService

CounterService–> 1个可以增加,减少,重置的命名的计数器服务.其声明的方法如下:

// 计数器加1
void increment(String metricName);

// 计数器减1
void decrement(String metricName);

// 重置给定的技术器
void reset(String metricName);

MetricWriter

MetricWriter继承自GaugeWriter,CounterWriter接口.没有声明其他的方法,只是1个合并接口.

GaugeWriter

GaugeWriter–>测量值的写出接口.声明了如下方法:

/** * Set the value of a metric. * @param value the value */
void set(Metric<?> value);

Delta

Delta–> 1个可增长的测量值所对应的值对象(通常作为计数器).代码如下:

public class Delta<T extends Number> extends Metric<T> {

    public Delta(String name, T value, Date timestamp) {
        super(name, value, timestamp);
    }

    public Delta(String name, T value) {
        super(name, value);
    }

}

CounterWriter

CounterWriter–>计数器的简单 writer 接口.声明了如下方法:

// 增加当前metric的值(或者减少,如果Delta 是负数的话). Delta 中指定的name指定了要增加的metric的名字
void increment(Delta<?> delta);

// 重置,通常会置为0.该操作是可选的(一些实现可能无法实现该契约--> 什么也没有做)
void reset(String metricName);

SimpleInMemoryRepository

SimpleInMemoryRepository–> 在内存中存储数据的工具类.该类还是一个泛型类,其泛型参数T为存储类型

  1. 字段如下:

    // key-->metricName,value--> T
    private ConcurrentNavigableMap<String, T> values = new ConcurrentSkipListMap<String, T>();
    
    // key--> metricName,value-->Object(用于加锁)
    private final ConcurrentMap<String, Object> locks = new ConcurrentHashMap<String, Object>();
  2. 该类还声明1个接口–> Callback,作用是用来修改值的回调接口,其泛型参数T为值的类型.如下:

    public interface Callback<T> {
    
        // 修改给定的值
        T modify(T current);
    
    }
  3. 声明了如下方法:

    1. getLock–>用于获得给定名称所对应的锁.代码如下:

      private Object getLock(String name) {
          Object lock = this.locks.get(name);
          if (lock == null) {
              Object newLock = new Object();
              lock = this.locks.putIfAbsent(name, newLock);
              if (lock == null) {
                  lock = newLock;
              }
          }
          return lock;
      }

      从缓存中获取,如果获取到则直接返回,如果获取不到则实例化1个Object放入缓存中,然后返回

    2. update–> 对指定的name所对应的值,调用传入的Callback进行修改,然后放入缓存中.代码如下:

      public T update(String name, Callback<T> callback) {
          Object lock = getLock(name);
          synchronized (lock) {
              T current = this.values.get(name);
              T value = callback.modify(current);
              this.values.put(name, value);
              return value;
          }
      }
    3. findAllWithPrefix–> 通过指定的前缀进行查找.代码如下:

      public Iterable<T> findAllWithPrefix(String prefix) {
          if (prefix.endsWith(".*")) {
              prefix = prefix.substring(0, prefix.length() - 1);
          }
          if (!prefix.endsWith(".")) {
              prefix = prefix + ".";
          }
          return new ArrayList<T>(
                  // 不包括头,含为-->(xx]
                  this.values.subMap(prefix, false, prefix + "~", true).values());
      }
      1. 如果prefix是.* 结尾的,则进行截取.如prefix为aa.*,则会截取为aa.
      2. 如果prefix不是.结尾的,则为其加上.
      3. 获得value中key在prefix到prefix~范围中的值,左开右闭

    还有其他的方法,比较简单,这里就不在赘述了

InMemoryMetricRepository

1个在内存中存储metrics的MetricRepository的实现

  1. 字段如下:

    private final SimpleInMemoryRepository<Metric<?>> metrics = new SimpleInMemoryRepository<Metric<?>>();
  2. 其接口的方法实现如下:

    1. increment,代码如下:

      public void increment(Delta<?> delta) {
          final String metricName = delta.getName();
          final int amount = delta.getValue().intValue();
          final Date timestamp = delta.getTimestamp();
          this.metrics.update(metricName, new Callback<Metric<?>>() {
      
              @Override
              public Metric<?> modify(Metric<?> current) {
                  if (current != null) {
                      return new Metric<Long>(metricName,
                              current.increment(amount).getValue(), timestamp);
                  }
                  return new Metric<Long>(metricName, (long) amount, timestamp);
              }
      
          });
      }
      1. 从metrics中获得Delta所对应的Metric
      2. 如果Metric存在的话,则直接实例化1个Metric,名字,时间戳不变,值则在原先的基础上加上传入的Delta的值
      3. 如果不存在,则直接根据传入Delta的名称,时间戳,值实例化1个Metric,加入到metrics的values中
    2. set 实现如下:

      public void set(Metric<?> value) {
          this.metrics.set(value.getName(), value);
      }

      调用SimpleInMemoryRepository的 set方法直接保存至SimpleInMemoryRepository持有的values中.代码如下:

      public void set(String name, T value) {
          this.values.put(name, value);
      }
    3. count–>返回SimpleInMemoryRepository中持有的values的大小.实现如下:

      public long count() {
          return this.metrics.count();
      }

      调用:

      public long count() {
          return this.values.size();
      }
    4. reset–> 从SimpleInMemoryRepository中的values删除.代码如下:

      public void reset(String metricName) {
          this.metrics.remove(metricName);
      }

      调用:

      public void remove(String name) {
          this.values.remove(name);
      }
    5. findOne–>从SimpleInMemoryRepository中的values中查找,代码如下:

      public Metric<?> findOne(String metricName) {
          return this.metrics.findOne(metricName);
      }

      调用:

      public T findOne(String name) {
          return this.values.get(name);
      }
    6. findAll –> 获得SimpleInMemoryRepository中values的所有值.代码如下:

      public Iterable<Metric<?>> findAll() {
          return this.metrics.findAll();
      }
    7. findAllWithPrefix–> 从SimpleInMemoryRepository的values中查找前缀为prefix的Metric.代码如下:

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

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

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

    当满足以下条件时生效:

    • @ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN) –> 在jdk1.8之前的运行环境下运行
    • @ConditionalOnMissingBean(name = “actuatorMetricRepository”)–> beanFactory中不存在id为actuatorMetricRepository的bean

    其中: @ExportMetricReader 如下:

    @Qualifier
    @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
            ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface ExportMetricReader {
    }

    @ActuatorMetricWriter如下:

    @Qualifier
    @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
            ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface ActuatorMetricWriter {
    }

DefaultCounterService

DefaultCounterService –> 在jdk1.8之前默认装配的CounterService的实现

  1. 字段,构造器如下:

    // 此时注入的是InMemoryMetricRepository
    private final MetricWriter writer;
    
    // key-->metricName,value-->加入前缀后的metricName
    private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
    
    public DefaultCounterService(MetricWriter writer) {
        this.writer = writer;
    }
  2. 方法实现如下:

    1. increment,代码如下:

      public void increment(String metricName) {
          this.writer.increment(new Delta<Long>(wrap(metricName), 1L));
      }
      1. 尝试为metricName加上前缀.代码如下:

        private String wrap(String metricName) {
            String cached = this.names.get(metricName);
            if (cached != null) {
                return cached;
            }
            if (metricName.startsWith("counter.") || metricName.startsWith("meter.")) {
                return metricName;
            }
            String name = "counter." + metricName;
            this.names.put(metricName, name);
            return name;
        }
        1. 如果缓存中有的话,则直接返回对应的值
        2. 如果传入的metricName是counter.或者meter.开头的则直接返回
        3. 为metricName 加上counter.的前缀,放入到names缓存中,然后进行返回
      2. 实例化Delta
      3. 加入到InMemoryMetricRepository中
    2. decrement,代码如下:

      public void decrement(String metricName) {
          this.writer.increment(new Delta<Long>(wrap(metricName), -1L));
      }
    3. reset,代码如下:

      public void reset(String metricName) {
          this.writer.reset(wrap(metricName));
      }

Buffer

Buffer,抽象泛型类–>可变的buffer(含有时间戳和所对应的值)的基类.泛型参数为T extends Number–>其持有值的类型.代码如下:

abstract class Buffer<T extends Number> {

    private volatile long timestamp;

    Buffer(long timestamp) {
        this.timestamp = timestamp;
    }

    public long getTimestamp() {
        return this.timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    /** * Returns the buffer value. * @return the value of the buffer */
    public abstract T getValue();

}

CounterBuffer

CounterBuffer–> 继承自Buffer,泛型参数为Long.其类上声明了@UsesJava8注解,表明该类是使用java8特有的api进行实现的,不意味着其严格要求java 8. 代码如下:

@UsesJava8// 表明该类是使用java8特有的api进行实现的,不意味着其严格要求java 8 
public class CounterBuffer extends Buffer<Long> {

    private final LongAdder adder;

    public CounterBuffer(long timestamp) {
        super(timestamp);
        this.adder = new LongAdder();
    }

    public void add(long delta) {
        this.adder.add(delta);
    }

    public void reset() {
        this.adder.reset();
    }

    @Override
    public Long getValue() {
        return this.adder.sum();
    }

}

关于LongAdder,可以参考如下链接:

Java8 更快的原子类:LongAdder(笔记)

Java 8 LongAdders:管理并发计数器的正确方式

Buffers

Buffers–> 管理1个Buffer对象的映射的抽象泛型基类.泛型参数为B extends Buffer

CounterBuffers

CounterBuffers–> 继承自Buffers,泛型参数为CounterBuffer.

  1. createBuffer–> 直接实例化了CounterBuffer.代码如下:

    protected CounterBuffer createBuffer() {
        return new CounterBuffer(0);
    }
  2. 此外,还声明了2个方法.如下:

    1. increment–>对给定名字的CounterBuffer增长给定的幅度. 代码如下:

      public void increment(final String name, final long delta) {
          doWith(name, new Consumer<CounterBuffer>() {
      
              @Override
              public void accept(CounterBuffer buffer) {
                  buffer.setTimestamp(System.currentTimeMillis());
                  buffer.add(delta);
              }
      
          });
      }
      1. 从父类中的buffers获得给定名字所对应的CounterBuffer,如果不存在,则创建1个
      2. 将CounterBuffer中的时间戳设为当前时间,并增加指定的步幅.
    2. reset–> 对给定名字的CounterBuffer进行重置.代码如下:

      public void reset(final String name) {
          doWith(name, new Consumer<CounterBuffer>() {
      
              @Override
              public void accept(CounterBuffer buffer) {
                  buffer.setTimestamp(System.currentTimeMillis());
                  buffer.reset();
              }
      
          });
      }
      1. 从父类中的buffers获得给定名字所对应的CounterBuffer,如果不存在,则创建1个
      2. 将CounterBuffer中的时间戳设为当前时间,将其值设置为0
  3. 自动装配:

    在FastMetricServicesConfiguration中进行了配置.代码如下:

    @Configuration
    @ConditionalOnJava(JavaVersion.EIGHT)
    @ConditionalOnMissingBean(GaugeService.class)
    static class FastMetricServicesConfiguration {
    
        @Bean
        @ConditionalOnMissingBean
        public CounterBuffers counterBuffers() {
            return new CounterBuffers();
        }
    }   

    当满足如下条件时该配置生效:

    1. @ConditionalOnJava(JavaVersion.EIGHT)–> 在jdk1.8的环境中运行
    2. @ConditionalOnMissingBean(GaugeService.class)–>BeanFactory中不存在GaugeService类型的bean时生效
    3. @ConditionalOnMissingBean–>BeanFactory中不存在CounterBuffers类型的bean时生效

BufferCounterService

BufferCounterService–>实现了CounterService接口.

  1. 字段,构造器如下:

    // key --> 原始的名字,value --> buffer中的名字
    private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
    
    private final CounterBuffers buffers;
    
    public BufferCounterService(CounterBuffers buffers) {
        this.buffers = buffers;
    }
  2. 方法实现如下:

    1. increment,代码如下:

      public void increment(String metricName) {
          this.buffers.increment(wrap(metricName), 1L);
      }
      1. 尝试为metricName加上前缀.代码如下:

        private String wrap(String metricName) {
            String cached = this.names.get(metricName);
            if (cached != null) {
                return cached;
            }
            if (metricName.startsWith("counter.") || metricName.startsWith("meter.")) {
                return metricName;
            }
            String name = "counter." + metricName;
            this.names.put(metricName, name);
            return name;
        }
        1. 如果缓存中有的话,则直接返回对应的值
        2. 如果传入的metricName是counter.或者meter.开头的则直接返回
        3. 为metricName 加上counter.的前缀,放入到names缓存中,然后进行返回
      2. 加入到CounterBuffers中
    2. decrement,代码如下:

      public void decrement(String metricName) {
          this.buffers.increment(wrap(metricName), -1L);
      }
    3. reset,代码如下:

      public void reset(String metricName) {
          this.buffers.reset(wrap(metricName));
      }

CounterService使用案例

我们可以使用CounterService来完成对接口请求次数的统计.

  1. 新建CounterController,代码如下:

    package com.example.demo;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.actuate.metrics.CounterService;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    public class CounterController {
    
        @Autowired
        private CounterService counterService;
    
        @RequestMapping("/test-counter")
        public String testCounter() {
    
            counterService.increment("test-counter.count");
    
            return "操作成功";
        }
    }
  2. 我们访问如下链接后 http://127.0.0.1:8080/test-counter 后,访问 http://127.0.0.1:8080/metrics,即可发现counter.test-counter.count对应的统计次数为1.如下:

    counter.test-counter.count: 1
    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_26000415/article/details/79154161
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞