目录
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() {