Configuration of Spring Websocket under load balancing

The last article Spring Websocket Configuration introduced the server-side websocket configuration under a single machine.

This implementation method will cause problems in the case of multiple servers. Because my business scenario is that the server often sends messages actively, it will WebSocketSessioncache it in memory, and when needed, it will find the corresponding connection to send data according to the user's primary key.

So this kind of memory cache is not effective in multiple servers, what should I do? The first idea is to WebSocketSessioncentralize cache, such as cache in Redis. However, serialization is WebSocketSessionnot supported and cannot be stored in redis.

Since there is no centralized cache, then when we need to send data, we send notifications to each server separately: please send a message to user A. After each server receives the notification, it traverses its own cache respectively. WebSocketSessionIf there is a connection of user A, it can send a message. Isn't this the application scenario of the MQ publish/subscribe model?

It just so happens that our system uses redis, and redis supports the publish/subscribe model, so let's start the transformation.

1. redis configuration

<context:annotation-config/>
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <!-- 超时时间(秒) -->
        <property name="maxInactiveIntervalInSeconds" value="3600" />
    </bean>

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="30"/>
        <property name="maxIdle" value="10"/>
        <property name="minIdle" value="1"/>
        <property name="maxWaitMillis" value="30000"/>
        <property name="testOnBorrow" value="true"/>
        <property name="testOnReturn" value="false"/>
        <property name="testWhileIdle" value="false"/>
    </bean>

    <!--2-->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis_server}" />
        <property name="port" value="6379" />
        <property name="password" value="${redis_password}" />
        <property name="timeout" value="3000" />
        <property name="poolConfig" ref="jedisPoolConfig" />
        <property name="usePool" value="true" />
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
        <property name="defaultSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
    </bean>

    <bean id="websocketTopicMessageListener" class="com.xx.xx.websocket.redisListener.WebsocketTopicMessageListener">
    </bean>

    <bean id="topicContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer" destroy-method="destroy">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
        <property name="taskExecutor"><!-- 此处有个奇怪的问题,无法正确使用其他类型的Executor -->
            <bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
                <property name="poolSize" value="3"></property>
            </bean>
        </property>
        <property name="messageListeners">
            <map>
                <entry key-ref="websocketTopicMessageListener">
                    <bean class="org.springframework.data.redis.listener.ChannelTopic">
                        <constructor-arg value="websocket:sendMsgTopic"/>
                    </bean>
                </entry>
            </map>
        </property>
    </bean>

In the above configuration, we define a Topic: websocket:sendMsgTopicand define the corresponding listener websocketTopicMessageListener.

The implementation of the listener is as follows. The implementation is very simple. After receiving the subscribed message, the userMessagesHandler.sendMessageToUser()method sends data to the websocket connection.

public class WebsocketTopicMessageListener implements MessageListener {

    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private UserMessagesHandler userMessagesHandler;

    @Override
    @Transactional(readOnly = true)
    public void onMessage(Message message, byte[] pattern) {
        byte[] body = message.getBody();
        byte[] channel = message.getChannel();
        String itemValue = (String) redisTemplate.getValueSerializer().deserialize(body);
        String topic = (String) redisTemplate.getStringSerializer().deserialize(channel);

        Gson gson = new Gson();
        UserNotice userNotice = gson.fromJson(itemValue, UserNotice.class);
        if (null != userNotice) {
            User user = userDAO.get(userNotice.getUserId());
            userMessagesHandler.sendMessageToUser(user.getLoginName(), new TextMessage(itemValue));
        }
    }
}

Therefore, userMessagesHandler.sendMessageToUser()the can be changed to websocket:sendMsgTopicpublish a message to Topic:.

public void sendNotification(UserNotice userNotice) {
        String channel = "websocket:sendMsgTopic";
        Gson gson = new Gson();
        redisTemplate.convertAndSend(channel, gson.toJson(userNotice));
    }

Through the above transformation, our websocket can support multi-server load balancing deployment.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324431303&siteId=291194637