Histogram用来统计数据的分布。Histogram可以提供收集到的数据的最大值、最小值、平均值和中值,此外还能提供百分比分布,如75%,95%,99.9%等等。 Histogram是我学习Metrics的驱动之一。最初是想使用Histogram来优化接口处理统计能力。 看下下面的类图: 类图表示了Histogram需要用到的几类和个接口之间的关系,简单说明下: Sampling接口:意思是取样器,只有一个方法,作用是取出某一阶段的统计结果快照(Snapshot); Reservoir接口:数据池,所有的记录最终都会写入Reservoir实例并完成运算;Metrics提供了多种数据池来执行不同的抽样运算; Snapshot接口:快照接口,作用是对Reservoir中的数据进行二次计算并生成统计结果;Snapshot提供了统计数据的最大值、最小值、标准差、平均值、中值、95分位值等指标; Histogram类:实现了Sampling接口,是对外交互的入口。 老规矩,看一段示例代码: 这段代码中定义了一个delayedMethod()方法,该方法会随机sleep一段时间来模拟方法执行时长。代码主体就是使用Histogram报表来统计每三秒钟内这个方法的执行状态。 MetricRegistry也提供了Histogram实例的创建注册方法,不过为这里了更直观一些,还是使用了直接new关键字来构建Histogram实例。可以看到,每次创建Histogram对象都需要传入一个Reservoir接口的实例。 看下执行结果片段: 统计结果即是由Snapshot提供。 简单介绍下metrics提供的几种Reservoir: UniformReservoir:默认保存1028条记录,每次进行update操作的时候,首先会依次地将值填入1028条记录中,当记录满了之后,就会使用随机替换0 – 1027中的一条(随机抽样1028条记录)。因为是随机替换,所以也不需要进行加锁和解锁。 SlidingWindowReservoir:固定大小的数据池,从0到n-1填入数据,但是不会对数据进行更新,也不会进行加锁和解锁(固定抽样n条记录)。 SlidingTimeWindowReservoir:非固定大小的数据池,但是只会存储过去N秒的数据(抽样N秒内的记录)。使用ConcurrentSkipListMap进行存储。 ExponentiallyDecayingReservoir:固定大小的数据池。首先会逐个数据填满数据池,随后会将老的数据替换为新的数据(抽样n条最新的记录),使用ConcurrentSkipListMap进行存储。可以说是SlidingWindowReservoir与SlidingTimeWindowReservoir的结合。 就这样。
[阅读更多...]-
Metrics学习03 – Histogram
-
Metrics学习02 – Counter
Counter是要学习的Metrics的第二个工具,顾名思义即是计数器,通常用来执行统计之类的工作。 Counter比Gauge也复杂不了多少,直接看代码好了: 这里的代码较Gauge的那段稍稍有些不同:主要是在Counter实例的创建上 —— 这里使用了MetricRegistry实例的一个工具方法counter()。 MetricRegistry的实例为各种指标工具都提供了快速创建实例的方法,通过MetricRegistry提供的方法创建完成指标实例后可以自动完成注册。所以在上面的代码中没有再显式地将counter实例注册到MetricRegistry中。 另外值得细细品一下的是Counter的实现:Counter的计数能力主要依赖LongAdder类完成。 一般执行计数统计,最先想到的是AtomicLong/AtomicInt类。AtmoicXXX使用硬件级别的指令 CAS 来更新计数器的值,这样可以避免加锁,机器直接支持的指令,效率也很高。但是AtomicXXX中的 CAS 操作在出现线程竞争时,失败的线程会白白地循环一次,在并发很大的情况下,因为每次CAS都只有一个线程能成功,竞争失败的线程会非常多。失败次数越多,循环次数就越多,很多线程的CAS操作越来越接近 自旋锁(spin lock)。计数操作本来是一个很简单的操作,实际需要耗费的cpu时间应该是越少越好,AtomicXXX在高并发计数时,大量的cpu时间都浪费会在 自旋 上了,这很浪费,也降低了实际的计数效率。 使用LongAdder计数器可以避免这个问题。LongAdder采用了锁分段的思想,每个LongAdder实例都维护了一组计数单元Cell[],并发计数时,不同的线程可以在不同的计数单元cell[threadId]上进行计数,这样减少了线程竞争,提高了并发效率。本质上是用空间换时间的思想。 不过LongAdder一开始并不会直接使用计数单元Cell[],而是先使用一个long类型的base存储,当casBase()出现失败时,则会创建计数单元Cell[]。此时,如果在单个计数单元面出现了更新冲突,那么会尝试创建新的计数单元Cell,或者将Cell[]扩容为2倍。代码如下: 在高并发的情况下,LongAdder较之AtomicXXX有着数倍的性能优势。因此,通常建议使用LongAdder替换AtomicXXX。 再看下Counter的测试结果:
[阅读更多...] -
Metrics学习01 – Gauge
最近在写一个小应用,有些计量方式觉得可以参考一下Metrics,所以打算花两天的时间学习一下这个工具。 Overview Metrics是一个java监控计量工具包。在Spark、Hadoop、Spring等软件中都可以看到它的影子。Metrics提供了多种指标工具,如Gauge、Counter、Metrer、Timer、Histogram以及HealthCheck等。 这次先看一下Gauge,其他的看时间再逐个学习。 Gauge可以说是Metrics的最简单的一个指标:它的作用就是引用一个值。 来看个例子: 代码中通过匿名类的形式创建了一个Gauge接口的实例,作用是获取当前的时间。实现得非常简单,不需要多做解释。 因为Gauge接口只有一个方法getValue,是一个函数接口,所以可以考虑用lambda表达式创建Gauge接口实例: 既然Gauge这么简单,为什么不直接使用Gauge的值,还偏要用Gauge接口封装一下?是为了能在Metrics框架中记录并表示这个值。 Metrics框架中有几个基础概念:MetricRegistry、Reporter以及Metric。Metric前面提过两句,也演示了Metric之一的Gauge的用法。接下来简单介绍下MetricRegistry和Reporter。 MetricRegistry MetricRegistry的作用从类名就可以看出来:是Metric的注册中心(或者说是Metric容器),负责管理用户创建的所有Metric实例。 MetricRegistry主要提供了几种工具方法: 指标名称创建 创建Metric实例并自动注册 增删Metric实例 对注册的Metric实例应用监听器和过滤器 Reporter接口 从接口名称看起来,Reporter的作用应该是汇总指标实例的数据并生成报表。 Reporter接口的主要子类是ScheduledReporter,其核心是ScheduledExecutorService和ScheduledFuture,用来管理报表的定时输出。ScheduledReporter的子类包括ConsoleReporter、CsvReporter和Slf4jReporter,可以以不同的形式展示报表数据。 在4.x版本以前,Reporter接口还有实现一个类JmxReporter,可以通过JMX的形式输出报表数据。 扫了几个Reporter的实现,看出Reporter确实主要用来生成报表。不过也许是Metrics框架想要提供更多的自由,Reporter接口里并没有定义任何需要实现的方法: 如果需要以自定义的形式输出报表数据,可以继承ScheduledReporter类或实现Report接口来实现自己的需求,比如将报表数据以HTTP发送给统计应用。 Other 最后,看一下示例代码的执行结果:
[阅读更多...]