背景:一個(gè)跑的好好的祖?zhèn)髀顸c(diǎn)接收系統(tǒng),最近突然出了問題,CPU使用率飆高,磁盤使用率開始飆升。

先理一下業(yè)務(wù)系統(tǒng)的邏輯,埋點(diǎn)上報(bào)服務(wù)上報(bào)的埋點(diǎn)分json消息和zip包兩種,一般都放入服務(wù)端的內(nèi)存隊(duì)列,內(nèi)存隊(duì)列超過閾值1w條之后,改為丟入磁盤,等隊(duì)列空閑之后慢慢消費(fèi)。
就這,為啥突然出問題了呢?
先扒tomcat配置,好家伙,清楚地寫著,server.tomcat.maxThread=20,限制了每秒接口并發(fā)最多20個(gè)線程。秉著對(duì)系統(tǒng)的信任,先將其改為100。
but,沒啥卵用。
dump下線程棧,發(fā)現(xiàn)大量線程處于等待狀態(tài)。
我們知道,ConcurrentLinkedQueue是非阻塞隊(duì)列,怎么會(huì)引起線程的等待呢?原來,這個(gè)系統(tǒng)自己基于ConcurrentLinkedQueue寫了個(gè)內(nèi)部隊(duì)列,大概是這么個(gè)形式:
class MQ {
private ConcurrentLinkedQueue<Message> queue = new ConcurrentLinkedQueue<>();
private Lock lock = new ReentrantLock();
private static final int QUEUE_MAX_SIZE = 10_000;
public void putMessage(Message message) {
lock.lock();
try {
//隊(duì)列大小到達(dá)門限時(shí)不再放入消息,轉(zhuǎn)存磁盤
if(getMQSize() < QUEUE_MAX_SIZE) {
queue.add(message);
} else {
saveToDisk(message);
}
} finally {
lock.unlock();
}
}
public Message getMessage() {
Message message;
lock.lock();
try {
message = queue.poll();
} finally {
lock.unlock();
}
return message;
}
public int getMQSize() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
}
我天,直接加了個(gè)粗粒度的鎖,將并發(fā)容器給堵住了。而且,每次放消息,都要查一次容器大小,該方法可要遍歷整個(gè)容器的對(duì)象,這個(gè)性能可太低了啊。既然如此,那為什么不用BlockingQueue呢?
當(dāng)時(shí)本著少改少錯(cuò)的原則,并沒有直接上,而是先去掉鎖,并在類內(nèi)維護(hù)一個(gè)原子累加器,雖然不會(huì)特別精準(zhǔn),但這種場(chǎng)景下,內(nèi)存隊(duì)列放1w個(gè)對(duì)象和9500個(gè)對(duì)象差不了多少,毛估估的一個(gè)值,主要是怕把內(nèi)存撐爆。
第一版就這么上了,然而,機(jī)器又掛了。這又是咋回事?
原來,從磁盤加載消息進(jìn)內(nèi)存隊(duì)列的速度太快,應(yīng)用啟動(dòng)瞬間,隊(duì)列里放了幾十萬對(duì)象了,dump文件分析,就這個(gè)隊(duì)列對(duì)象就占了1.2G了。失??!
第二版還是老老實(shí)實(shí)上BlockingQueue了,在生產(chǎn)者生產(chǎn)消息進(jìn)隊(duì)列時(shí),隊(duì)列滿了給他堵住,上線后經(jīng)過漫長(zhǎng)的幾個(gè)小時(shí),磁盤里堆積的百萬消息終于給他消費(fèi)完了。
完結(jié),撒花、