simpMessagingTemplate convertAndSendToUser等待线程阻塞了其他功能

时间:2019-01-21 06:34:54

标签: java spring spring-boot websocket stomp

我们在应用程序中使用了Stomp,SpringBoot和WebSockets。服务器应用程序正在执行以下操作: 1)生成要推送给用户的消息, 2)接受WebSocket连接和 3)将消息推送到ActiveMQ踩踏代理。线程转储显示了许多与simpMessagingTemplate convertAndSendToUser API调用关联的等待线程。

该应用程序的两个实例正在云中运行。该应用程序使用simpMessagingTemplate convertAndSendToUser API生成消息并将其推送到ActiveMQ脚踩代理(单独运行)。

我们使用Gatling模拟用户WebSocket连接以进行负载测试。加特林在一个单独的实例上运行。该应用程序适用于2000个用户连接。将用户增加到4000后,我们看到消息生成线程停止了。用户可以毫无问题地连接到相同的服务器。

如果我们注释simpMessagingTemplate convertAndSendToUser API调用,则一切工作都很好(生成消息和新的WebSocket连接)。因此,我们对convertAndSendToUser API的问题表示怀疑。

Threaddump堆栈跟踪如下:

"ForkJoinPool-1-worker-440" #477 daemon prio=5 os_prio=0 tid=0x00007f0c541c2800 nid=0x2a47 sleeping[0x00007f08e6371000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at reactor.util.concurrent.WaitStrategy$Sleeping.waitFor(WaitStrategy.java:319)
	at reactor.core.publisher.MonoProcessor.block(MonoProcessor.java:211)
	at reactor.core.publisher.MonoProcessor.block(MonoProcessor.java:176)
	at org.springframework.messaging.tcp.reactor.AbstractMonoToListenableFutureAdapter.get(AbstractMonoToListenableFutureAdapter.java:73)
	at org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler$SystemStompConnectionHandler.forward(StompBrokerRelayMessageHandler.java:980)
	at org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler.handleMessageInternal(StompBrokerRelayMessageHandler.java:549)
	at org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler.handleMessage(AbstractBrokerMessageHandler.java:234)
	at org.springframework.messaging.support.ExecutorSubscribableChannel$SendTask.run(ExecutorSubscribableChannel.java:138)
	at org.springframework.messaging.support.ExecutorSubscribableChannel.sendInternal(ExecutorSubscribableChannel.java:94)
	at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:119)
	at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:105)
	at org.springframework.messaging.simp.SimpMessagingTemplate.sendInternal(SimpMessagingTemplate.java:187)
	at org.springframework.messaging.simp.SimpMessagingTemplate.doSend(SimpMessagingTemplate.java:162)
	at org.springframework.messaging.simp.SimpMessagingTemplate.doSend(SimpMessagingTemplate.java:48)
	at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:108)
	at org.springframework.messaging.simp.user.UserDestinationMessageHandler.handleMessage(UserDestinationMessageHandler.java:227)
	at org.springframework.messaging.support.ExecutorSubscribableChannel$SendTask.run(ExecutorSubscribableChannel.java:138)
	at org.springframework.messaging.support.ExecutorSubscribableChannel.sendInternal(ExecutorSubscribableChannel.java:94)
	at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:119)
	at org.springframework.messaging.simp.SimpMessagingTemplate.sendInternal(SimpMessagingTemplate.java:187)
	at org.springframework.messaging.simp.SimpMessagingTemplate.doSend(SimpMessagingTemplate.java:162)
	at org.springframework.messaging.simp.SimpMessagingTemplate.doSend(SimpMessagingTemplate.java:48)
	at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:108)
	at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:150)
	at org.springframework.messaging.simp.SimpMessagingTemplate.convertAndSendToUser(SimpMessagingTemplate.java:229)
	at org.springframework.messaging.simp.SimpMessagingTemplate.convertAndSendToUser(SimpMessagingTemplate.java:218)
	at org.springframework.messaging.simp.SimpMessagingTemplate.convertAndSendToUser(SimpMessagingTemplate.java:204)
	at com.mypackage.PushMessageManager.lambda$sendMyMessage$2(PushMessageManager.java:77)
	at com.mypackage.PushMessageManager$$Lambda$923/1850582969.accept(Unknown Source)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
	at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:401)
	at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:734)
	at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at com.mypackage.PushMessageManager.sendMyMessage(PushMessageManager.java:74)
	at com.mypackage.PushMessageManager.lambda$processPushMessage$0(PushMessageManager.java:61)
	at com.mypackage.PushMessageManager$$Lambda$664/624459498.run(Unknown Source)
	at nl.talsmasoftware.context.functions.RunnableWithContext.run(RunnableWithContext.java:42)
	at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1626)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at nl.talsmasoftware.context.executors.ContextAwareExecutorService$1.call(ContextAwareExecutorService.java:59)
	at nl.talsmasoftware.context.delegation.RunnableAdapter.run(RunnableAdapter.java:44)
	at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

   Locked ownable synchronizers:
	- None

