45

Metrics学习02 – Counter

 5 years ago
source link: https://www.tuicool.com/articles/Ffeaear
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Counter是要学习的Metrics的第二个工具,顾名思义即是计数器,通常用来执行统计之类的工作。

Counter比Gauge也复杂不了多少,直接看代码好了:

public class CounterShow {
 
    public static void main(String[] args) {
 
        final MetricRegistry metrics = new MetricRegistry();
        final ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();
        reporter.start(3, TimeUnit.SECONDS);
 
        Counter counter = metrics.counter("异常监控");
 
        for (int i = 0; i < 100; i++) {
            try {
                if (0 == i % 3) {
                    throw new RuntimeException("自定义异常");
                }
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                counter.inc();
            }
        }
    }
}

这里的代码较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倍。代码如下:

    public void increment() {
        add(1L);
    }
 
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null || !casBase(b = base, b + x)) {// 如cells不为空,直接对cells操作;否则casBase
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))    // CAS cell
                longAccumulate(x, null, uncontended);    // 创建新的Cell或者扩容
        }
    }

在高并发的情况下,LongAdder较之AtomicXXX有着数倍的性能优势。因此,通常建议使用LongAdder替换AtomicXXX。

再看下Counter的测试结果:

19-6-24 22:18:37 ===============================================================
-- Counters --------------------------------------------------------------------
异常监控
             count = 2
 
19-6-24 22:18:40 ===============================================================
-- Counters --------------------------------------------------------------------
异常监控
             count = 3
 
19-6-24 22:18:43 ===============================================================
-- Counters --------------------------------------------------------------------
异常监控
             count = 5

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK