Spring WebSocket+SockJS+Stomp 最大连接数测试

本文通过三次实验测试了Spring WebSocket+SockJS+Stomp在不同环境下客户端的最大连接数,包括win10和macOS系统,并对服务器端进行了初步测试。

测试目的

测试默认配置下的Spring WebSocket+SockJS+Stomp 最大连接数

服务器环境

系统:win10
内存:8G
运行环境:spring-boot-starter-undertow,以Spring jar包造型运行
服务器搭建参考《Spring Boot 的 WebSocket 开发说明文档》
服务器没有进行调优,使用的是SpringBoot默认配置。

测试环境

使用纯java代码测试

//连接数
public static int connectNum = 0;
//连接成功数
public static int successNum = 0;
//连接失败数
public static int errorNum = 0;

/**
 * 测试websocket最大连接数
 * @throws InterruptedException
 */
@Test
public void testConnect() throws InterruptedException {
    new Thread() {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //每次3秒打印一次连接结果
                System.out.println(                       DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss:sss") +
                        "  连接数:" + connectNum
                        + "  成功数:" + successNum
                        + "  失败数:" + errorNum);
            }
        }
    }.start();
    List<WebSocketStompClient> list = new ArrayList<>();
    System.out.println("开始时间:"
            + DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss:sss"));
    while (true) {
        //连接失败超过10次,停止测试
        if(errorNum > 10){
            break;
        }
        list.add(newConnect(++connectNum));
        Thread.sleep(10);
    }
}

/**
 * 创建websocket连接
 * @param i
 * @return
 */
private WebSocketStompClient newConnect(int i) {
    List<Transport> transports = new ArrayList<>();
    transports.add(new WebSocketTransport(new StandardWebSocketClient()));
    WebSocketClient socketClient = new SockJsClient(transports);
    WebSocketStompClient stompClient = new WebSocketStompClient(socketClient);

    stompClient.setMessageConverter(new MappingJackson2MessageConverter());
    ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
    taskScheduler.afterPropertiesSet();
    stompClient.setTaskScheduler(taskScheduler);

    String url = "ws://localhost:8088/websocket?token=" + i;
    stompClient.connect(url, new TestConnectHandler());
    return stompClient;
}

private static synchronized void addSuccessNum() {
    successNum++;
}

private static synchronized void addErrorNum() {
    errorNum++;
}

private static class TestConnectHandler extends StompSessionHandlerAdapter {
    @Override
    public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
        addSuccessNum();
    }

    @Override
    public void handleTransportError(StompSession session, Throwable exception) {
        addErrorNum();
    }
}

开始测试

第一次测试
测试环境

win10 一台
网络:本地网络 localhost
测试方式:直接跑上面的java代码
测试时长:2018-03-09 10:16:18 ~ 2018-03-09 10:26:42 共 10分24秒
测试结果:
测试机的开始测试的时间图
这里写图片描述

测试机达到连接上限的结束测试图
这里写图片描述

服务器的当时的连接数图
这里写图片描述

最终当测试机器的连接数达到16343时,出现异常,无法再添加连接了。

第二次测试
测试环境同第一次

win10 一台
网络:本地网络 localhost
测试方式:直接跑上面的java代码
测试时长:2018-03-09 10:29:23 ~ 2018-03-09 10:39:41 共 10分18秒
测试结果:
测试机的开始测试的时间图
这里写图片描述

测试机达到连接上限的结束测试图
这里写图片描述

服务器的当时的连接数图
这里写图片描述

最终当测试机器的连接数达到16284时,出现异常,无法再添加连接了。

第三次测试
测试环境

win10 三台,macOS10.11.6 一台
网络:局域网
测试方式:每台测试机跑上面java代码,然后限制跑10000个连接数
测试时长:2018-03-09 13:02:26 ~ 2018-03-09 14:18:00:41 共 1小时16分15秒
测试结果:
测试机忘记截图了
服务器的连接数图
这里写图片描述

一开始是用三台win10测试的,当三台机子跑满10000个连接时服务器还是没有出来异常,所以又加了一台macOS机子进来,最终当服务器的连接数达到37001时,macOS出现了异常,又没找到更多机子测试,最终终止了本次测试。

测试结论

前两次测试是在本地测试的(服务器和测试在同一台机子上),当连接数到1万6的时候测试端就出现了异常,看异常是已经达到了最大连接数了,服务器的连接并未到达上限,测试出现了瓶颈。所以第三次测试又找来了几台机子一起测,一开始是用三台win10测试,每台跑10K个连接,当三台机器跑满10K时,测试端和服务器端都没有出现异常,所以又找到了一台macOS苹果机加入测试,当macOS端跑到7K时出现了连接数最大限制异常,无法再新增连接了,最后由于没有新机子测试,就终止了测试。

