背景
背景实时定位数据mqtt数据,使用线程池进行消费,线程任务中采用websocket实时推送给前端实时定位数据,线程直接打满,消费速度降到 5/s个左右,线程池等待队列挤压几十万待处理任务;
重启服务后很快线程池又被打满(100个核心线程);
mqtt实时定位数据推送频率200多tps;
线上问题排查:
找到服务PID
打印运行中的线程堆栈信息
jstack 1
通过线程池线程前缀查找,并分析问题
注释掉瓶颈逻辑后观察线程池状态恢复正常;
解决,削峰填谷 MQ登场
第一次解决:
本场景属于实时位置推送,故采用本地服务内存队列实现;
采用阻塞队列 LinkedBlockingQueue 实现;可能存在队列数据积压问题
@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
数据发送异常失败,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);
}
}
}
}