【问题标题】:Spring WebSocket - messages lost after being sent by controllerSpring WebSocket - 控制器发送后丢失的消息
【发布时间】:2015-01-19 14:22:14
【问题描述】:

Spring WebSocket 版本 4.1.1.RELEASE 未发送 WebSocket 消息时遇到问题。并非每条消息都发生这种情况,只是有些消息由于我不明白的原因未发送。消息的内容需要一些时间才能加载,但我不确定这是否是问题的原因,因为有时它可以正常工作,但大多数时候不能。

我的 Web 应用程序成功订阅了一个频道 /user/queue/overview/reqinfo/events,然后发送了四条消息来请求信息。我的控制器类通过 spring-data-jpa 和 Hibernate 在数据库上触发 COUNT() 语句,并将结果作为消息返回给请求来自的用户。所有四个消息都由相同的控制器方法处理,但只有两个响应被传递给发送者。我从 Spring 日志中提取了相关信息并附在此处。如您所见,SimpleBrokerMessageHandler 没有处理最后两条消息,因此不会发送给用户。创建这些消息的内容需要一分钟多的时间。

12:50:54,953 DEBUG clientInboundChannel-12 user.UserDestinationMessageHandler:187 - Translated /user/queue/overview/reqinfo/events -> [/queue/overview/reqinfo/events-user8178efnd]
12:50:54,953 DEBUG clientInboundChannel-12 broker.SimpleBrokerMessageHandler:175 - Processing SUBSCRIBE /queue/overview/reqinfo/events-user8178efnd id=sub-7 session=8178efnd
[...]
12:50:54,957 DEBUG clientInboundChannel-2 support.SimpAnnotationMethodMessageHandler:348 - Searching methods to handle SEND /app/overview/reqinfo/events session=8178efnd
12:50:54,957 DEBUG clientInboundChannel-1 support.SimpAnnotationMethodMessageHandler:348 - Searching methods to handle SEND /app/overview/reqinfo/events session=8178efnd
12:50:54,957 DEBUG clientInboundChannel-16 support.SimpAnnotationMethodMessageHandler:348 - Searching methods to handle SEND /app/overview/reqinfo/events session=8178efnd
12:50:54,959 DEBUG clientInboundChannel-10 support.SimpAnnotationMethodMessageHandler:348 - Searching methods to handle SEND /app/overview/reqinfo/events session=8178efnd
[...]
12:50:54,959 DEBUG clientInboundChannel-16 support.SimpAnnotationMethodMessageHandler:446 - Invoking de.d.i.g.websocket.OverviewController#requestOverviewEvents[2 args]
12:50:54,959 DEBUG clientInboundChannel-10 support.SimpAnnotationMethodMessageHandler:446 - Invoking de.d.i.g.websocket.OverviewController#requestOverviewEvents[2 args]
12:50:54,959 DEBUG clientInboundChannel-2 support.SimpAnnotationMethodMessageHandler:446 - Invoking de.d.i.g.websocket.OverviewController#requestOverviewEvents[2 args]
12:50:54,959 DEBUG clientInboundChannel-1 support.SimpAnnotationMethodMessageHandler:446 - Invoking de.d.i.g.websocket.OverviewController#requestOverviewEvents[2 args]
[...]
12:50:55,006 DEBUG clientInboundChannel-1 websocket.OverviewController:196 - requestOverviewEvents, incoming message: MsgOverviewEventsRequest(timeframe=THIS_WEEK)
12:50:55,006 DEBUG clientInboundChannel-10 websocket.OverviewController:196 - requestOverviewEvents, incoming message: MsgOverviewEventsRequest(timeframe=LAST_WEEK)
12:50:55,006 DEBUG clientInboundChannel-2 websocket.OverviewController:196 - requestOverviewEvents, incoming message: MsgOverviewEventsRequest(timeframe=YESTERDAY)
12:50:55,006 DEBUG clientInboundChannel-16 websocket.OverviewController:196 - requestOverviewEvents, incoming message: MsgOverviewEventsRequest(timeframe=TODAY)
[...]
12:50:55,032 DEBUG clientInboundChannel-16 websocket.OverviewController:225 - requestOverviewEvents, outgoing message: MsgOverviewEvents(timeframe=TODAY, count=31, error=false)
12:50:55,035 DEBUG clientInboundChannel-16 broker.SimpleBrokerMessageHandler:152 - Processing MESSAGE destination=/queue/overview/reqinfo/events-user8178efnd session=null payload={"timeframe":"TODAY","count":31,"error":false}
12:50:55,035 DEBUG clientInboundChannel-16 broker.SimpleBrokerMessageHandler:196 - Broadcasting to 1 sessions.
[...]
12:51:01,018 DEBUG clientInboundChannel-2 websocket.OverviewController:225 - requestOverviewEvents, outgoing message: MsgOverviewEvents(timeframe=YESTERDAY, count=190292, error=false)
12:51:01,020 DEBUG clientInboundChannel-2 broker.SimpleBrokerMessageHandler:152 - Processing MESSAGE destination=/queue/overview/reqinfo/events-user8178efnd session=null payload={"timeframe":"YESTERDAY","count":190292,"error":false}
12:51:01,020 DEBUG clientInboundChannel-2 broker.SimpleBrokerMessageHandler:196 - Broadcasting to 1 sessions.
[...]
12:51:19,081 DEBUG clientInboundChannel-1 websocket.OverviewController:225 - requestOverviewEvents, outgoing message: MsgOverviewEvents(timeframe=THIS_WEEK, count=845956, error=false)
12:51:19,088 DEBUG clientInboundChannel-10 websocket.OverviewController:225 - requestOverviewEvents, outgoing message: MsgOverviewEvents(timeframe=LAST_WEEK, count=1421118, error=false)

