spring boot 源码解析41-CounterWriter,GaugeWriter解析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

前言

本文我们来分析一下CounterWriter,GaugeWriter接口的实现.前面的几篇文章,我们介绍了spring boot中的CounterService,GaugeService.数据有了,总得有输出的地方.那么本文就为你介绍一番.关于这部分的类图如下:

20191017100447\_1.png

这里面有些实现是不会进行详解的,有很多实现都是依赖第三方实现的.

解析

CounterWriter

CounterWriter–> 计数器的简单writer接口.代码如下:

    public interface CounterWriter {

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

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

    }

GaugeWriter

GaugeWriter–>代码如下:

    public interface GaugeWriter {

        void set(Metric<?> value);

    }

OpenTsdbGaugeWriter

这是使用OpenTSDB来实现的,OpenTSDB –> 基于Hbase的分布式的,可伸缩的时间序列数据库。主要用途,就是做监控系统;譬如收集大规模集群(包括网络设备、操作系统、应用程序)的监控数据并进行存储,查询。关于这方面的内容可以参考如下链接:

Opentsdb简介(一)

OpenTSDB监控系统的研究和介绍

MetricWriter

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

    public interface MetricWriter extends GaugeWriter, CounterWriter {

    }

StatsdMetricWriter

StatsdMetricWriter–> 依靠StatsD来实现MetricWriter. .Statsd 有计数器和gauge(测量)的概念,但是gauges只支持Long类型的数据.所以其值将被截断,接近零。Metrics 中含有timer. 名称的(不是gauge. 或者counter.)的,会被视作执行次数(在statsd的概念中)任何递增的被视作计数器,任何拥有快照值的被视作统计器.

关于StatsD的可以参考如下链接:

StatsD Metric

使用 StatsD + Grafana + InfluxDB 搭建 Node.js 监控系统

MessageChannelMetricWriter

