README
¶
Instant Messaging System
中文 | English
基于微服务架构的生产级即时通讯系统,采用 DDD(领域驱动设计)+ 六边形架构,支持 100k+ 并发 WebSocket 连接
目录
功能特性
业务功能
- ✅ 单聊 / 群聊 - 支持一对一和多人群组通讯
- ✅ 联系人管理 - 好友申请、通过、删除
- ✅ 多种消息类型 - 文本 / 图片 / 文件 / 音视频
- ✅ 离线消息 - 自动存储和拉取
- ✅ 消息撤回 - 支持时间窗口内撤回
- ✅ 已读状态 - 实时同步已读位置
- ✅ 在线状态 - 批量查询用户在线状态
- ✅ 文件存储 - MinIO 对象存储,支持大文件
- ✅ 音视频通话 - WebRTC 实时通信
技术特性
- 高并发 - 单节点支持 30k+ 稳定连接,多机集群支持 100k+
- 自动扩缩容 - Kubernetes HPA 基于 CPU/内存自动调整
- 可观测 - Prometheus + pprof 全链路监控
- 安全认证 - JWT Token 认证,支持刷新
- 消息可靠性 - Kafka 消息队列 + 死信队列 + ACK 机制
- 数据一致性 - Redis Lua 脚本原子操作
- 分布式 - 支持多实例部署,全局在线路由
技术架构
技术栈
| 分类 | 技术选型 | 版本/说明 |
|---|---|---|
| 编程语言 | Go | 1.25 |
| Web 框架 | Gin | HTTP/WebSocket 服务器 |
| ORM | GORM | MySQL 对象映射 |
| 服务间通信 (同步) | gRPC + Protobuf | 高性能 RPC |
| 服务间通信 (异步) | Kafka | 消息队列,KRaft 模式 |
| 数据库 | MySQL 8.0 | 主数据库 |
| 缓存 | Redis 7.2 | 会话 / 在线状态 / 限流 |
| 对象存储 | MinIO | S3 兼容 API |
| 容器化 | Docker + Docker Compose | 本地开发 |
| 编排 | Kubernetes | 生产部署,HPA 自动扩缩容 |
| 监控 | Prometheus + Grafana | 指标采集和可视化 |
| 日志 | Zap | 结构化日志 |
| 前端 | Vue3 + Vite + TypeScript | SPA 应用 |
架构总览
┌─────────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ Web (Vue3) / Mobile / Desktop │
└────────────────────────┬────────────────────────────────────────┘
│ HTTP/WebSocket
↓
┌─────────────────────────────────────────────────────────────────┐
│ API Gateway (8080) │
│ 统一入口 / JWT 认证 / 限流 / 路由转发 │
└─────────┬───────────────────────────────────────────────────────┘
│
├──────────────────────────────────────────┐
│ │
↓ gRPC ↓ WebSocket
┌──────────────────────┐ ┌──────────────────────┐
│ 微服务层 (9080+) │ │ Delivery Service │
│ │ │ (WebSocket 网关) │
│ • Identity Service │ │ • 长连接管理 │
│ • Conversation Svc │ │ • 消息投递 │
│ • Message Service │ │ • 在线路由 │
│ • Presence Service │ │ • WebRTC 信令 │
│ • File Service │ └──────────┬───────────┘
└──────────┬───────────┘ │
│ │
└─────────────────┬───────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 数据与消息层 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ MySQL │ │ Redis │ │ Kafka │ │ MinIO │ │
│ │ :3306 │ │ :6379 │ │ :29092 │ │ :9000 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ 主存储 缓存/会话 消息队列 对象存储 │
└─────────────────────────────────────────────────────────────────┘
微服务说明
| 服务 | HTTP 端口 | gRPC 端口 | 职责 |
|---|---|---|---|
| API Gateway | 8080 | - | 统一入口网关、JWT 认证、限流、路由 |
| Identity Service | 8081 | 9080 | 用户注册登录、联系人管理(好友申请、好友列表)、用户资料管理、JWT 签发与刷新 |
| Conversation Service | - | 9081 | 单聊/群聊会话创建、成员管理和信息维护(仅 gRPC) |
| Message Service | 8083 | 9082 | 消息发送与存储、历史查询、已读管理、撤回、消息事件发布 |
| Delivery Service | 8084 | - | WebSocket 连接、消息投递、在线路由、离线消息存储 |
| Presence Service | - | 9084 | 用户上下线状态管理、在线状态查询(仅 gRPC) |
| File Service | 8085 | 9085 | 文件上传、MinIO 预签名 URL、文件元数据管理 |
项目结构
IM/
├── api/ # Proto 定义和生成代码
│ ├── proto/im/v1/ # *.proto 源文件
│ └── gen/im/v1/ # 生成的 Go 代码
│
├── services/ # 微服务
│ ├── api_gateway/ # API 网关
│ ├── identity_service/ # 身份认证
│ ├── conversation_service/ # 会话管理
│ ├── message_service/ # 消息服务
│ ├── delivery_service/ # 消息投递
│ ├── presence_service/ # 在线状态
│ └── file_service/ # 文件服务
│
├── pkg/ # 共享库
│ ├── zlog/ # 日志模块
│ ├── constants/ # 常量
│ ├── enum/ # 枚举
│ └── util/ # 工具函数
│
├── deploy/ # 部署配置
│ ├── docker-compose.dev.yml # 本地开发
│ ├── docker-compose.prod.yml.example # 生产示例
│ ├── k8s/ # Kubernetes 配置
│ │ ├── base/ # 基础配置
│ │ └── overlays/ # 环境覆盖
│ │ └── docker-desktop/ # Docker Desktop 环境
│ ├── scripts/ # 自动化脚本
│ │ ├── collect.sh # 数据采集
│ │ ├── install-metrics-server.sh # Metrics Server 安装
│ │ └── server-init.sh # 服务器初始化
│ └── sql/schema.sql # 数据库脚本
│
├── bench/ # 压测工具
│ ├── wsbench/ # WebSocket 压测工具
│ │ └── main.go # 压测实现
│ ├── scripts/ # 压测脚本
│ │ ├── bench-ws.sh # WebSocket 连接压测
│ │ └── bench-msg.sh # 消息吞吐量压测
│ └── results/ # 测试结果输出
│
├── web/ # 前端
│ └── chat-server/ # Vue3 聊天客户端
│
├── Makefile # 构建和部署命令
└── go.work # Go workspace
核心实现与技术亮点
1. Redis Lua 脚本原子递增(消息序列号)
文件位置:services/message_service/internal/adapters/out/redis/sequence_repo.go
核心代码:
luaScript := `
local seq = redis.call('HINCRBY', KEYS[1], 'max_seq', 1)
if ARGV[1] ~= '' then
redis.call('HSET', KEYS[1], 'msg_' .. ARGV[1], seq)
end
return seq
`
技术点:
- 使用 Lua 脚本保证原子性,避免并发冲突
- 单次 Redis 调用完成序列号递增和消息映射
- 支持分布式环境下的全局有序序列号
2. Timeline 写扩散缓存
文件位置:services/message_service/internal/adapters/out/redis/timeline_repo.go
核心功能:
- 使用 Redis ZSet 存储消息索引(score = 时间戳)
- 支持分页获取历史消息
- 自动过期清理(TTL)
- 批量添加消息
技术点:
- 写扩散:消息发送时写入所有接收者的 Timeline
- 读取速度快:直接从接收者自己的 Timeline 读取
- 适合读多写少场景
3. 读扩散 Inbox 收件箱
文件位置:services/message_service/internal/adapters/out/redis/inbox_repo.go
核心功能:
- 用户收件箱管理
- Lua 脚本批量获取消息
- 会话未读计数
- 已读位置追踪
技术点:
- 读扩散:消息存储在发送者侧,接收者读取时聚合
- 写入速度快:只写一份
- 适合群聊等写多读少场景
4. Kafka 死信队列 & 可靠消费
文件位置:services/delivery_service/internal/adapters/out/mq/reliable_consumer.go
核心逻辑:
type ReliableConsumer struct {
maxRetries int // 默认 3 次
retryInterval time.Duration // 默认 1 秒
dlqSuffix string // 死信队列后缀 "-dlq"
}
功能:
- 3 次重试机制 - 失败后自动重试
- 指数退避策略 - 每次重试延迟递增
- 自动转移死信队列 - 超过重试次数后移入 DLQ
- 手动确认模式 - 处理成功后才 commit offset
技术点:
- 防止消息丢失
- 隔离有问题的消息,不影响正常消费
- 支持人工介入处理死信
5. ACK 机制(消息确认)
文件位置:
- services/delivery_service/internal/adapters/out/redis/pending_ack_repo.go
- services/delivery_service/internal/application/delivery.go
核心功能:
- 待确认消息存储(Redis Hash)
- 超时重传机制(10 秒)
- 批量 ACK 支持
- 已读状态同步
技术点:
- 客户端收到消息后发送 ACK
- 服务端未收到 ACK 则重传
- 保证消息至少投递一次
6. Push-Pull 混合同步
文件位置:
- services/delivery_service/internal/adapters/out/redis/sync_state_repo.go
- services/delivery_service/internal/application/delivery.go
核心功能:
- Push(在线用户) - WebSocket 实时推送
- Pull(离线消息) - 上线后主动拉取
- 同步位置记录 - 记录用户已同步的消息位置
- 增量拉取支持 - 只拉取未读消息
技术点:
- 在线时实时投递,离线时缓存
- 上线后根据同步位置增量拉取
- 避免消息重复或丢失
7. WebSocket 服务器
文件位置:services/delivery_service/internal/adapters/in/ws/ws_server.go
核心功能:
- JWT 认证 - 连接建立时验证 Token
- 心跳检测(30 秒) - 超时断开
- 连接管理 - 维护 userId → conn 映射
- 房间广播 - 支持群聊消息
- 消息分发 - 根据在线状态路由
技术点:
- Gorilla WebSocket 库
- 连接池管理,支持高并发
- 优雅关闭,避免连接泄漏
8. 全局在线路由(多实例)
文件位置:services/delivery_service/internal/adapters/out/redis/online_user_repo.go
核心功能:
- Redis 分布式存储 - 用户在线状态
- 支持多实例部署 - 记录用户所在实例 IP/ID
- 用户所在实例查找 - 跨实例消息转发
- 自动过期清理 - 心跳超时自动下线
技术点:
- 多个 Delivery Service 实例共享在线状态
- 消息投递时先查询用户所在实例,再转发
- 支持水平扩展
9. WebRTC 信令服务
文件位置:services/delivery_service/internal/application/signaling.go
核心功能:
- Offer/Answer 交换 - 建立 P2P 连接
- ICE Candidate 转发 - NAT 穿透
- 通话状态机 - 呼叫、接听、挂断
- 超时处理(30 秒) - 无人接听自动挂断
支持的消息类型:
call_offer- 发起呼叫call_answer- 接听呼叫call_ice- ICE Candidatecall_hangup- 挂断
技术点:
- 服务端仅做信令转发,音视频流 P2P 传输
- 支持 1v1 视频通话
- 未来可扩展 SFU/MCU 支持多人通话
快速开始
前置要求
| 软件 | 版本要求 | 验证命令 |
|---|---|---|
| Go | 1.25 | go version |
| Docker | 最新版 | docker --version |
| Docker Compose | v2.0+ | docker compose version |
| kubectl (可选) | 最新版 | kubectl version |
| Make | 任意版本 | make --version |
本地开发
1. 克隆项目
git clone https://github.com/EthanQC/IM.git
cd IM
2. 下载 Go 依赖
# 使用 Go workspace 模式,一次性下载所有模块依赖
go work sync
# 或者分别进入各服务目录下载(首次运行可能需要几分钟)
for svc in api_gateway identity_service conversation_service message_service delivery_service presence_service file_service; do
echo ">>> Downloading $svc dependencies..."
(cd services/$svc && go mod download)
done
# 下载压测工具依赖
cd bench/wsbench && go mod download && cd ../..
3. 启动依赖服务
# 启动 MySQL、Redis、Kafka、MinIO
make docker-deps-up
# 验证服务状态(等待所有容器 healthy)
docker ps --format "table {{.Names}}\t{{.Status}}"
依赖服务信息:
| 服务 | 端口 | 访问地址 | 凭据 |
|---|---|---|---|
| MySQL | 3306 | localhost:3306 | root / imdev |
| Redis | 6379 | localhost:6379 | (无密码) |
| Kafka | 29092 | localhost:29092 | - |
| MinIO API | 9000 | localhost:9000 | admin / admin123 |
| MinIO Console | 9001 | http://localhost:9001 | admin / admin123 |
4. 初始化数据库
# 连接 MySQL 容器并执行 schema.sql
docker exec -i im_mysql mysql -uroot -pimdev < deploy/sql/schema.sql
5. 初始化配置文件
各服务的配置文件需要从 .example 模板复制:
# 方式一:一键初始化(推荐)
bash scripts/init-configs.sh
# 方式二:手动复制(如需自定义配置)
for svc in api_gateway identity_service conversation_service message_service delivery_service presence_service file_service; do
cp services/$svc/configs/config.dev.yaml.example services/$svc/configs/config.dev.yaml
done
# 批量替换密码(将 your_password 替换为 Docker Compose 默认密码 imdev)
# macOS:
find services -name "config.dev.yaml" -exec sed -i '' 's/your_password/imdev/g' {} \;
# Linux:
find services -name "config.dev.yaml" -exec sed -i 's/your_password/imdev/g' {} \;
配置文件说明:
| 文件 | 说明 | 是否提交 Git |
|---|---|---|
config.dev.yaml.example |
配置模板(占位符) | ✅ 提交 |
config.dev.yaml |
实际配置(含真实密码) | ❌ 不提交 |
config.prod.yaml.example |
生产环境模板 | ✅ 提交 |
config.prod.yaml |
生产环境配置 | ❌ 不提交 |
需要修改的配置项(仅当默认值不适用时):
| 配置项 | 默认值 | 说明 |
|---|---|---|
mysql.dsn |
root:imdev@tcp(127.0.0.1:3306)/im_db |
MySQL 连接串 |
redis.addr |
127.0.0.1:6379 |
Redis 地址 |
kafka.brokers |
127.0.0.1:29092 |
Kafka 地址 |
jwt.secret |
默认 32 字符 | JWT 密钥(生产环境必须修改) |
6. 启动微服务
每个服务在独立终端启动(或使用 tmux/screen):
# Terminal 1: Identity Service
cd services/identity_service && go run cmd/main.go
# Terminal 2: Conversation Service
cd services/conversation_service && go run cmd/main.go
# Terminal 3: Message Service
cd services/message_service && go run cmd/main.go
# Terminal 4: Delivery Service
cd services/delivery_service && go run cmd/main.go
# Terminal 5: Presence Service
cd services/presence_service && go run cmd/main.go
# Terminal 6: File Service
cd services/file_service && go run cmd/main.go
# Terminal 7: API Gateway (最后启动)
cd services/api_gateway && go run cmd/main.go cmd/handlers.go
7. 验证部署
# 健康检查
curl http://localhost:8080/healthz
# 返回: {"status":"ok"}
# 访问 API 文档
open http://localhost:8080/swagger
Swagger UI 使用:
- 访问 http://localhost:8080/swagger
- 测试
/api/auth/register注册用户 - 测试
/api/auth/login获取 Token - 点击右上角 "Authorize" 按钮,输入
Bearer <token> - 测试其他需要认证的接口
部署上云 / Docker 一键部署
本项目支持通过 Docker Compose 一键部署全部服务,适用于:
- 本地压测环境
- 云服务器生产部署
服务器要求(云部署)
| 配置 | 最低要求 | 推荐配置 |
|---|---|---|
| CPU | 2核 | 4核+ |
| 内存 | 4GB | 8GB+ |
| 硬盘 | 40GB SSD | 100GB SSD |
| 系统 | Ubuntu 22.04 / macOS | Linux 推荐 |
Docker 一键部署步骤
# 1. 克隆项目
git clone https://github.com/EthanQC/IM.git
cd IM
# 2. 系统调优(高并发必须)
# macOS:
sudo bash scripts/tune-macos.sh
# Linux:
sudo bash scripts/tune-server.sh
# 3. 启动完整服务栈(首次需要构建镜像,约 3-5 分钟)
cd deploy
docker compose -f docker-compose.bench.yml up -d --build
# 4. 查看服务状态(等待所有容器 healthy/running)
docker compose -f docker-compose.bench.yml ps
# 5. 初始化数据库
docker exec -i im_mysql mysql -uroot -pimdev < sql/schema.sql
# 6. 验证部署
curl http://localhost:8080/healthz
curl http://localhost:8084/stats
服务列表:
| 服务 | 容器名 | 端口 | 说明 |
|---|---|---|---|
| MySQL | im_mysql | 3306 | root / imdev |
| Redis | im_redis | 6379 | 无密码 |
| Kafka | im_kafka | 29092 | KRaft 模式 |
| MinIO | im_minio | 9000/9001 | admin / admin123 |
| API Gateway | im_gateway | 8080 | HTTP API |
| Delivery Service | im_delivery | 8084 | WebSocket |
停止/重启服务
# 停止所有服务
docker compose -f docker-compose.bench.yml down
# 停止并删除数据卷(完全重置)
docker compose -f docker-compose.bench.yml down -v
# 重启单个服务
docker compose -f docker-compose.bench.yml restart delivery-service
# 查看日志
docker compose -f docker-compose.bench.yml logs -f delivery-service
防火墙配置(云服务器)
# 开放必要端口
sudo ufw allow 8080/tcp # API Gateway
sudo ufw allow 8084/tcp # WebSocket
sudo ufw enable
域名与 HTTPS(可选)
推荐使用 Nginx 反向代理 + Let's Encrypt:
# 安装 Nginx 和 Certbot
sudo apt install nginx certbot python3-certbot-nginx
# 配置反向代理
sudo vim /etc/nginx/sites-available/im
# 添加代理配置指向 localhost:8080 和 localhost:8084
# 申请证书
sudo certbot --nginx -d your-domain.com
高并发压测
本节提供完整的压测指南,使用 Docker Compose 部署服务,配合 wsbench 工具进行压测。
压测总原则
⚠️ 重要:在开始前务必阅读
- 使用 Docker Compose 部署:所有服务通过容器运行,避免本地环境差异
- 分离测试:连接压测和消息压测分开跑,否则无法定位瓶颈
- 多轮测试:每个场景至少跑 3 轮(冷启动、热身后、调参后)
- 完整记录:每个场景记录 规模参数 + 成功率 + p95/p99 + 资源曲线 + 队列积压
- 区分瓶颈:区分"服务端瓶颈"和"压测端瓶颈"
单机连接数限制
单台机器的出站连接数受限于本地端口范围(通常 1024-65535),即约 64,000 个连接。
如需达到 100k+ 连接,需要:
- 使用 2+ 台压测机器
- 或在单机配置多个 IP 地址
硬件拓扑
┌─────────────────────────────────────────────────────────────────────────────┐
│ 局域网 (1Gbps+) │
└────────────────┬─────────────────────┬─────────────────────┬────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ Node-A (服务节点) │ │ Node-B (压测节点1) │ │ Node-C (压测节点2) │
│ Mac Mini / Linux │ │ Linux / WSL2 │ │ Linux / WSL2 │
│ │ │ │ │ │
│ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │
│ │ Docker Compose │ │ │ │ wsbench 压测工具 │ │ │ │ wsbench 压测工具 │ │
│ │ 全套微服务 + 依赖 │ │ │ │ 目标: 50k 连接 │ │ │ │ 目标: 50k 连接 │ │
│ └─────────────────────┘ │ │ └─────────────────────┘ │ │ └─────────────────────┘ │
│ IP: 192.168.x.x │ └─────────────────────────┘ └─────────────────────────┘
└─────────────────────────┘
环境准备与系统调优
⚠️ 系统调优是高并发的前提,不调优会遇到假瓶颈(FD 耗尽、端口耗尽、TIME_WAIT 堆积)
Step 1: 服务节点准备 (Docker Compose 部署)
# 1. 克隆项目
git clone https://github.com/EthanQC/IM.git && cd IM
# 2. 系统调优(必须!100k 连接需要调优)
# macOS:
sudo bash scripts/tune-macos.sh
# Linux:
sudo bash scripts/tune-server.sh
# 3. 验证调优结果
ulimit -n # 应显示 >= 1000000
sysctl kern.ipc.somaxconn 2>/dev/null || sysctl net.core.somaxconn # 应显示 >= 65535
# 4. 启动完整服务栈(Docker Compose)
cd deploy
docker compose -f docker-compose.bench.yml up -d --build
# 5. 等待所有服务 healthy(约 1-2 分钟)
docker compose -f docker-compose.bench.yml ps
# 6. 初始化数据库(如果还没有)
docker exec -i im_mysql mysql -uroot -pimdev < sql/schema.sql
# 7. 获取 IP(告知压测节点)
# macOS:
ipconfig getifaddr en0 || ipconfig getifaddr en1
# Linux:
hostname -I | awk '{print $1}'
# 8. 验证服务
curl http://localhost:8080/healthz
curl http://localhost:8084/stats # WebSocket 服务状态
Step 2: 压测节点准备 (WSL2 / Linux / macOS)
方法一:WSL2 环境配置
在 Windows PowerShell 中执行(Win+X → PowerShell):
# 创建 .wslconfig
@"
[wsl2]
memory=28GB
processors=12
swap=16GB
localhostForwarding=true
"@ | Out-File -FilePath "$env:USERPROFILE\.wslconfig" -Encoding utf8
# 重启 WSL
wsl --shutdown
进入 WSL2 后执行:
# 1. 克隆项目
git clone https://github.com/EthanQC/IM.git && cd IM
# 2. 系统调优(必须!)
sudo bash scripts/tune-bench-client.sh
# 3. 验证调优(在同一终端直接执行)
ulimit -n # 应显示 >= 500000
sysctl net.core.somaxconn # 应显示 65535
sysctl net.ipv4.ip_local_port_range # 应显示 1024 65535
# 4. 编译压测工具
cd bench/wsbench && go build -o wsbench .
# 5. 查看压测参数
./wsbench --help
方法二:macOS 压测机器
# 1. 克隆项目
git clone https://github.com/EthanQC/IM.git && cd IM
# 2. 系统调优
sudo bash scripts/tune-bench-client.sh
# 3. 验证
ulimit -n # 应显示 >= 500000
# 4. 编译压测工具
cd bench/wsbench && go build -o wsbench .
Step 3: 测试连通性
# 替换为 Node-A 的实际 IP
SERVER_IP="192.168.1.100"
ping $SERVER_IP
curl http://$SERVER_IP:8080/healthz
curl http://$SERVER_IP:8084/stats
# 快速验证 WebSocket 连接
./wsbench --target=ws://$SERVER_IP:8084/ws --conns=100 --duration=30s --ramp=5s
wsbench 参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
--target |
ws://localhost:8084/ws | WebSocket 服务器地址 |
--conns |
1000 | 目标连接数 |
--duration |
5m | 压测持续时间 |
--ramp |
1m | 爬坡时间(建立连接的时间跨度) |
--ping-interval |
30s | 心跳间隔 |
--handshake-timeout |
30s | 握手超时(高并发时需要更长) |
--max-cps |
500 | 每秒最大连接数(限速,避免瞬时压力过大) |
--retry |
3 | 连接失败重试次数 |
--retry-delay |
1s | 重试延迟(指数退避) |
--read-timeout |
2m | 读超时(防止连接假死) |
--write-timeout |
10s | 写超时 |
--read-buffer |
8192 | 读缓冲区大小 |
--write-buffer |
8192 | 写缓冲区大小 |
--mode |
connect-only | 模式:connect-only / messaging |
--msg-rate |
10 | 每连接每分钟消息数(messaging 模式) |
--output |
text | 输出格式:text / json / csv |
场景1:连接层压测
目的:验证 Go 高并发能力,测试 Delivery Service 单机能撑多少稳定连接
1.1 Connect-Only 极限连接数
测试目标:只建立 WebSocket 连接,不发消息,验证稳定维持能力
⚠️ 重要参数说明:
--max-cps=300:每秒最大建立 300 个连接,避免服务端处理不过来导致连接被拒绝--ping-interval=60s:与服务端心跳间隔一致--read-timeout=180s:与服务端 pongWait 一致
# ===== 在压测节点执行 =====
cd IM/bench/wsbench
# 设置 ulimit(每个终端都要执行)
ulimit -n 500000
# 预热测试(验证环境正常)
./wsbench --target=ws://192.168.1.100:8084/ws --conns=1000 --duration=1m --ramp=10s
# 阶梯压测(找到极限)
# 10k 连接 - 约 33 秒建立完成
./wsbench --target=ws://192.168.1.100:8084/ws --conns=10000 --duration=5m --ramp=1m --max-cps=300 --ping-interval=60s --read-timeout=180s
# 30k 连接 - 约 100 秒建立完成
./wsbench --target=ws://192.168.1.100:8084/ws --conns=30000 --duration=10m --ramp=2m --max-cps=300 --ping-interval=60s --read-timeout=180s
# 50k 连接 - 约 167 秒建立完成(单机极限)
./wsbench --target=ws://192.168.1.100:8084/ws --conns=50000 --duration=15m --ramp=5m --max-cps=300 --ping-interval=60s --read-timeout=180s
# ===== 双机联合 100k 目标 =====
# 使用一键脚本(推荐):
# Node-B 执行:
bash scripts/run-50k-bench.sh 192.168.1.100:8084
# Node-C 同时执行:
bash scripts/run-50k-bench.sh 192.168.1.100:8084
# 或手动执行(带完整参数):
# Node-B:
./wsbench \
--target=ws://192.168.1.100:8084/ws \
--conns=50000 \
--duration=30m \
--ramp=5m \
--ping-interval=60s \
--handshake-timeout=30s \
--max-cps=300 \
--retry=3 \
--retry-delay=2s \
--read-timeout=180s
# Node-C 同时执行相同命令
实时监控(在服务节点执行):
# 使用监控脚本
bash scripts/monitor-bench.sh localhost:8084
终端2: Goroutine 数量
watch -n 1 'curl -s http://localhost:8084/metrics | grep go_goroutines'
终端3: 内存使用
watch -n 1 'curl -s http://localhost:8084/metrics | grep go_memstats_heap_inuse_bytes'
终端4: 网络连接状态
watch -n 2 'netstat -an | grep 8084 | grep ESTABLISHED | wc -l'
**需要记录的指标**:
| 指标 | 说明 | 目标 |
|------|------|------|
| 成功连接数峰值 | 最大同时在线 | 100k+ |
| 稳定维持时长 | 无大规模断连 | 30min+ |
| 建连失败率 | 429/5xx/超时/reset | < 1% |
| 建连延迟 p50/p95/p99 | 握手耗时 | < 100ms |
| 断开连接数 | 压测期间意外断开 | < 1% |
| 服务端 Goroutine | curl metrics | ~2x 连接数 |
| 服务端内存 | heap_inuse_bytes | 记录曲线 |
| GC Pause | pprof 或日志 | < 10ms |
**常见问题排查**:
| 错误类型 | 可能原因 | 解决方案 |
|----------|----------|----------|
| `conn_refused` | 服务端未启动或端口未开放 | 检查服务状态和防火墙 |
| `timeout` | 握手超时 | 增加 `--handshake-timeout` |
| `fd_exhausted` | 文件描述符耗尽 | 运行 `tune-bench-client.sh` |
| `conn_reset` | 服务端资源不足或被限流 | 降低 `--max-cps` |
| `eof` | 连接被服务端关闭 | 检查服务端日志 |
**pprof 分析(压测进行时)**:
```bash
# 需要先安装 graphviz: brew install graphviz (macOS) 或 apt install graphviz (Linux)
# 内存分析
go tool pprof -http=:8000 http://localhost:8084/debug/pprof/heap
# Goroutine 分析
go tool pprof -http=:8001 http://localhost:8084/debug/pprof/goroutine
# CPU 分析(30秒采样)
go tool pprof -http=:8002 http://localhost:8084/debug/pprof/profile?seconds=30
1.2 建连速率上限 (Ramp-up)
测试目标:测试每秒能新建多少连接
# 注意:压测工具默认限制 500 conn/s,可通过 --max-cps 调整
# 测试不同连接速率
./wsbench --target=ws://192.168.1.100:8084/ws --conns=10000 --duration=2m --ramp=20s --max-cps=500 # 500 conn/s
./wsbench --target=ws://192.168.1.100:8084/ws --conns=10000 --duration=2m --ramp=10s --max-cps=1000 # 1000 conn/s
./wsbench --target=ws://192.168.1.100:8084/ws --conns=10000 --duration=2m --ramp=5s --max-cps=2000 # 2000 conn/s
./wsbench --target=ws://192.168.1.100:8084/ws --conns=10000 --duration=2m --ramp=2s --max-cps=5000 # 5000 conn/s
记录:不同 --max-cps 下的失败率拐点
1.3 心跳与空闲连接稳定性
测试目标:长连接稳定保活,不是"连上就算"
# 维持 50k 连接 30 分钟,观察心跳
./wsbench \
--target=ws://192.168.1.100:8084/ws \
--conns=50000 \
--duration=30m \
--ramp=5m \
--ping-interval=45s \
--read-timeout=120s
需要记录:
- Ping/Pong 成功率(应接近 100%)
- 超时断开数
- 最终连接数 vs 初始连接数
1.4 广播下行压力
测试目标:服务端向所有连接推送小消息的能力
# 10k 连接,每连接每分钟 1 条消息(messaging 模式)
./wsbench \
--target=ws://192.168.1.100:8084/ws \
--conns=10000 \
--duration=5m \
--ramp=1m \
--mode=messaging \
--msg-rate=1 \
--payload-size=100
# 更高压力:每连接每分钟 5 条消息
./wsbench \
--target=ws://192.168.1.100:8084/ws \
--conns=10000 \
--duration=5m \
--ramp=1m \
--mode=messaging \
--msg-rate=5 \
--payload-size=100
场景2:消息链路压测
目的:测试 IM 核心能力 —— 消息发送与接收
2.1 单聊吞吐与端到端延迟
测试目标:消息从发送到对端收到的完整链路
# 基础:5k 连接,每连接每分钟 1 条消息
./wsbench \
--target=ws://192.168.1.100:8084/ws \
--conns=5000 \
--duration=5m \
--ramp=1m \
--mode=messaging \
--msg-rate=1 \
--payload-size=100
# 中等:10k 连接,每连接每分钟 5 条消息
./wsbench \
--target=ws://192.168.1.100:8084/ws \
--conns=10000 \
--duration=5m \
--ramp=2m \
--mode=messaging \
--msg-rate=5 \
--payload-size=100
# 高压:20k 连接,每连接每分钟 10 条消息
./wsbench \
--target=ws://192.168.1.100:8084/ws \
--conns=20000 \
--duration=5m \
--ramp=3m \
--mode=messaging \
--msg-rate=10 \
--payload-size=100
需要记录的指标:
| 指标 | 说明 | 目标 |
|---|---|---|
| msg/s 成功发送 | 发送吞吐 | 100k+ |
| msg/s 成功接收 | 投递吞吐 | 接近发送 |
| 端到端 RTT p50/p95/p99 | 消息延迟 | < 50ms |
| 丢消息率 | seq 校验 | 0% |
| 重复率 | msg_id 校验 | 0% |
| Kafka lag | consumer 积压 | < 1000 |
| DB 写入延迟 | 慢查询日志 | < 10ms |
Kafka 监控:
# 查看 consumer lag(需要 kafka 客户端)
docker exec im_kafka kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group im-delivery
2.2 小群聊 Fanout
测试目标:群消息写扩散能力
# 需要先通过 API 创建群,然后压测群消息
# 这里假设群 ID 为 "group_100",有 100 个成员
# 模拟群聊:100 人群,10 人同时发言
# 需要定制 wsbench 或使用脚本调用 API
2.3 顺序一致性测试
测试目标:证明 Kafka 分区键保证局部有序
# 同一会话高并发发消息,接收端校验 seq 单调递增
# 需要 wsbench 增加 seq 校验功能,或手动测试:
# 1. 多个客户端同时向同一会话发消息
# 2. 接收端记录所有消息的 seq
# 3. 验证 seq 严格递增,无乱序无缺失
场景3:在线状态与重连
3.1 重连风暴测试
测试目标:模拟网络抖动,全员重连时的恢复能力
# 1. 建立 30k 稳定连接
./wsbench -target=ws://192.168.1.100:8084/ws -conns=30000 -duration=10m -ramp=2m
# 2. 压测进行中,模拟断网(在压测机执行)
# 方法:暂停 wsbench 进程 10 秒
kill -STOP $(pgrep wsbench)
sleep 10
kill -CONT $(pgrep wsbench)
# 观察 wsbench 的重连行为和服务端的恢复
需要记录:
- 重连成功率
- 恢复到稳态耗时
- 重连期间消息丢失率
- 服务端 CPU 峰值
3.2 离线补拉测试
测试目标:验证 Last_Ack_Seq 增量拉取
# 手动测试流程:
# 1. 用户 A 在线,用户 B 发送 100 条消息
# 2. A 断线 1 分钟
# 3. 期间 B 继续发送 50 条消息
# 4. A 重连,触发补拉
# 5. 验证 A 收到完整 50 条,无缺失无重复
# 需要通过 API 或定制客户端测试
3.3 Presence 热点压力
测试目标:在线状态高频写入
# 大量用户频繁上下线
# 可通过快速建连-断开-建连模拟
./wsbench -target=ws://192.168.1.100:8084/ws -conns=5000 -duration=30s -ramp=5s
# 脚本循环执行,观察 Redis 写入压力
场景4:系统稳定性测试
4.1 长稳 Soak 测试
测试目标:证明无内存泄漏、无 goroutine 爆炸
# 中等负载长时间运行
# 30k 连接,每连接每分钟 1 条消息,持续 4 小时
./wsbench \
--target=ws://192.168.1.100:8084/ws \
--conns=30000 \
--duration=4h \
--ramp=10m \
--mode=messaging \
--msg-rate=1 \
--ping-interval=45s \
--read-timeout=120s
# 同时持续监控服务端(新开终端)
while true; do
echo "$(date): conns=$(curl -s http://localhost:8084/stats | grep -o '"total_connections":[0-9]*' | cut -d':' -f2) goroutines=$(curl -s http://localhost:8084/metrics | grep '^go_goroutines ' | awk '{print $2}') heap=$(curl -s http://localhost:8084/metrics | grep '^go_memstats_heap_inuse_bytes ' | awk '{print $2}')"
sleep 60
done | tee soak_metrics.log
需要记录:
- 内存曲线(应稳定,不应持续上升)
- goroutine 数量(应稳定)
- GC 频率和耗时
- 错误率随时间变化(应稳定)
4.2 背压与过载保护
测试目标:系统到瓶颈时优雅降级,不雪崩
# 逐步提高消息速率,直到触发限流
./wsbench --target=ws://192.168.1.100:8084/ws --conns=10000 --duration=3m --ramp=1m --mode=messaging --msg-rate=10
./wsbench --target=ws://192.168.1.100:8084/ws --conns=10000 --duration=3m --ramp=1m --mode=messaging --msg-rate=20
./wsbench --target=ws://192.168.1.100:8084/ws --conns=10000 --duration=3m --ramp=1m --mode=messaging --msg-rate=50
观察:
- 延迟是"缓慢上升"还是"突然爆炸"
- 是否有 429 限流响应
- 错误码分布
4.3 故障注入测试
测试目标:验证可靠性设计
# 1. Kafka 短暂不可用
docker stop im_kafka && sleep 30 && docker start im_kafka
# 观察:消息是否丢失、恢复时间、积压消化速度
# 2. Redis 重启
docker restart im_redis
# 观察:在线状态是否恢复、会话数据是否正常
# 3. MySQL 慢查询模拟
# 在 MySQL 执行: SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 0.001;
# 观察日志中的慢查询
压测结果汇总
完成以上测试后,你应该能产出以下数据(可直接写入简历):
核心指标模板
| 指标 | 测试值 | 目标 | 状态 |
|---|---|---|---|
| 最大稳定 WS 连接数 | _____ | 100k+ | [ ] |
| 连接稳定维持时长 | _____min | 30min+ | [ ] |
| 建连速率上限 | _____conn/s | 5k+/s | [ ] |
| 单聊吞吐 | _____msg/s | 100k+ | [ ] |
| 端到端延迟 p95 | _____ms | < 50ms | [ ] |
| 端到端延迟 p99 | _____ms | < 100ms | [ ] |
| 消息丢失率 | _____% | 0% | [ ] |
| 重连风暴恢复时间 | _____s | < 30s | [ ] |
| 离线补拉正确率 | _____% | 100% | [ ] |
| 长稳 4h 内存稳定 | 是/否 | 是 | [ ] |
简历可写指标
保守写法(基于实测数据):
支持 30,000+ 并发 WebSocket 连接,消息吞吐 100,000 msg/s,端到端延迟 < 50ms (P95)
进阶写法(需完整跑完上述测试):
单集群支持 80,000+ 并发长连接,消息吞吐 300,000 msg/s,实现重连风暴 30 秒内恢复
极限写法(三机满载):
分布式 IM 系统支持 100,000+ 并发连接,消息吞吐 500,000+ msg/s,通过 4 小时 Soak 测试无内存泄漏
监控与调试命令速查
# ===== 服务端 (Node-A) =====
# 一键监控脚本(推荐)
bash scripts/monitor-bench.sh localhost:8084
# 手动监控命令
# 实时连接统计
watch -n 1 'curl -s http://localhost:8084/stats'
# Goroutine 数量(每个连接约 2 个)
watch -n 1 'curl -s http://localhost:8084/metrics | grep "^go_goroutines "'
# 内存使用
watch -n 1 'curl -s http://localhost:8084/metrics | grep "^go_memstats_heap_inuse_bytes "'
# 网络连接状态
watch -n 2 'netstat -an | grep 8084 | grep ESTABLISHED | wc -l'
# pprof 分析
go tool pprof http://localhost:8084/debug/pprof/heap
go tool pprof http://localhost:8084/debug/pprof/goroutine
go tool pprof -http=:8000 http://localhost:8084/debug/pprof/profile?seconds=30
# ===== 压测端 (Node-B/C) =====
# 确认 ulimit
ulimit -n # 应 >= 500000
# 连接状态
ss -s | grep estab
netstat -an | grep ESTABLISHED | wc -l
# 端口使用情况
# Linux:
cat /proc/sys/net/ipv4/ip_local_port_range
# macOS:
sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
# 查看压测错误分布
./wsbench --target=ws://192.168.1.100:8084/ws --conns=1000 --duration=1m --verbose
新增脚本说明
| 脚本 | 位置 | 用途 |
|---|---|---|
tune-server.sh |
scripts/ | 服务端系统调优(支持 100k+ 连接) |
tune-bench-client.sh |
scripts/ | 压测客户端系统调优 |
monitor-bench.sh |
scripts/ | 实时监控压测状态 |
run-50k-bench.sh |
scripts/ | 一键运行 50k 连接压测 |
常见问题
压测相关问题
Q: 压测时大量连接建立失败?
-
检查系统调优:
# 压测机器 ulimit -n # 应 >= 500000 # 服务端 ulimit -n # 应 >= 1000000 -
降低连接速率:
# 使用 --max-cps 限制每秒连接数 ./wsbench --max-cps=300 ... -
增加握手超时:
./wsbench --handshake-timeout=60s ...
Q: 压测时连接中途大量断开?
-
检查心跳配置:
# 使用更长的心跳间隔和读超时 ./wsbench --ping-interval=45s --read-timeout=120s ... -
检查服务端资源:
# 监控 Goroutine 和内存 curl http://localhost:8084/metrics | grep go_goroutines curl http://localhost:8084/metrics | grep go_memstats_heap_inuse_bytes -
查看错误分布:
./wsbench --verbose ... # 查看详细错误
Q: 每台机器最多能建立多少连接?
受限于本地端口数量,单台机器最多约 64,000 个出站连接(端口范围 1024-65535)。
如需超过 64k 连接,需要:
- 配置多个本地 IP 地址
- 或使用多台压测机器
Q: 服务端能支持多少连接?
理论上 Go 单机可支持百万级连接,实际受限于:
- 内存:每个连接约 10-20KB
- CPU:心跳处理和消息分发
- 文件描述符:需要
ulimit -n足够大
Mac Mini M4 16GB 实测可稳定支持 30k+ 连接,通过双压测机器可达 100k+。
环境配置问题
Q: 端口被占用怎么办?
# 查看占用端口的进程
lsof -i :8080
# 杀死进程
kill -9 <PID>
Q: MySQL 连接失败?
等待 MySQL 完全启动(约 30 秒):
docker logs im_mysql # 查看日志
Q: Swagger 页面打不开?
确保是从 services/api_gateway/cmd 目录启动的:
cd services/api_gateway/cmd
go run main.go handlers.go -config ../configs/config.dev.yaml
Q: 如何完全重置环境?
# 停止所有容器并删除数据
cd deploy && docker compose -f docker-compose.dev.yml down -v
# 重新启动
docker compose -f docker-compose.dev.yml up -d
许可证
MIT License
⭐ 如果这个项目对你有帮助,请给一个 Star~ 谢谢~
前端(web/chat-server)
技术栈
- Vue 3 + TypeScript
- Vite 5
- Pinia + Vue Router
- Axios
目录
- 前端工程路径:
web/chat-server
本地启动(前后端联调)
- 启动后端(Docker,dev 环境)
cd deploy
docker compose -f docker-compose.bench.yml up -d --build
- 启动前端
cd web/chat-server
npm install
npm run dev
- 打开页面
前端环境变量
可参考 web/chat-server/.env.example,默认即可本地联调:
VITE_API_BASE_URL=/apiVITE_WS_BASE_URL=/wsVITE_API_PROXY_TARGET=http://127.0.0.1:8080VITE_WS_PROXY_TARGET=ws://127.0.0.1:8084
可用命令
cd web/chat-server
npm run dev
npm run typecheck
npm run build
npm run preview
功能覆盖说明
当前前端已接入并验证以下后端能力:
- 注册、登录、Token 刷新、个人资料查询/更新
- 联系人申请、处理、列表、删除
- 会话创建、列表、详情、更新
- 消息发送、历史拉取、已读上报、撤回
- 在线状态查询
- 文件上传(创建上传票据、直传对象存储、完成上传)
- WebSocket 实时消息、ACK、已读事件、撤回事件
- 音视频信令流程(呼叫、接听、拒绝、挂断、offer/answer/ice candidate)
说明
- 本地 Docker dev 配置下,文件服务使用
host.docker.internal:9000生成预签名上传地址,确保浏览器可直接上传。