这是一个经常出现问题的示例。我对其他受相同问题影响的消息和控制器有其他看法,它们的消息构建不需要一分钟,只需几秒钟甚至更少。我正在使用支持 SockJS 的 SimpleMessageBroker(使用 SockJS 0.3.4)。在尝试解决此问题时,我将此 bean 添加到我的 WebSocket 配置类中以手动设置连接超时,但它没有解决问题:

@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
    long tenMinutesInMillis = 10 * 60 * 1000;

    ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
    container.setAsyncSendTimeout(tenMinutesInMillis);
    container.setMaxSessionIdleTimeout(tenMinutesInMillis);

    return container;
}

为什么有些消息被丢弃而其他消息却被正确发送?如果您需要日志或源代码中的更多信息,请询问。

[更新]有关该行为的一些附加信息:

始终返回计数最少项目的第一条消息。第二条消息大部分时间都返回,最后两条消息在大约 90% 的情况下无法发送。在加载失败后,其他视图也不会加载,即使接收并处理了订阅,响应消息也不会发送到 Web 界面。在浏览器中点击刷新以重新加载页面后一切正常,直到消息再次卡住。

但是,在后台运行并使用 WebSocket 向用户发送消息的心跳系统一直在工作。它似乎完全不受该问题的影响。

[更新 2]

当问题发生时,sockJsScheduler 似乎没有处理所有请求。 WebSocketMessageBrokerStats 记录的 INFO 消息提供以下输出:

INFO MessageBrokerSockJS-2 config.WebSocketMessageBrokerStats:113 - WebSocketSession[1 current WS(1)-HttpStream(0)-HttpPoll(0), 1 total, 0 closed abnormally (0 connect failure, 0 send limit, 0 transport error)], stompSubProtocol[processed CONNECT(1)-CONNECTED(1)-DISCONNECT(0)], stompBrokerRelay[null], inboundChannel[pool size = 16, active threads = 0, queued tasks = 0, completed tasks = 120], outboundChannelpool size = 16, active threads = 0, queued tasks = 0, completed tasks = 16], sockJsScheduler[pool size = 8, active threads = 1, queued tasks = 4, completed tasks = 18]

它说sockJsScheduler 有四个排队的任务没有被处理,也许这些是没有正确发送的消息?不幸的是,没有进一步的信息说明为什么它们没有在日志中处理。

【问题讨论】:

    标签: java spring spring-websocket


    【解决方案1】:

    在对我的应用程序,尤其是处理 Spring 代码进行深入调试后,我找到了该行为的原因。

    问题既不是控制器也不是 Spring 内部进程,而是我在 Spring Security 身份验证期间用于 Principal 对象的类。它存储用户成功回答的最后一个心跳 ID,以检查向系统发送消息的用户是否处于活动状态或会话是否已被视为已死亡。此 ID 包含在该对象的 equals()hashCode()toString() 方法中。

    Spring 使用其中一种方法(经过一些测试,我猜它是toString(),在我看来这不是一个好的选择)来转换@SendToUser 操作的目标路径。如果字符串发生了变化,我的应用程序在响应新的心跳 ID 时就是这种情况,Spring 无法转换目标路径并且不会发送消息。声明这一点的日志条目被隐藏为 TRACE 级别的消息,这就是我一开始没有找到它的原因。消息如下所示:

    14:29:00,260 TRACE clientInboundChannel-16 user.UserDestinationMessageHandler:175 - No user destinations found for /user/RtAuthorizedUser(super=AuthorizedUser(username=SIEM-User 1, authenticated=true, lastAnsweredHeartbeat=9971f9ea-8e64-4c24-823b-f7e6e277c775, alive=true), rtSessionId=RT_SID_iMonitor.443=782acc747aac01b9e0a7ece3ab2d27bd)/queue/overview/reqinfo/events
    

    我建议开发人员将此消息至少设为 DEBUG 级别(甚至是 WARN 或 ERROR),因为在发送过程中搜索问题时很难发现此信息。实际上这是一个错误。

    从上述三种方法中删除心跳ID后,系统运行良好,不再丢失消息。所以这首先是我的错误,但我认为toString() 的使用在这里并不是最佳选择。现在 Spring 可以正确翻译消息,生成这些消息(它们是 DEBUG 级别而不是上面的 TRACE):

    14:37:35,661 DEBUG clientInboundChannel-3 user.UserDestinationMessageHandler:187 - Translated /user/RtAuthorizedUser(super=AuthorizedUser(username=SIEM-User 1, authenticated=true, alive=true), rtSessionId=RT_SID_iMonitor.443=bf3adbaa858932b64a03724b6137e95d)/queue/overview/reqinfo/events -> [/queue/overview/reqinfo/events-user_qtca5il]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-05
      • 2015-05-25
      • 1970-01-01
      • 1970-01-01
      • 2011-10-11
      • 2013-02-27
      • 1970-01-01
      • 2018-07-02
      相关资源
      最近更新 更多