之前写自己的rpc框架时候遇到这个问题,记录下
一、问题
假设代码会打印日志“key111 count=1”,每秒有1000qps请求调用这行代码,那么一秒打印1000条。
如何实现监控日志按秒聚合,每秒只打印一条“key111 count=1000”?
二、线程模型
a. 生产消费者模式,消费者做聚合、打日志
image.png日志全打印到queue里,一个消费者负责聚合、落盘;
如果queue满了,那么生产者直接打印。
优点:好写
缺点:
- 占内存
- queue满了(比如写吞吐太快来不及消费/log线程异常/log线程饥饿)有退化问题
b. 生产消费者模式,生产者写入时聚合
image.png我的框架就是这么实现的,见https://github.com/seeflood/PUA-RPC/blob/master/src/main/java/pua/rpc/framework/common/utils/LogUtils.java
缺点如上图,包括:
- jdk提供的DelayQueue是无界的,需要自己手动封装一个有界DelayQueue
https://stackoverflow.com/questions/29832664/how-to-set-capacity-for-java-delayqueue
Q: schedulerExecutorService是有界还是无界的?
A: schedulerExecutorService也是无界的。https://stackoverflow.com/questions/7403764/what-is-best-way-to-have-bounded-queue-with-scheduledthreadpoolexecutor
Q: 能否用schedulerExecutorService避免这个问题?
能。用schedulerExecutorService的话,queue就用普通的blockingQueue就行,即:
image.png
- 退化问题
- 有点复杂
c. 生产消费者模式,生产者写入时聚合,一个单独的scheduler负责投递任务
image.pnglog遍历map里所有元素,打完之后不用删,让map等着被自动gc
Q: 为啥不是scheduler把map换下来后,自己把日志打印了,干嘛丢给另一个线程打日志?
A: scheduler刚把map换下来,自己打日志可能遍历map时有并发写,导致打印不精确,想通过等一会再遍历map打日志来避免这种并发问题。
优点:
相比于b,结构清晰些
缺点:
- jdk提供的DelayQueue是无界的
- 退化问题
c2. 简单一点,scheduler先sleep再自己打日志
image.png优点:省了queue,就没有queue无界、queue满时退化问题
缺点:sleep(200)解并发问题可能不准,也可能导致再次唤醒时太晚
- Q: 等待下一秒时,是用await()还是Thread.sleep()
c3. 不换map也不删元素,遍历map、把每个counter通过cas减到0
image.png-
Q: 遍历ConcurrentHashMap的并发安全性?
A: 看文章说能正常遍历,但是简单翻了下代码怕扔异常,有空再细看
https://stackoverflow.com/questions/3768554/is-iterating-concurrenthashmap-values-thread-safe -
Q: 一直不删元素可能时间长了垃圾太多,可能遍历时间变长、甚至内存泄漏OOM。是否要清理、如何清理?
d. 生产消费者模式,基于环+Timer
image.png其实就是时间轮
Q: 写环太麻烦了,能否直接用Map代替环?
A: 不能,有可能内存泄漏
三、使用开源库来实现?
a. Dubbo Metrics?
https://developer.aliyun.com/article/693569
文档太少,只能靠看ut、源码来猜怎么用;
看着没有按秒统计、按秒打日志的功能;
结论:不选择
- Q: 看统计吞吐用到EWMA,这啥算法?
b. Dropwizard Metrics?
https://metrics.dropwizard.io/4.1.2/getting-started.html
https://www.baeldung.com/dropwizard-metrics
看着没有按秒统计、按秒打日志的功能;
结论:不选择
网友评论