下面在图中提到了步骤:

  1. 加特林JMS发布者将JMS消息以每分钟20000条消息的速度推送到Active MQ代理。请注意,这些消息不只针对一个用户。它基于WebSocket用户连接进行分发。
  2. 我们的应用程序具有JMS侦听器来接收这些消息。我们正在运行说该应用程序的2个实例,因此需要两个JMS侦听器来处理此消息。
  3. 一旦应用程序收到JMS消息,它将检查来自缓存的会话信息以识别已连接的用户,并使用simpMessagingTemplate convertAndSendToUser API simpMessagingTemplate.convertAndSendToUser(sessionId,“ / queue / abc”,有效负载)推送到另一个ActiveMQ踩踏代理。请注意,当用户首次连接到应用程序时,sessionId将存储在分布式缓存中。因此,这些是有效的会话ID。
  4. ActiveMQ踩踏代理然后将这些消息传播到各个用户踩踏队列。
  5. Gatling WebSocket客户端(每个具有2000个用户连接)应通过WebSocket连接接收这些消息。
  6. 客户端连接和订阅看起来像这样

    stompClient.connect({'username':$(“#userName”)。val()},函数(框架){     setConnected(true);     subscription = stompClient.subscribe('/ user / queue / abc',function(message){          showData(JSON.parse(message.body));      },headers = {'loginusername':$(“#userName”)。val()}); });

因此,每个用户将仅收到针对他们的消息,而不是所有消息。这就是我们在通过WebSocket连接时将用户连接到各个队列的原因,并且还使用convertAndSendToUser将消息推送到特定会话。后端JMS发布者确保将消息以循环方式发布给用户。

要回答有关识别瓶颈的问题,如果我们说有2000个用户,那么一切正常。但是,当我们添加更多用户时,我们看到应用程序的JMS侦听器无法侦听后端Gatling JMS负载生成器每分钟发送的20000条消息。因此,ActiveMQ JMS队列深度会增加。

为确保瓶颈是convertAndSendToUser API,我们对该API的调用进行了注释。如果这样做,我们将能够连接约13k WebSocket连接,并且后端JMS侦听器还能够每分钟消耗20000条消息。

希望这可以澄清您的一些问题。 更新 下面给出了显示simpMessagingTemplate.convertAndSendToUser API的异步调用的代码段。这里RepositoryUtil.executor()是我们自己的executor对象包装器。

    public CompletableFuture<Void> processPushMessage(String userName, String payload) {
    return ContextAwareCompletableFuture.runAsync(() -> {
        sendABCMessage(payload, userName);
    }, RepositoryUtil.executor());
}

public void sendABCMessage(@Payload String payload, String username) {
    ArrayList<UserProfiles> userProfiles = (ArrayList<UserProfiles>) cacheService.getValue(username);
    if (Objects.nonNull(userProfiles) && userProfiles.size() > 0) {
      userProfiles.parallelStream()
          .filter(userProfiles1 -> ("/user/queue/abc".equalsIgnoreCase(userProfiles1.getSubscribeMapping()) && username.equals(userProfiles1.getUserName())))
          .forEach(userProfiles1 -> {              simpMessagingTemplate.convertAndSendToUser(userProfiles1.getSessionId(), "/queue/abc", payload);
          });
    } else {
      LOGGER.info("sendABCMessage userProfiles is null. Payload: {}", payload);
    }
}

2 个答案:

答案 0 :(得分:2)

我们可以通过移至/ user / topic而不是/ user / queue来解决此问题。现在,我们每分钟可以处理约35,000条来自后端和8k Web套接字用户连接的消息。

答案 1 :(得分:0)

  

该应用程序适用于2000个用户连接,每分钟负载20,000条消息。将用户增加到4000后,我们看到消息生成线程停止了。

如果将20,000条消息推送到ActiveMQ,并且每条消息有1,000个订阅者,则意味着20,000,000条消息(1,000 * 20,000)被发布回WebSocket客户端。因此,请尝试确定流经的消息总量并了解瓶颈所在(服务器将消息转发到ActiveMQ,ActiveMQ处理消息或服务器将消息发布到WebSocket客户端)。

对于20,000条消息,它们是从单个线程生成的,还是从大量不同的线程发送的,例如是处理来自WebSocket客户端的消息还是REST HTTP调用的结果?如果是后者,则可能是有太多线程试图同时将消息转发给代理,因此您可能必须应用某种速率限制。

最终,您需要了解总体数量,瓶颈所在以及应用某些速率限制的位置。