生产线程池打满问题排查

740 阅读2分钟

背景

背景实时定位数据mqtt数据,使用线程池进行消费,线程任务中采用websocket实时推送给前端实时定位数据,线程直接打满,消费速度降到 5/s个左右,线程池等待队列挤压几十万待处理任务;

重启服务后很快线程池又被打满(100个核心线程);

mqtt实时定位数据推送频率200多tps;

线上问题排查:

找到服务PID

打印运行中的线程堆栈信息

jstack 1

通过线程池线程前缀查找,并分析问题

image.png

注释掉瓶颈逻辑后观察线程池状态恢复正常;

解决,削峰填谷 MQ登场

第一次解决:

本场景属于实时位置推送,故采用本地服务内存队列实现;

采用阻塞队列 LinkedBlockingQueue 实现;可能存在队列数据积压问题

image.png

@Component
class LocalBlockingQueue : InitializingBean {
    //阻塞队列
    private val blockingQueue = LinkedBlockingQueue<String>()
    //bean实例化后初始化队列消费线程
    override fun afterPropertiesSet() {
        try {
            Thread {
                log.info("实时定位待推送推送任务_启动成功")
                while (true) {
                    if (Thread.currentThread().isInterrupted) {
                        log.warn("实时定位待推送推送任务_被中断 ")
                        break
                    }
                    try {
                        WebSocketUtils.sendMessageToAllOnline(blockingQueue.take())
                    } catch (e: Exception) {
                        log.error("实时定位待推送推送任务_推送失败 errorMsg:{}", e.message, e);
                    }
                }
            }.apply {
                this.name = "websocket_queue_task"
            }.start()
        } catch (e: Exception) {
            log.error("实时定位待推送推送任务_启动异常失败 errorMsg:{}", e.message, e)
        }
    }
    
    //添加队列消息
    fun addMsgToQueue(data: String) {
        blockingQueue.add(data)
    }

    fun getQueue(): LinkedBlockingQueue<String> {
        return blockingQueue
    }
}

发送消息采用并行流(如果担心默认线程池问题,可指定自己的线程池实现)

/**
 * 发送消息
 *
 * @param message 消息文本
 */
public static void sendMessageToAllOnline(String message) {
    WebSocketSessionHolder.getAllSessions().parallelStream().forEach(webSocketSession -> sendMessage(webSocketSession, message));
}

运行几个小时后,观察待推送队列出现积压了!!!!

第一次再解决:

异常日志查看

java.io.IOException: UT002027: Could not send data, as the underlying web socket connection has been broken

image.png

数据发送异常失败,session断连逻辑增加,通过afterConnectionClosed方法自动从session管理池中移除;

private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
        if (session != null && session.isOpen()) {
            try {
                session.sendMessage(message);
            } catch (IOException e) {
                if (session != null && session.isOpen()) {
                    try {
                        session.close();
                    } catch (IOException ex) {
                        log.warn("websocket_send 发送消息异常 断开连接 session[{}],发送消息({}) errorMsg:{}", session, message, ex.getMessage());
                    }
                } else {
                    log.error("websocket_send 发送消息异常 [send] session({}) 发送消息({}) 异常", session, message, e);
                }
            }
        }
    }