OpenResty实战之百万级WebSocket实时推送:OpenResty+Redis集群实战
摘要:本文介绍了一套支撑158万并发的WebSocket实时通信系统架构,采用OpenResty+Redis技术组合。OpenResty网关处理WebSocket协议,单节点支持5万+连接,内存消耗仅3-5KB/连接。Redis集群负责连接路由、消息分发和会话管理,通过PUB/SUB实现跨节点通信。系统采用水平扩展架构,Nginx四层负载均衡,消息延迟稳定在50ms内,服务器成本降低65%。关键实
·
某大型在线教育平台通过本方案成功支撑了峰值158万并发WebSocket连接,消息延迟稳定在50ms以内,服务器成本降低65%。本文将完整揭秘这套高并发实时通信系统的架构设计与实现细节。
一、为什么选择WebSocket+OpenResty?
1.1 实时通信技术对比
性能对比数据:
| 方案 | 连接密度 | 平均延迟 | 带宽消耗 | 开发复杂度 |
|---|---|---|---|---|
| HTTP长轮询 | 3,000 | 500ms+ | 高 | 中等 |
| SSE | 5,000 | 300ms | 中等 | 简单 |
| WebSocket | 50,000+ | 50ms | 低 | 中等 |
1.2 OpenResty的核心优势
- 连接密度:单Worker进程支持5万+并发连接
- 内存效率:每个连接仅需3-5KB内存
- 协议处理:原生支持WebSocket协议栈
- LuaJIT性能:比传统PHP/Python方案快10倍
1.3 Redis的作用
- 连接路由:存储用户ID与网关节点的映射
- 消息分发:PUB/SUB实现跨节点广播
- 会话管理:存储临时状态数据
二、百万级架构设计
2.1 整体架构图
2.2 核心组件说明
- 接入层:OpenResty处理WebSocket握手/协议解析
- 会话管理:Redis存储连接路由信息
- 消息分发:Redis PUB/SUB实现跨节点通信
- 监控系统:Prometheus+Grafana实时监控
三、OpenResty实现WebSocket网关
3.1 WebSocket握手处理
server {
listen 80;
location /push {
# 协议升级
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 会话保持
proxy_set_header Host $host;
proxy_read_timeout 3600s; # 长连接超时
# 交给Lua处理
content_by_lua_file lua/websocket.lua;
}
}
3.2 连接管理核心逻辑
-- websocket.lua
local websocket = require "resty.websocket.server"
local function handle_connection()
local wb, err = websocket:new{
timeout = 60000, -- 心跳超时
max_payload_len = 65535
}
if not wb then
ngx.log(ngx.ERR, "failed to new websocket: ", err)
return ngx.exit(444)
end
-- 注册到Redis集群
local redis = require "resty.redis"
local red = redis:new()
red:connect("redis-cluster-ip", 6379)
-- 存储连接信息:user_id -> 网关IP
local ok, err = red:hset("ws_route:" .. ngx.ctx.user_id,
"gateway", ngx.var.server_addr)
-- 心跳检测
local function heartbeat()
while true do
ngx.sleep(30) -- 30秒心跳间隔
local bytes, err = wb:send_ping()
if err then
break -- 连接已断开
end
end
end
ngx.thread.spawn(heartbeat)
-- 消息接收循环
while true do
local data, typ, err = wb:recv_frame()
if not data then
-- 清理Redis中的连接信息
red:hdel("ws_route:" .. ngx.ctx.user_id, "gateway")
break
end
-- 处理消息...
end
end
四、Redis集群消息分发
4.1 订阅频道设计
-- 消息订阅处理
local function subscribe_channel()
local red_sub = redis:new()
red_sub:connect("redis-cluster-ip", 6379)
-- 订阅用户专属频道
red_sub:subscribe("user:" .. ngx.ctx.user_id)
while true do
local res, err = red_sub:read()
if res then
-- 收到消息后推送给客户端
wb:send_text(res[3])
else
ngx.log(ngx.ERR, "subscribe failed: ", err)
end
end
end
ngx.thread.spawn(subscribe_channel)
4.2 消息投递接口
-- 业务系统调用此接口发送消息
location /send {
content_by_lua_block {
local user_id = ngx.var.arg_user_id
local message = ngx.req.get_body_data()
-- 查询用户所在网关
local red = redis:new()
red:connect("redis-cluster-ip", 6379)
local gateway = red:hget("ws_route:" .. user_id, "gateway")
if gateway and gateway ~= ngx.null then
-- 发布到用户专属频道
red:publish("user:" .. user_id, message)
else
-- 用户离线处理
store_offline_message(user_id, message)
end
ngx.say("ok")
}
}
五、集群化部署方案
5.1 水平扩展架构
5.2 负载均衡配置
# Nginx四层负载配置
stream {
upstream websocket_cluster {
server 10.0.0.1:80 weight=5;
server 10.0.0.2:80 weight=5;
server 10.0.0.3:80 weight=5;
zone tcp_servers 64k;
least_conn; # 最少连接算法
}
server {
listen 443;
proxy_pass websocket_cluster;
proxy_protocol on; # 传递客户端IP
proxy_timeout 1h; # 长连接超时
}
}
5.3 会话同步策略
-- 连接断开时清理路由
wb:set_timeout(1000) -- 设置读超时
while true do
local data, typ, err = wb:recv_frame()
if not data then
-- 删除Redis中的路由
red:hdel("ws_route:" .. ngx.ctx.user_id, "gateway")
break
end
-- 处理消息...
end
六、性能优化实战
6.1 内存控制技术
# nginx.conf优化
worker_processes auto; # 自动匹配CPU核心数
events {
worker_connections 102400; # 单个Worker连接数
use epoll;
}
http {
lua_socket_pool_size 2048; # 连接池大小
lua_shared_dict ws_shared 100m; # 共享内存
}
6.2 连接复用优化
-- Redis连接池管理
local function get_redis()
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("redis-cluster-ip", 6379)
if not ok then
return nil, err
end
-- 设置连接池参数
red:set_keepalive(10000, 500) -- 10秒空闲时间,500连接池大小
return red
end
6.3 协议压缩支持
# 启用WebSocket压缩
map $http_accept_encoding $ws_compression {
default off;
"~*permessage-deflate" on;
}
server {
listen 443 ssl;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
websocket_compression $ws_compression; # 动态启用压缩
}
七、压测数据与调优结果
7.1 测试环境
- 硬件配置:8核32G * 5节点
- 网络环境:万兆内网
- 测试工具:TSUNG + 分布式压测集群
7.2 性能指标
| 场景 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 单机连接数 | 32,000 | 58,000 | 81% |
| 10万消息延迟 | 210ms | 89ms | 2.4x |
| 集群吞吐量 | 120k/s | 280k/s | 2.3x |
7.3 资源消耗对比
| 指标 | Node.js方案 | OpenResty方案 | 节省 |
|---|---|---|---|
| 服务器数量 | 32台 | 8台 | 75% |
| 月成本 | $18,400 | $4,800 | 74% |
| 运维复杂度 | 高 | 低 | - |
八、生产环境问题排查
8.1 典型故障案例
案例1:连接闪断
- 现象:客户端每5分钟断开
- 原因:SLB空闲超时设置为300秒
- 解决:调整SLB超时为3600秒
案例2:内存泄漏
- 现象:Worker内存每小时增长2%
- 定位:Lua协程未正确退出
- 修复:增加协程超时检查
8.2 监控指标配置
# Prometheus采集配置
- job_name: 'openresty'
metrics_path: '/status'
static_configs:
- targets: ['gateway1:9145', 'gateway2:9145']
- job_name: 'redis'
static_configs:
- targets: ['redis1:9121', 'redis2:9121']
九、安全加固方案
9.1 WSS强制加密
server {
listen 443 ssl;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# HTTP自动跳转HTTPS
if ($scheme = "http") {
return 301 https://$host$request_uri;
}
}
9.2 连接频率限制
-- 基于IP的连接限制
local limit_conn = require "resty.limit.conn"
local limiter = limit_conn.new("conn_limit", 1000, 10, 0.5)
local key = ngx.var.remote_addr
local delay, err = limiter:incoming(key, true)
if not delay then
ngx.log(ngx.ERR, "limit failed: ", err)
return ngx.exit(503)
end
十、总结与展望
10.1 方案收益总结
- 性能突破:单机5万+连接,集群支持百万并发
- 成本优化:服务器资源减少75%
- 低延迟:端到端消息延迟<50ms
- 高可用:无单点故障
10.2 演进方向
- 边缘计算:就近接入降低延迟
- QUIC协议:适应弱网环境
- AI预测:基于用户行为的消息预推送
下篇预告:《亿级物联网MQTT集群:OpenResty深度优化实践》
(点击关注获取实时更新)
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)