目录
1. 前言:为什么要学习WebSocket主从架构
第一章:基础知识准备
2.1 什么是WebSocket
生活中的例子
技术特点
2.2 WebSocket vs HTTP
什么时候用WebSocket?
2.3 什么是主从架构
生活中的例子
技术架构图
2.4 环境准备
需要的软件
项目结构
第二章:WebSocket基础入门
3.1 第一个WebSocket服务器
3.2 第一个WebSocket客户端
3.3 消息通信基础
消息格式设计
常见消息类型
完整的消息处理示例
第三章:主从架构设计原理
4.1 架构设计思路
为什么需要主从架构?
架构演进过程
4.2 核心组件说明
1. 主服务器(Master Server)
2. 从服务器(Slave Server)
3. 客户端(Client)
4.3 通信协议设计
通信流程详解
消息协议规范
第四章:主服务器详细实现
5.1 主服务器架构
核心类设计
5.2 从服务器管理
从服务器信息结构
从服务器注册流程
5.3 负载均衡实现
多种负载均衡策略
5.4 完整代码实现
第五章:从服务器详细实现
6.1 从服务器架构
核心类设计
6.2 与主服务器通信
连接管理
6.3 客户端管理
客户端连接处理
6.4 完整代码实现
第六章:客户端详细实现
7.1 客户端连接流程
连接流程图
7.2 自动重连机制
重连策略
1. 前言:为什么要学习WebSocket主从架构
想象一下,你正在开发一个实时在线游戏,有成千上万的玩家同时在线。如果只用一台服务器,会发生什么?
- 服务器压力巨大,可能随时崩溃
- 玩家体验差,延迟高
- 无法扩展,玩家数量受限
这就是为什么我们需要学习WebSocket主从架构!它能帮助我们:
- 分散压力:把玩家分配到不同的服务器
- 提高性能:每个服务器只处理部分玩家
- 易于扩展:需要时随时增加服务器
- 高可用性:一台服务器挂了,其他的继续工作
本教程将带你从零开始,一步步掌握这个强大的架构!
第一章:基础知识准备
2.1 什么是WebSocket
生活中的例子
想象两种通信方式:
-
写信(HTTP):
- 你写一封信(请求)
- 寄给朋友
- 等待回信(响应)
- 想再说话?再写一封信
-
打电话(WebSocket):
- 拨通电话(建立连接)
- 随时可以说话(发送消息)
- 对方也能随时回话(接收消息)
- 直到挂断电话(断开连接)
WebSocket就像打电话,一旦连接建立,双方可以随时通信!
技术特点
// HTTP方式(传统)
客户端: "服务器,现在几点?"
服务器: "下午3点"
// 连接断开客户端: "服务器,现在几点?" // 需要重新连接
服务器: "下午3点01分"
// 连接又断开// WebSocket方式
客户端 <--持续连接--> 服务器
客户端: "现在几点?"
服务器: "下午3点"
客户端: "天气如何?"
服务器: "晴天"
服务器: "对了,有新消息!" // 服务器主动推送
// 连接一直保持
2.2 WebSocket vs HTTP
特性 | HTTP | WebSocket |
---|---|---|
通信方式 | 请求-响应 | 全双工 |
连接状态 | 短连接 | 长连接 |
服务器推送 | 不支持 | 支持 |
实时性 | 低(需要轮询) | 高 |
资源消耗 | 高(频繁建立连接) | 低(保持连接) |
适用场景 | 普通网页 | 实时应用 |
什么时候用WebSocket?
✅ 适合使用WebSocket的场景:
- 在线聊天
- 实时游戏
- 股票行情
- 协同编辑
- 直播弹幕
- IoT实时数据
❌ 不适合使用WebSocket的场景:
- 静态网页
- RESTful API
- 文件下载
- 表单提交
2.3 什么是主从架构
生活中的例子
想象一个大型餐厅的厨房:
总厨师长(主服务器)|分配任务和协调|+-----+-----+-----+| | | |厨师1 厨师2 厨师3 厨师4(从服务器)| | | |菜品1 菜品2 菜品3 菜品4(处理客户请求)
- 总厨师长:不做菜,只负责分配订单给各个厨师
- 厨师们:实际做菜,每个厨师负责一部分订单
- 优势:效率高,一个厨师累了还有其他的
这就是主从架构的核心思想!
技术架构图
┌─────────────────┐│ 主服务器 ││ (Master) ││ ││ · 管理从服务器 ││ · 分配客户端 ││ · 负载均衡 ││ · 健康检查 │└────────┬────────┘│┌────────────────────┼────────────────────┐│ │ │┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐│从服务器1 │ │从服务器2 │ │从服务器3 ││(Slave 1) │ │(Slave 2) │ │(Slave 3) ││ │ │ │ │ ││·处理业务 │ │·处理业务 │ │·处理业务 ││·管理客户端│ │·管理客户端│ │·管理客户端│└────┬─────┘ └────┬─────┘ └────┬─────┘│ │ │客户端1,2,3 客户端4,5,6 客户端7,8,9
2.4 环境准备
需要的软件
-
Node.js(版本 14.0 以上)
# 检查是否安装 node --version# 如果没有安装,去官网下载:https://nodejs.org
-
代码编辑器(推荐 VS Code)
- 下载地址:https://code.visualstudio.com
-
创建项目目录
# 创建项目文件夹 mkdir websocket-master-slave cd websocket-master-slave# 初始化项目 npm init -y# 安装依赖 npm install ws
项目结构
websocket-master-slave/
├── package.json # 项目配置文件
├── master-server.js # 主服务器
├── slave-server.js # 从服务器
├── client.js # 客户端
├── config/ # 配置文件夹
│ ├── master.json # 主服务器配置
│ └── slave.json # 从服务器配置
├── logs/ # 日志文件夹
├── test/ # 测试文件夹
└── README.md # 项目说明
第二章:WebSocket基础入门
在深入主从架构之前,我们先来掌握WebSocket的基础知识。
3.1 第一个WebSocket服务器
让我们从最简单的WebSocket服务器开始:
// simple-server.js
const WebSocket = require('ws');// 创建WebSocket服务器,监听8080端口
const wss = new WebSocket.Server({ port: 8080 });console.log('WebSocket服务器启动在端口 8080');// 当有客户端连接时
wss.on('connection', function connection(ws) {console.log('新客户端连接了!');// 向客户端发送欢迎消息ws.send('欢迎连接到WebSocket服务器!');// 当收到客户端消息时ws.on('message', function incoming(message) {console.log('收到消息:', message.toString());// 回复客户端ws.send(`服务器收到了你的消息: ${message}`);});// 当客户端断开连接时ws.on('close', function close() {console.log('客户端断开连接');});// 错误处理ws.on('error', function error(err) {console.error('WebSocket错误:', err);});
});// 每5秒向所有连接的客户端发送时间
setInterval(() => {wss.clients.forEach((client) => {if (client.readyState === WebSocket.OPEN) {client.send(`当前时间: ${new Date().toLocaleTimeString()}`);}});
}, 5000);
代码解释:
new WebSocket.Server({ port: 8080 })
- 创建服务器wss.on('connection', ...)
- 监听新连接ws.on('message', ...)
- 监听消息ws.send(...)
- 发送消息wss.clients
- 所有连接的客户端
3.2 第一个WebSocket客户端
// simple-client.js
const WebSocket = require('ws');// 连接到服务器
const ws = new WebSocket('ws://localhost:8080');// 连接成功时
ws.on('open', function open() {console.log('成功连接到服务器!');// 发送消息给服务器ws.send('你好,服务器!');// 每3秒发送一次消息setInterval(() => {if (ws.readyState === WebSocket.OPEN) {ws.send(`客户端时间: ${new Date().toLocaleTimeString()}`);}}, 3000);
});// 收到服务器消息时
ws.on('message', function incoming(data) {console.log('收到服务器消息:', data.toString());
});// 连接关闭时
ws.on('close', function close() {console.log('与服务器断开连接');
});// 错误处理
ws.on('error', function error(err) {console.error('连接错误:', err);
});// 处理进程退出
process.on('SIGINT', () => {console.log('\n正在关闭连接...');ws.close();process.exit();
});
3.3 消息通信基础
消息格式设计
在实际应用中,我们通常使用JSON格式传递消息:
// 消息格式示例
const message = {type: 'chat', // 消息类型data: { // 消息数据user: '张三',text: '大家好!',time: Date.now()}
};// 发送时转换为字符串
ws.send(JSON.stringify(message));// 接收时解析JSON
ws.on('message', (data) => {const message = JSON.parse(data);console.log(`${message.data.user}: ${message.data.text}`);
});
常见消息类型
// 定义消息类型常量
const MessageTypes = {// 系统消息CONNECT: 'connect', // 连接DISCONNECT: 'disconnect', // 断开HEARTBEAT: 'heartbeat', // 心跳ERROR: 'error', // 错误// 业务消息CHAT: 'chat', // 聊天JOIN_ROOM: 'join_room', // 加入房间LEAVE_ROOM: 'leave_room', // 离开房间USER_LIST: 'user_list', // 用户列表// 主从通信REGISTER: 'register', // 注册SYNC: 'sync', // 同步BROADCAST: 'broadcast' // 广播
};
完整的消息处理示例
// message-handler.js
class MessageHandler {constructor(ws) {this.ws = ws;this.handlers = new Map();this.registerHandlers();}// 注册消息处理器registerHandlers() {this.handlers.set('chat', this.handleChat.bind(this));this.handlers.set('join_room', this.handleJoinRoom.bind(this));this.handlers.set('heartbeat', this.handleHeartbeat.bind(this));}// 处理收到的消息handleMessage(data) {try {const message = JSON.parse(data);const handler = this.handlers.get(message.type);if (handler) {handler(message.data);} else {console.log('未知消息类型:', message.type);}} catch (error) {console.error('消息解析错误:', error);}}// 处理聊天消息handleChat(data) {console.log(`[聊天] ${data.user}: ${data.text}`);// 广播给其他用户this.broadcast({type: 'chat',data: data});}// 处理加入房间handleJoinRoom(data) {console.log(`${data.user} 加入了房间 ${data.room}`);// 处理加入房间的逻辑}// 处理心跳handleHeartbeat(data) {// 回复心跳this.send({type: 'heartbeat',data: { timestamp: Date.now() }});}// 发送消息send(message) {if (this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify(message));}}// 广播消息(需要访问所有客户端)broadcast(message) {// 这里需要服务器支持}
}
第三章:主从架构设计原理
4.1 架构设计思路
为什么需要主从架构?
让我们通过一个故事来理解:
小明开了一家网络游戏公司,最初只有100个玩家,一台服务器轻松应对。随着游戏火爆,玩家增长到10000人,服务器开始卡顿。小明买了更好的服务器,但玩家继续增长到100000人,再好的单台服务器也扛不住了。
这时,小明想到:为什么不用多台服务器分担压力呢?
这就是主从架构的起源!
架构演进过程
-
单服务器阶段
所有客户端 --> 单一服务器优点:简单 缺点:性能瓶颈、单点故障
-
简单集群阶段
客户端 --> 负载均衡器 --> 多个独立服务器优点:负载分散 缺点:服务器间无法通信
-
主从架构阶段
客户端 --> 从服务器 <--> 主服务器 <--> 从服务器 <-- 客户端优点:负载分散、统一管理、服务器间可通信 缺点:架构复杂度增加
4.2 核心组件说明
1. 主服务器(Master Server)
职责:
- 🎯 从服务器的注册和管理
- ⚖️ 负载均衡(决定客户端连接哪个从服务器)
- 💓 健康检查(监控从服务器状态)
- 📢 消息路由(跨服务器消息转发)
- 📊 全局状态管理
类比:就像交通指挥中心,不直接运送乘客,但协调所有公交车的调度。
2. 从服务器(Slave Server)
职责:
- 👥 处理客户端连接
- 💼 执行具体业务逻辑
- 📨 向主服务器报告状态
- 🔄 与其他从服务器同步数据
类比:就像具体的公交车,实际运送乘客,并向调度中心报告位置。
3. 客户端(Client)
职责:
- 🔗 向主服务器请求分配
- 🎮 连接到指定的从服务器
- 📱 执行应用功能
类比:就像乘客,先问调度中心该上哪辆车,然后上车。
4.3 通信协议设计
通信流程详解
1. 从服务器启动并注册Slave --> Master: {type: "register", name: "slave-1", capacity: 100}Master --> Slave: {type: "register_success", slaveId: "s1"}2. 心跳保活Slave --> Master: {type: "heartbeat", slaveId: "s1"}Master --> Slave: {type: "heartbeat_ack"}3. 客户端请求分配Client --> Master: {type: "request_server"}Master --> Client: {type: "server_assigned", host: "192.168.1.2", port: 8081}4. 客户端连接从服务器Client --> Slave: {type: "connect", userId: "u123"}Slave --> Client: {type: "connect_success"}5. 跨服务器消息Slave1 --> Master: {type: "forward", target: "slave2", data: {...}}Master --> Slave2: {type: "forwarded", from: "slave1", data: {...}}
消息协议规范
// 基础消息结构
{id: "唯一消息ID",type: "消息类型",timestamp: "时间戳",data: {// 具体数据}
}// 主从通信消息类型
const MasterSlaveProtocol = {// 从服务器 -> 主服务器SLAVE_REGISTER: 'slave:register', // 注册SLAVE_HEARTBEAT: 'slave:heartbeat', // 心跳SLAVE_STATUS: 'slave:status', // 状态报告SLAVE_SYNC: 'slave:sync', // 数据同步// 主服务器 -> 从服务器MASTER_REGISTER_ACK: 'master:register_ack', // 注册确认MASTER_HEARTBEAT_ACK: 'master:heartbeat_ack', // 心跳确认MASTER_COMMAND: 'master:command', // 命令下发MASTER_BROADCAST: 'master:broadcast', // 广播消息// 客户端 -> 主服务器CLIENT_REQUEST: 'client:request', // 请求分配// 主服务器 -> 客户端MASTER_ASSIGN: 'master:assign', // 分配服务器MASTER_REJECT: 'master:reject' // 拒绝请求
};
第四章:主服务器详细实现
5.1 主服务器架构
主服务器是整个系统的大脑,让我们详细实现它的每个功能。
核心类设计
// master-server-core.js
class MasterServer {constructor(config) {// 配置this.config = {port: config.port || 8080,heartbeatInterval: config.heartbeatInterval || 10000,heartbeatTimeout: config.heartbeatTimeout || 30000,...config};// 核心数据结构this.slaves = new Map(); // 从服务器集合this.clients = new Map(); // 客户端分配记录this.stats = { // 统计信息totalConnections: 0,totalMessages: 0,startTime: Date.now()};// 状态this.slaveIdCounter = 0; // 从服务器ID计数器this.isRunning = false; // 运行状态}
}
5.2 从服务器管理
从服务器信息结构
class SlaveInfo {constructor(ws, registerData) {this.id = null; // 由主服务器分配this.ws = ws; // WebSocket连接this.name = registerData.name; // 从服务器名称this.host = registerData.host; // 主机地址this.port = registerData.port; // 端口号this.capacity = registerData.capacity || 100; // 容量this.currentLoad = 0; // 当前负载this.status = 'active'; // 状态this.lastHeartbeat = Date.now(); // 最后心跳时间this.metadata = registerData.metadata || {}; // 额外信息this.performance = { // 性能指标cpu: 0,memory: 0,responseTime: 0};}// 获取负载率getLoadRate() {return this.currentLoad / this.capacity;}// 是否可用isAvailable() {return this.status === 'active' && this.getLoadRate() < 0.9;}// 更新心跳updateHeartbeat() {this.lastHeartbeat = Date.now();}// 转换为客户端可见信息toClientInfo() {return {id: this.id,host: this.host,port: this.port,name: this.name};}
}
从服务器注册流程
// 在 MasterServer 类中
registerSlave(ws, data) {// 验证注册信息if (!this.validateSlaveData(data)) {this.sendToWebSocket(ws, {type: 'register_error',error: '注册信息不完整'});return;}// 检查是否重复注册const existingSlave = this.findSlaveByHostPort(data.host, data.port);if (existingSlave) {console.log(`从服务器 ${data.name} 重新连接`);// 更新现有连接existingSlave.ws = ws;existingSlave.status = 'active';existingSlave.updateHeartbeat();ws.slaveId = existingSlave.id;this.sendToWebSocket(ws, {type: 'register_success',slaveId: existingSlave.id,message: '重新连接成功'});return;}// 创建新的从服务器记录const slaveInfo = new SlaveInfo(ws, data);slaveInfo.id = ++this.slaveIdCounter;// 保存到集合this.slaves.set(slaveInfo.id, slaveInfo);ws.slaveId = slaveInfo.id;// 发送注册成功消息this.sendToWebSocket(ws, {type: 'register_success',slaveId: slaveInfo.id,config: {heartbeatInterval: this.config.heartbeatInterval}});console.log(`✅ 从服务器 ${slaveInfo.name} (ID: ${slaveInfo.id}) 注册成功`);// 通知其他从服务器this.broadcastToSlaves({type: 'slave_joined',slave: slaveInfo.toClientInfo()}, slaveInfo.id);// 触发事件this.emit('slave:registered', slaveInfo);
}// 验证从服务器数据
validateSlaveData(data) {return data.name && data.host && data.port;
}// 根据主机和端口查找从服务器
findSlaveByHostPort(host, port) {for (const slave of this.slaves.values()) {if (slave.host === host && slave.port === port) {return slave;}}return null;
}
5.3 负载均衡实现
多种负载均衡策略
// load-balancer.js
class LoadBalancer {constructor() {this.strategies = new Map();this.currentStrategy = 'least_connections';this.roundRobinIndex = 0;// 注册所有策略this.registerStrategies();}registerStrategies() {