Scaling WebSockets in Spring services [Part 2]

Alexander Kozhenkov
Javarevisited
Published in
3 min readApr 12, 2022
Photo by CHUTTERSNAP on Unsplash

In the previous article, I wrote about a Broker Relay in Spring Framework that allows you to horizontally scale up WebSockets connections.

But I did not cover the problem with @SubscribeMapping well. In this article, I want to fix it.

@SubscribeMapping

Let me remind you that the problem was that often we want to send the initial state as soon as the user subscribes to the topic.

For example, we have a topic /topic/charts/{charId} and we want to send 20 latest messages at the time of subscription.

If we declare app prefixes /app and broker prefixes /topic, then the subscription message will not get into Spring controllers. It will be proxied directly to the broker.

In the previous article, I suggested 2 ways to fix it:

  1. Create our own BeanPostProcessor
  2. Use @GetMapping to request initial data

Fortunately, there is a much simpler way (spoiler: we can just add /topic and /user to app prefixes as well as to broker prefixes). Now I will explain how it works.

WebSocket channels

In Spring Framework, we have 3 message channels:

  1. Inbound — to get messages from clients
  2. Broker — to proxy messages from the inbound channel or from the application itself (e.g., using SimpMessagingTemplate)
  3. Outbound — to send messages to clients

Each channel has a set of message handlers.

WebSocketAnnotationMethodMessageHandler

WebSocketAnnonationMethodMessageHandler receives messages from clients and if they match app prefixes, tries to match their destinations with controllers.

If a controller method returns a value, it is sent directly to the outbound channel to this WebSocket connection. So, it doesn’t matter how many connections an individual user holds, a response will be sent only to one subscribed connection.

UserDestinationMessageHandler

If a destination matches a user prefix, UserDestinationMessageHandler resolves a user and sends this message with another destination to the broker channel. For multiple user connections, it will send multiple messages.

StompBrokerRelayMessageHandler

StompBrokerRelayMessageHandler proxies messages to an external broker. For, example it may proxy messages from the broker channel after resolving a user in UserDestinationMessageHandler.

Each handler works independently. So, we can easily add /topic and /user to app prefixes. Thus, messages will be sent to controllers as well as will be proxied to an external message broker.

Users resolving

One more thing about how UserDestinationMessageHandler works. Users may be connected to different application instances, but they still need to resolve users somehow.

For this purpose, we can use MultiServerUserRegistry . It periodically sends a snapshot of local users to a specific topic, and subscribes to this topic to get remote users.

If a handler needs to resolve a message but it didn’t get remote users yet, it sends a message to the unresolved topic. So, it will be resolved in another instance.

That’s pretty much it about Spring Broker Relay.
You can find a complete code on GitHub ( scalable branch contains the final solution).

--

--