最终结论是
1. win10下客户端WebSocketStompClient的最大连接数是16K+,macOS下客户端WebSocketStompClient的最大连接数是7K。
2. 由于测试条件不成熟,没用专业的测试工具,没能测出服务器端的最大连接数,估计不少于40K。
3. 本次测试仅仅是测试连接数,连接没有加入负载代码,数据仅做参考,不能应用到实际项目中。

### SpringBootWebSocket、MySQL 和 Vue 实现 1v1 多个会话的聊天系统的架构设计 #### 架构概述 为了实现支持1v1多个会话的聊天系统,可以采用分层架构设计。以下是各层的主要职责: - **Presentation Layer (前端)** 使用 Vue.js 开发用户交互界面,负责显示聊天窗口、处理用户输入以及通过 WebSocket 进行通信。 - **Application Layer (应用层)** 负责协调 Presentation 层和 Domain 层之间的交互,定义用例控制器来管理具体的业务逻辑。 - **Domain Layer (领域层)** 定义核心业务逻辑,包括领域模型(如 User、Message)、仓储口以及领域服务。 - **Infrastructure Layer (基础设施层)** 提供底层技术支持,例如 MySQL 据库访问、Redis 缓存管理和 WebSocket 的具体实现。 --- #### 技术选型与模块划分 ##### 前端部分 (Vue.js) 前端使用 Vue.js 来构建动态的聊天界面,主要包括以下几个模块: - `ChatComponent.vue`:用于渲染聊天窗口。 - `WebSocketService.js`:封装 WebSocket 的连、消息发送和收逻辑。 - `Store.js`:使用 Vuex 或 Pinia 管理全局状态,存储当前用户的会话列表和未读消息。 示例代码如下: ```javascript // WebSocketService.js export default class WebSocketService { constructor() { this.socket = null; this.connect(); } connect() { const userId = localStorage.getItem('userId'); this.socket = new WebSocket(`ws://localhost:8080/ws/${userId}`); this.socket.onmessage = (event) => { console.log("Received message:", event.data); // 更新Vuex中的消息状态 }; this.socket.onerror = (error) => { console.error("WebSocket error:", error); }; } sendMessage(message) { if (this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify(message)); } } } ``` ##### 后端部分 (Spring Boot + WebSocket) 后端使用 Spring Boot 实现 WebSocket 功能,并结合 Redis 和 MySQL 存储在线状态和历史消息。 ###### WebSocket 配置 创建一个 WebSocket 配置类,启用 STOMP 协议并配置消息代理。 ```java import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); // 订阅路径前缀 config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws/{userId}") .setAllowedOrigins("*") // 设置允许跨域请求 .withSockJS(); // 支持 SockJS 兼容性 } } ``` ###### 消息处理器 编写一个 Controller 类来处理客户端发送的消息,并将其广播给目标用户。 ```java import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; @Controller public class ChatController { @MessageMapping("/sendMessage") @SendTo("/topic/messages/{receiverId}") // 将消息推送给特定用户 public Message handleMessage(Message message) { System.out.println("Handling chat message:" + message.getContent()); return message; } } class Message { private String senderId; private String receiverId; private String content; // Getters and Setters... } ``` ##### 据库存储 (MySQL) 据库主要用于持久化历史消息和用户信息。表结构设计如下: - **users 表** ```sql CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` - **messages 表** ```sql CREATE TABLE messages ( id BIGINT AUTO_INCREMENT PRIMARY KEY, sender_id INT NOT NULL, receiver_id INT NOT NULL, content TEXT NOT NULL, sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_read BOOLEAN DEFAULT FALSE, FOREIGN KEY (sender_id) REFERENCES users(id), FOREIGN KEY (receiver_id) REFERENCES users(id) ); ``` 当用户上线时,可以从据库中加载未读消息;当新消息到达时,更新 `is_read` 字段以标记为已读。 --- #### 关键点解析 1. **在线状态管理** 使用 Redis 存储用户的在线状态,以便快速判断某个用户是否处于活动状态[^3]。如果目标用户不在线,则将消息暂存在 MySQL 中,待其重新上线后再推送。 2. **消息可靠性保障** 对于重要的消息(如文件传输或敏感通知),可以通过 ACK 机制确认消息已被成功收[^4]。 3. **性能优化** - 引入心跳检测机制,定期检查 WebSocket的有效性。 - 结合 Nginx 反向代理负载均衡多个 WebSocket 节点,提升并发能力[^3]。 --- ### 示例运行流程 假设 Alice 和 Bob 正在进行聊天: 1. Alice 登录系统后,前端通过 `/ws/Alice` 地址建立 WebSocket。 2. 当 Alice 发送一条消息给 Bob 时,前端调用 `sendMessage` 方法并将消息内容传至服务器。 3. 服务器收到消息后,检查 Bob 是否在线(通过 Redis 查询)。如果在线,则立即将消息推送到 Bob 的订阅地址 `/topic/messages/Bob`;否则,将消息存入 MySQL 并等待 Bob 上线后再推送。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值