MessageChannelMetricWriter–> 实现了MetricWriter接口,1个发布metric 更新消息通过MessageChannel的MetricWriter.发送的消息将Delta和Metric 作为消息体,并且带上了消息头–>metricName,说明是那个metric 需要处理.需要spring-messaging的依赖.

  1. 该类的字段,构造器如下:

        // 此时注入的是id为metricsChannel的MessageChannel
        private final MessageChannel channel;
    
        public MessageChannelMetricWriter(MessageChannel channel) {
            this.channel = channel;
        }
  2. 该类方法实现如下:

        public void increment(Delta delta) {
            this.channel.send(MetricMessage.forIncrement(delta));
        }
    
        public void set(Metric value) {
            this.channel.send(MetricMessage.forSet(value));
        }
    
        public void reset(String metricName) {
            this.channel.send(MetricMessage.forReset(metricName));
        }

    可以发现都是向d为metricsChannel的MessageChannel中发送消息.消息是由MetricMessage来创建的.

    MetricMessage的字段,构造器如下:

        // 构造Message中添加的头部
        private static final String METRIC_NAME = "metricName";
    
        // 构造Reset类型的消息时的消息体
        private static final String DELETE = "delete";
    
        private final Message message;
    
        MetricMessage(Message message) {
            this.message = message;
        }
    

    方法如下:

    
        public static Message forIncrement(Delta delta) {
            return forPayload(delta.getName(), delta);
        }
    
        public static Message forSet(Metric value) {
            return forPayload(value.getName(), value);
        }
    
        public static Message forReset(String metricName) {
            return forPayload(metricName, DELETE);
        }

    发现最终都是调用forPayload–> 根据消息的类型,metricName 构建出对应的Message.代码如下:

        private static Message forPayload(String metricName, Object payload) {
            MessageBuilder builder = MessageBuilder.withPayload(payload);
            builder.setHeader(METRIC_NAME, metricName);
            return builder.build();
        }
    
    
  3. 自动装配:

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

        @Configuration
        @ConditionalOnClass(MessageChannel.class)
        @ConditionalOnBean(name = "metricsChannel")
        @AutoConfigureBefore(MetricRepositoryAutoConfiguration.class)
        public class MetricsChannelAutoConfiguration {
    
            @Bean
            @ExportMetricWriter
            @ConditionalOnMissingBean
            public MessageChannelMetricWriter messageChannelMetricWriter(
                    @Qualifier("metricsChannel") MessageChannel channel) {
                return new MessageChannelMetricWriter(channel);
            }
        }

    当满足以下条件时会进行装配:

    1. @ConditionalOnClass(MessageChannel.class)–> 在当前的类路径下存在MessageChannel.class时生效
    2. @ConditionalOnBean(name = “metricsChannel”) –> BeanFactory中存在id为metricsChannel的bean时生效
    3. @ConditionalOnMissingBean –> BeanFactory中不存在MessageChannelMetricWriter类型的bean时生效
  4. 使用案例:

    关于如何使用,我们后续有文章会进行分析.

  5. JmxMetricWriter

    关于这个后续有文章会进行分析

    CompositeMetricWriter

    CompositeMetricWriter–> 组合其他MetricWriter的实现,在方法的实现过程中会依次调用其持有的MetricWriter进行处理.组合模式.代码如下:

        public class CompositeMetricWriter implements MetricWriter, Iterable<MetricWriter> {
    
            private final List<MetricWriter> writers = new ArrayList<MetricWriter>();
    
            public CompositeMetricWriter(MetricWriter... writers) {
                Collections.addAll(this.writers, writers);
            }
    
            public CompositeMetricWriter(List<MetricWriter> writers) {
                this.writers.addAll(writers);
            }
    
            @Override
            public Iterator<MetricWriter> iterator() {
                return this.writers.iterator();
            }
    
            @Override
            public void increment(Delta<?> delta) {
                for (MetricWriter writer : this.writers) {
                    writer.increment(delta);
                }
            }
    
            @Override
            public void set(Metric<?> value) {
                for (MetricWriter writer : this.writers) {
                    writer.set(value);
                }
            }
    
            @Override
            public void reset(String metricName) {
                for (MetricWriter writer : this.writers) {
                    writer.reset(metricName);
                }
            }
    
        }

    MetricRepository,InMemoryMetricRepository

    我们在spring boot 源码解析37-CounterService详解中详细记录,这里就不在赘述了.

    RedisMetricRepository

    RedisMetricRepository–> 1个使用redis 来实现MetricRepository. Metric的值被存储到zset中,时间戳存储到string中,其key是由前缀(默认是spring.metrics.)加metric名组成的. 如果你有许多的RedisMetricRepository使用同一个Redis的实例,可能需要改变其前缀使其唯一(除非你希望它们对同一前缀的metrics)

    1. 字段如下:

          // redis中存储key的默认前缀
          private static final String DEFAULT_METRICS_PREFIX = "spring.metrics.";
      
          private static final String DEFAULT_KEY = "keys.spring.metrics";
      
          // 前缀
          private String prefix = DEFAULT_METRICS_PREFIX;
      
          // redis中的zset的名称
          private String key = DEFAULT_KEY;
      
          private BoundZSetOperations zSetOperations;
      
          private final RedisOperations redisOperations;
      
    2. 构造器如下:

          public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory) {
              this(redisConnectionFactory, null);
          }
      
          public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
                  String prefix) {
              this(redisConnectionFactory, prefix, null);
          }
      
          public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
                  String prefix, String key) {
              if (prefix == null) {
                  prefix = DEFAULT_METRICS_PREFIX;
                  if (key == null) {
                      key = DEFAULT_KEY;
                  }
              }
              else if (key == null) {
                  key = "keys." + prefix;
              }
              Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null");
              this.redisOperations = RedisUtils.stringTemplate(redisConnectionFactory);
              if (!prefix.endsWith(".")) {
                  prefix = prefix + ".";
              }
              this.prefix = prefix;
              if (key.endsWith(".")) {
                  key = key.substring(0, key.length() - 1);
              }
              this.key = key;
              this.zSetOperations = this.redisOperations.boundZSetOps(this.key);
          }

      最终都会调用其RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,String prefix, String key)构造器,处理逻辑如下:

      1. 如果prefix等于null,则使用默认的前缀–>spring.metrics.

        1. 如果key等于null,则使用默认的key–>keys.spring.metrics
      2. 如果前缀指定了,key没有指定,则key等于在指定的prefix前加上keys.
      3. 实例化RedisOperations
      4. 如果prefix不是以.结尾的,则为其加上. 并赋值给prefix.
      5. 如果key是以.结尾的,则进行截取,并设置给key
      6. 根据给的key 创建BoundZSetOperations,默认为keys.spring.metrics
    3. 其方法实现如下:

      1. set,代码如下:

            public void set(Metric value) {
                // 1. 将Metric的名字加上前缀
                String name = value.getName();
                String key = keyFor(name);
                // 2. 加入到zset中,value 为key,评分为value值
                trackMembership(key);
                this.zSetOperations.add(key, value.getValue().doubleValue());
                // 3. 将Metric的时间戳存在redis中,key--> Metric的名字加上前缀,value-->时间戳的字符串格式
                String raw = serialize(value);
                this.redisOperations.opsForValue().set(key, raw);
            }
        1. 将Metric的名字加上前缀.代码如下:

              private String keyFor(String name) {
                  return this.prefix + name;
              }
        2. 首先在zset中添加value为metric的名字加上前缀,评分为0,然后将其评分设置为value值

              private void trackMembership(String redisKey) {
                  this.zSetOperations.incrementScore(redisKey, 0.0D);
              }
        3. 将Metric的时间戳存在redis中,key–> Metric的名字加上前缀,value–>时间戳的字符串格式.代码如下:

              private String serialize(Metric entity) {
                  return String.valueOf(entity.getTimestamp().getTime());
              }
      2. reset–>从zset和String结构中进行删除.代码如下:

            public void reset(String metricName) {
                String key = keyFor(metricName);
                if (this.zSetOperations.remove(key) == 1) {
                    this.redisOperations.delete(key);
                }
            }
        1. 对给定的metricName加上前缀
        2. 从zset和String结构中进行删除
      3. increment,代码如下:

            public void increment(Delta delta) {
                String name = delta.getName();
                String key = keyFor(name);
                trackMembership(key);
                double value = this.zSetOperations.incrementScore(key,
                        delta.getValue().doubleValue());
                String raw = serialize(new Metric(name, value, delta.getTimestamp()));
                this.redisOperations.opsForValue().set(key, raw);
            }
        1. 对指定Delta的名字加上前缀
        2. 如果zset中存在对应的key,则为其增长0,如果不存在,就加入,评分为0
        3. 对指定的值增长评分
        4. 实例化1个Metric后进行序列化,加入到String 结构中,key–> Delta的名字加上前缀,value->时间戳的字符串格式
      4. count–>返回zset中的数量.代码如下:

            public long count() {
                return this.zSetOperations.size();
            }
      5. findOne–> 根据给定的metricName获得对应的Metric.代码如下:

            public Metric findOne(String metricName) {
                // 1. 对metricName加上前缀生成rediskey
                String redisKey = keyFor(metricName);
                // 2. 获得对应的时间戳
                String raw = this.redisOperations.opsForValue().get(redisKey);
                // 3. 反序列化
                return deserialize(redisKey, raw, this.zSetOperations.score(redisKey));
            }
        1. 对metricName加上前缀生成rediskey
        2. 获得对应的时间戳
        3. 获得在zset中存储得到对应的评分(value值),然后进行反序列化.代码如下:

              private Metric deserialize(String redisKey, String v, Double value) {
                  if (redisKey == null || v == null || !redisKey.startsWith(this.prefix)) {
                      return null;
                  }
                  Date timestamp = new Date(Long.valueOf(v));
                  return new Metric(nameFor(redisKey), value, timestamp);
              }
      6. findAll,代码如下:

            public Iterable> findAll() {
        
                // This set is sorted
                // 1. 获得zset存储的所有的value
                Set keys = this.zSetOperations.range(0, -1);
                Iterator keysIt = keys.iterator();
        
                List> result = new ArrayList>(keys.size());
                // 2. 根据zset中存储的value获得存储在string结构中的值(时间戳)
                List values = this.redisOperations.opsForValue().multiGet(keys);
                // 3. 依次遍历values
                for (String v : values) {
                    // 3.1 获得存储在zset中的评分,然后进行反序列化为Metric.
                    String key = keysIt.next();
                    Metric value = deserialize(key, v, this.zSetOperations.score(key));
                    // 3.2 如果不为null,则加入到result中
                    if (value != null) {
                        result.add(value);
                    }
                }
                return result;
        
            }
        1. 获得zset存储的所有的value
        2. 根据zset中存储的value获得存储在string结构中的值(时间戳)
        3. 依次遍历values

          1. 获得存储在zset中的评分,然后进行反序列化为Metric.
          2. 如果不为null,则加入到result中

        思考:为什么能够这样实现?

        因为首先是通过zet zrange 0 -1 获得zset中所有的value,按照从小到大的顺序返回.然后通过MGET 命令获得对应的值,2者都是有序的,因此,可以这样实现

    RichGaugeRepository,InMemoryRichGaugeRepository

    RichGaugeRepository,InMemoryRichGaugeRepository的实现我们在后续的文章中会进行分析.


    来源:[]()

    赞(0) 打赏
    版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析41-CounterWriter,GaugeWriter解析

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址

    © 2014 - 2020 Java 技术驿站   网站地图  | 

    icp 湘ICP备14000180

    >>> 网站已平稳运行:

    觉得文章有用就打赏一下文章作者

    支付宝扫一扫打赏

    微信扫一扫打赏