简介:开源版在线客服管理系统源码包,面向需要快速搭建客户服务通道的中小企业、技术团队及独立开发者。系统以网页/移动端/社交媒体多渠道接入为核心,提供客服工作台、AI聊天机器人自动应答、客户信息管理、工单处理、数据报表分析、CRM/ERP集成、多语言切换与隐私合规保护等完整能力,源代码开放透明,便于按业务场景深度定制与二次开发。压缩包体积约7.78MB,随包内容以系统核心源码、前后端工程文件及配置资源为主,便于直接导入开发环境进行部署、调试与二次构建。目前已有468人学习下载,适合作为轻量级客服平台建设的基础版本,也可作为学习企业级Web应用架构、实时通信与工单流程设计的参考范例。借助活跃的开源社区,后续可持续获取功能补丁与优化建议,帮助企业以较低成本提升服务效率和客户满意度。
1. 开源版在线客服管理系统,这个压缩包里到底装了什么
某电商团队为了省下每年几千块的第三方客服软件年费,从网上找了一套开源版在线客服管理系统的源码包,解压、导库、启动一气呵成,结果访客端能正常发消息,坐席后台却一条都收不到。这类问题在自部署在线客服系统的第一周几乎人人都会遇到。这个 zip 里装的不是一个聊天插件,而是一整套包含访客聊天窗口、坐席工作台、管理后台以及长连接网关的服务端源码,目标是把“客服聊天”完整留在自己的服务器上。适合手里有云服务器、懂基本 Linux 操作,想私有化部署或二次开发的团队。接下来我按架构、部署、参数、避坑、强化这条线,把这套系统一次讲透。
2. 先拆系统:在线客服管理系统的消息链路与模块边界
2.1 一条消息从访客发出到坐席收到,经过哪几层
在线客服管理系统的核心不是那套好看的管理界面,而是那条从访客浏览器到坐席浏览器的实时消息通道。常见的开源方案会把它拆成五个部分:访客端(一段可嵌入网页的聊天窗口)、坐席端(客服登录的工作台页面)、管理后台(设置分配策略、查看统计)、业务 API 服务(处理登录、创建会话、拉取历史消息)、长连接网关(维护 WebSocket 连接、转发消息)。这五个部分可以部署在一台机器上,也可以拆开部署,但消息链路必须清晰。
访客发一条消息的实际路径是:访客在聊天窗口输入内容,前端先把消息通过 HTTP 接口提交给业务服务完成入库,同时建立或复用一条到网关的 WebSocket 连接;网关把消息路由到负责该坐席的 Worker 进程,再推给坐席端;坐席回复时走同一条链路反向回去。HTTP 层负责“存”,WebSocket 层负责“推”,两者职责不能混。很多新手部署完发现消息发不出去,就是只启动了 Web 服务,忘了长连接网关根本没起来。
访客端发送消息的典型逻辑,以常见的前端实现为例:
// 访客端发送消息:先入库,再走长连接推送 async function sendMessage(text) { // 1. 先通过业务API创建/获取会话ID(visitor_id 来自 cookie,每次访问保持不变) const session = await fetch('/api/visitor/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ visitor_id: getVisitorId() }) }).then(res => res.json()); // 2. 消息内容提交给业务服务,落库拿到 message_id const saved = await fetch('/api/visitor/message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: session.id, content: text }) }).then(res => res.json()); // 3. 通过WebSocket把消息推给网关,网关负责路由到坐席 ws.send(JSON.stringify({ type: 'chat.message', message_id: saved.message_id, session_id: session.id, content: text })); }这段代码说明了在线客服系统消息发送的“双通道”设计:HTTP 通道保证消息一定落库,WebSocket 通道保证消息能实时到达坐席端。visitor_id是访客身份的唯一标识,通常存在 cookie 里,刷新页面不能变;session_id是一次对话的会话标识,从创建会话接口返回。第三步如果 WS 发送失败,常见降级方案是把消息标记为“待推送”,等坐席上线后主动拉取,避免消息丢失。
网关内部的路由逻辑也有讲究。长连接服务一般会启动多个 Worker 进程,比如 4 个进程分别处理不同访客的连接。网关收到一条消息后,会根据session_id或agent_id计算通道号,把消息投递到对应坐席所在的 Worker 进程,再由该进程推给坐席端的 WebSocket 连接。这个路由用 Redis 发布订阅(PUB/SUB)或者进程间通讯都能实现,但 Redis 方案在网关做多节点扩展时更省事,这也是多数开源在线客服系统默认走 Redis 通道的原因。
2.2 会话分配、坐席上下线与消息持久化:三个核心模块的选型逻辑
会话分配解决的是“访客来了,谁来接待”的问题。常见有三种策略:轮询分配、最少会话数优先、空闲优先。轮询最容易实现,但会出现一种尴尬场景:A 坐席手上已经积了 10 个会话,B 坐席闲得发呆,新访客还是被轮流分给 A。最少会话数优先会实时看每个坐席当前的会话负载,把新访客分给负载最低的人,更适合客服工作量不均的团队。空闲优先则只看在线/离线状态,适合只有一两个坐席、不在意负载的小站点。
坐席上下线感知不能只靠登录和退出事件。客服可能直接关了浏览器,或者网络瞬间断开,服务端根本收不到“下线通知”。所以主流做法是双保险:坐席端每 30 秒发一次心跳包,网关收到后刷新该坐席的在线标记;同时给 Redis 里的在线状态设一个过期时间(比如 90 秒),心跳断了的坐席自动掉线。这套机制比单纯依赖登录事件可靠得多,也是在线客服系统区别于普通聊天软件的关键设计。
消息持久化必须分层,不能一律塞进 MySQL。在线客服系统的典型存储方案是:聊天记录最终落 MySQL,方便管理后台按条件检索;最近几小时的“热消息”和未读消息放在 Redis,保证实时推送快。Redis 里常见的存储结构长这样:
# 在线坐席状态:SET 类型,存放当前在线的坐席ID SADD chat:online:agents 1001 1002 1003 # 每个坐席当前会话数:HASH 类型,字段是坐席ID,值是会话数 HSET chat:agent:load 1001 2 1002 5 1003 1 # 待分配的访客会话:LIST 类型,FIFO 队列 LPUSH chat:pending:session 800123 800124坐席上下线感知、会话负载统计、待分配队列这三类数据用 Redis 实现,理由很直接:这些数据的特征是“读写频繁、单条体积小、需要原子操作”。如果放在 MySQL 里,每次心跳都要 UPDATE 一次在线状态,坐席一多就会把数据库拖垮。Redis 的 SADD/HSET 天然支持原子更新,配合过期时间和发布订阅,正好覆盖在线客服系统的全部实时需求。agent:load这个 Hash 是分配策略的核心数据来源,最少会话数策略就是读取它再做排序。
选型上还有最后一个关键点:长连接网关用什么技术栈实现。PHP 生态的方案多基于 Workerman 或 Swoole,Java 生态多用 Netty。对于大多数中小团队,PHP + Workerman 的开源方案部署成本最低,扩展性也够用——一台 2 核 4G 的服务器撑几百个并发连接问题不大,超过这个规模再考虑换 Java 或 Go 重写网关。拿到源码包后先确认网关目录用的什么框架,这决定了你后续的排查路径。
3. 本地跑通:从解压到双端连通的完整部署路径
3.1 环境准备与目录结构:先分清网关、坐席端、管理端
部署前先做环境体检。在线客服系统对服务器要求不高,但 PHP 扩展缺一不可,pcntl 和 posix 是长连接守护进程的基石,没有它们网关根本起不来;redis 扩展负责连接 Redis;pdo_mysql 负责连数据库。用下面一组命令快速检查:
# 检查PHP版本与关键扩展(长连接服务依赖 pcntl/posix,缺了网关无法启动) php -v php -m | grep -E 'pcntl|posix|pdo_mysql|redis' # 缺少 redis 扩展时安装(以 PHP 7.4 为例,具体版本看你服务器) pecl install redis echo "extension=redis.so" >> /usr/local/etc/php/conf.d/redis.ini # 检查端口占用,在线客服系统一般用到 8080(Web)和 8083(WebSocket) ss -lntp | grep -E '8080|8083'这里最关键的是php -m | grep pcntl,我第一次部署时没查这个,网关一直报 “Function pcntl_fork not exists”,折腾了半小时才发现是编译 PHP 时没开 pcntl 扩展。如果用的是宝塔这类面板,在 PHP 扩展管理里直接启用即可。端口检查也值得做,很多云厂商的安全组默认不放行 8083,访客端 WebSocket 连不上,界面看起来就是“消息一直发送中”。
目录结构每个包略有差异,但大体上会包含这几块:application或app放业务控制器(里面通常有admin、agent、visitor三个子目录,对应管理端、坐席端、访客端),config放数据库和 Redis 配置,public是 Web 入口目录,gateway或server放长连接启动脚本,database放 SQL 初始化文件。拿到包先别急着改代码,把这几个目录和它们的职责对应上,后面排查问题时能少走很多弯路。
3.2 数据库初始化与配置文件:IP、端口、密钥一次填对
数据库初始化一般分两步:建库导 SQL,改配置文件。SQL 文件通常在database目录下,文件名类似install.sql或kefu.sql。执行前务必确认库里没有重名表,否则会导入失败。建表语句里会包含坐席表、访客表、会话表、消息表、工单表等,一套完整的在线客服系统一般有 20 张以上的表,导入时要耐心等它跑完。
# 创建数据库并导入初始表结构 mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS kefu DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -uroot -p kefu < database/install.sql # 导入后可快速确认表数量,正常应该在 20 张以上 mysql -uroot -p kefu -e "SHOW TABLES;"字符集必须用 utf8mb4,不能用 utf8。访客消息里常带 emoji 表情,utf8 只能存三个字节,遇到 emoji 会直接报错或者存成乱码。utf8mb4是四字节编码,才能完整覆盖这些字符。导入完成后打开配置文件,常见位置是.env或config/database.php,把数据库连接信息和 Redis 连接信息一次填对:
# .env 配置模板(具体变量名以包内说明为准) DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=kefu DB_USER=kefu_user DB_PASS=your_password REDIS_HOST=127.0.0.1 REDIS_PORT=6379 # REDIS_PASS= # 如果 Redis 设置了密码,必须填,否则网关连不上 # WebSocket 网关监听地址 WS_HOST=0.0.0.0 WS_PORT=8083这里最容易埋雷的是 Redis 密码。本地开发的 Redis 一般不设密码,但线上部署时大家都会设,设了之后如果配置文件里忘了填REDIS_PASS,你会看到网关进程正常、坐席端也登录正常,但消息就是推不过去,因为网关连 Redis 认证失败后静默重试,不会报错。第一次部署建议先用redis-cli ping确认 Redis 能连通,再启动网关。
3.3 启动长连接网关和 Web 服务:最小命令组合
网关启动是整个部署的分水岭。常见的长连接服务基于 Workerman 实现,启动方式一般是执行一个start.php脚本。正常启动后终端会打印监听端口和 Worker 进程数。这里强烈建议先以前台方式启动看日志,确认无误后再转后台守护模式。
# 前台启动网关,观察启动日志(出现 start success 说明监听成功) php gateway/bin/start.php start # 确认无误后以守护进程方式启动 php gateway/bin/start.php start -d # 重启和停止 php gateway/bin/start.php restart php gateway/bin/start.php stopWeb 服务如果用的是 Nginx,需要配置两个 location:一个常规反向代理到 PHP-FPM 处理业务接口,一个专门的 WebSocket 反向代理到网关端口。WebSocket 反向代理是整个部署里最容易出错的地方,Nginx 必须带上 Upgrade 头才能完成从 HTTP 到 WebSocket 的协议升级:
map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # WebSocket 路径反向代理,必须带 Upgrade 头,否则握手失败 location /ws { proxy_pass http://127.0.0.1:8083; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 3600s; } }proxy_read_timeout 3600s这一行常被忽略,Nginx 默认 60 秒没有数据就断开连接,而 WebSocket 连接是长连接,心跳间隔可能超过 60 秒,不调大超时时间的话坐席端会频繁掉线。/ws这个路径要和源码里访客端、坐席端 JS 代码里配置的连接路径保持一致,不一致的话浏览器控制台会直接报 404。
3.4 联调验证:访客发消息、坐席回消息的完整判定方法
网关起来了,服务也通了,接下来用最小链路验证整套系统能不能正常收发消息。不要直接打开浏览器点来点去,那样出了问题不知道是哪一层。建议按下面三步走,每一层单独验证:
# 1. 模拟访客创建会话 curl -X POST http://127.0.0.1:8080/api/visitor/session \ -H "Content-Type: application/json" \ -d '{"visitor_id":"test-visitor-001"}' # 2. 模拟访客发送消息(这一步会写入数据库) curl -X POST http://127.0.0.1:8080/api/visitor/message \ -H "Content-Type: application/json" \ -d '{"session_id":"SESSION_ID_FROM_STEP1","content":"你好,想咨询一下售后问题"}' # 3. 验证数据库里是否落库 mysql -uroot -p kefu -e "SELECT id, session_id, content, create_time FROM chat_message ORDER BY id DESC LIMIT 5;" # 4. 用 websocat 连接 WebSocket 网关,看消息是否广播到坐席端 websocat ws://127.0.0.1:8083/ws?token=YOUR_TOKEN每步验证的目的要弄清楚。第一步拿到session_id说明业务 API 正常;第二步成功说明 HTTP 消息接口正常;第三步查库发现有记录,说明数据库配置正确;第四步如果 websocat 连接到网关后能收到坐席端推送的消息,说明长连接链路通。前三步没问题但第四步收不到,问题基本锁定在 Redis 频道名或网关路由配置上。
这一步走通后,再打开浏览器登录坐席端,把访客窗口和坐席窗口并排放在一起手动测一遍完整对话。我习惯在联调时把浏览器开发者工具的网络面板开着,看 WebSocket 的 Frame 里有没有消息来回。坐席端工作台的连接状态图标是一个很好的参考指标——大多数开源系统里有个“连接中/已断开”的指示,如果它显示已连接,那实时消息通道基本就是通的。
4. 关键参数:分配策略、离线消息、心跳阈值怎么调
4.1 会话分配策略:轮询、最少会话数、空闲优先三种模式的切换
在线客服系统的管理后台一般都会提供“分配方式”这个配置项。它的作用是在访客发起新会话时,决定把会话交给哪个坐席。三种策略对应不同的团队规模和业务形态,选错了最直接的后果是某些坐席忙不过来、某些坐席闲着,客诉响应时间被拉长。
// config/kefu.php(常见开源方案的分配配置,变量名以实际包为准) return [ // assign_mode 可选值:round_robin 轮询 / least_load 最少会话数 / free_first 空闲优先 'assign_mode' => 'least_load', // 访客排队超过该秒数后,允许重新分配给其他坐席 'requeue_timeout' => 30, // 单个坐席最大会话数,超过后不再分配新会话 'max_conversation_per_agent' => 8, // 分配时排除的坐席分组ID列表,常用于排除已满负荷的专席 'exclude_group_ids' => [], ];assign_mode是最核心的参数。round_robin实现简单,但会出现典型的“旱的旱死、涝的涝死”局面;least_load能根据chat:agent:load这个 Hash 动态判断负载,适合坐席人数多、工作量波动大的团队;free_first适合只有一两个坐席的小站点,只要在线就分配,不关心负载。max_conversation_per_agent也要重点调,默认值 8 对一般售前咨询够用,如果是售后技术支持类业务,一个坐席同时处理 8 个深度会话会非常吃力,建议调到 5。
4.2 Redis 里的在线状态与离线消息:过期时间别乱调
在线客服系统对 Redis 的依赖比想象中重得多。除了在线状态和会话负载,离线消息也临时存在 Redis 里,等坐席上线后补推。这里给出一份 Redis key 的作用和过期时间建议,排查问题时可以直接对照。
| Key 模式 | 类型 | 作用 | 建议过期时间 |
|---|---|---|---|
chat:online:{agent_id} | String | 标记坐席在线状态 | 由心跳续期,不主动淘汰 |
chat:agent:load | Hash | 记录每个坐席当前会话数 | 不设过期,坐席离线时清理 |
chat:offline:{agent_id} | List | 坐席离线期间收到的消息 | 建议 7 天 |
chat:session:{session_id} | Hash | 会话元信息 | 建议 24 小时后转 MySQL |
离线消息的过期时间是最容易被改坏的地方。有人觉得离线消息占内存,把 7 天改成 1 小时,结果坐席早上上班一看,夜里用户留的言全没了,只能被投诉。离线消息是客诉证据,默认不要低于 3 天。chat:online:{agent_id}的过期时间要和心跳间隔联动:心跳 30 秒一次,过期时间设 90 秒,允许漏掉 2 次心跳才算掉线,这样能避免网络抖动导致的误判。
# 检查某个坐席是否在线 EXISTS chat:online:1001 # 查看在线坐席总数 SCARD chat:online:agents # 查看某个坐席的离线消息积压量 LLEN chat:offline:1001 # 手动清理异常 key(谨慎使用,清掉会导致坐席状态异常) DEL chat:online:1001这套 key 结构在排查问题时非常有用。比如坐席反馈“明明在线,但系统不给我分配新会话”,可以先查chat:online:1001是否存在,再查chat:agent:load里这个坐席的会话数是不是已经到了max_conversation_per_agent的上限。多数时候是负载满了而不是在线状态丢了。
4.3 心跳参数:坐席明明在线却被判定离线的元凶
坐席端和网关之间通过 WebSocket 维持长连接,心跳机制是判断连接是否存活的标准手段。坐席端每隔固定时间发一个 ping 帧,网关收到后回 pong 帧并刷新在线状态。如果连续多次没收到心跳,网关就判定这个坐席离线,消息会转到离线队列。心跳参数如果设置不当,会出现“明明看着在线,消息却发不过去”的灵异现象。
// 网关心跳配置(常见参数,具体字段以包内实现为准) 'heartbeat' => [ // 服务端主动 ping 的间隔,单位秒 'ping_interval' => 30, // 超过该秒数未收到客户端心跳,判定离线 'ping_timeout' => 90, // 是否对空闲连接关闭(设置为 false 避免误杀) 'close_idle' => false, ],内网环境网络稳定,ping_interval可以收紧到 20 秒,ping_timeout设 60 秒,坐席掉线后能更快被感知到。但如果坐席分布在不同城市,走公网连接,网络抖动是常态,ping_timeout建议放宽到 90 秒甚至 120 秒。close_idle这个参数要保持默认的 false,如果设成 true,坐席端只要一会儿没发消息就会被服务端断开连接,造成频繁掉线。
这里有个排查经验:如果坐席端频繁显示“连接断开-重连中”,先看 Nginx 的proxy_read_timeout是不是改大了,再看心跳参数。前者是 WebSocket 连接被 Nginx 切断,后者是服务端主动判定离线,两者症状相似但解决路径完全不同。看网关日志里的断开原因,比瞎调参数靠谱得多。
5. 避坑指南:在线客服系统部署中的 5 个高频翻车点
5.1 访客端连不上 WebSocket 网关:先查 Nginx 的 Upgrade 头,再查安全组
现象:访客打开聊天窗,输入消息点发送,消息一直显示“发送中”,或者过一会儿提示“连接失败”。
原因:九成是两层问题叠加。一层是 Nginx 反代 WebSocket 时没有配置Upgrade和Connection头,导致 HTTP 升级到 WebSocket 的握手失败;另一层是云服务器安全组没放行 WebSocket 端口,连接直接被防火墙丢弃。
解决:先确认安全组放行了 8083 端口,再用curl -i http://127.0.0.1:8083/ws本地检查网关是否有响应。然后检查 Nginx 配置里location /ws块,必须包含proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection $connection_upgrade。我在帮一个朋友排查时,发现他抄的配置文件少了map那段,$connection_upgrade变量根本不存在,Nginx 直接报 500。这里建议把第 3.3 节的配置原样复制过去改端口即可。
5.2 坐席端能看到会话历史,但收不到实时消息
现象:坐席登录工作台后,历史会话列表能加载出来,但新访客发来的消息一条都不弹,刷新页面才能看到。
原因:HTTP 接口是通的,数据库也写入了,但 WebSocket 推送链路断了。最常见的是 Redis 频道名不一致——网关发布消息的 channel 和坐席端订阅的 channel 拼写或前缀不同,消息发出去后没人消费,全进了黑洞。
解决:用redis-cli monitor监听 Redis 命令,然后在访客端再发一条消息,观察网关是否往某个 channel 执行了 PUBLISH。同时打开坐席端浏览器的 WebSocket Frame 面板,看它 SUBSCRIBE 的频道名。两个名字一对就知道哪里不一致了。这类问题通常是改了配置文件里的channel_prefix但没同时改坐席端 JS 里的常量,两边各改各的。
5.3 同一条消息坐席端收到两三次:重连重放没做幂等
现象:网络不稳定时,坐席端会重复收到同一条消息,聊天记录里出现多次重复,客户截图投诉“客服在刷屏”。
原因:坐席端断线重连后,服务端会把离线期间未确认的消息全部补推一次,而补推的游标(last_message_id)没有正确记录,导致部分已收到的消息也被重新推送。前端收到消息后只做了渲染没有去重。
解决:消息在服务端生成时就要有全局唯一的message_id,坐席端维护一个last_received_id,每收到一条消息就更新。服务端补推接口接受游标参数,只返回大于游标的消息。前端渲染前再加一道校验:如果message_id已经存在于本地消息列表,直接丢弃。这套幂等逻辑在源码里不一定实现完整,需要自己补,属于拿到开源包后必改的点之一。
5.4 系统跑几天后 MySQL 报 too many connections
现象:部署完前两三天一切正常,后面坐席一多,管理后台开始报数据库连接错误,服务商邮件提醒 MySQL 连接数超限。
原因:长连接服务的 Worker 进程是常驻内存的,如果代码里每次处理消息都new PDO()且没有复用连接,每个进程每处理一条消息就多一个数据库连接,连接永远不释放。并发一高,连接数瞬间打满。
解决:检查代码里数据库连接是单例还是每次新建。常见开源方案会在start.php里初始化一个全局数据库连接池,业务代码通过getDb()获取同一个连接。如果发现代码里到处new PDO,需要改造成单例。另一个临时缓解方案是调大 MySQL 的max_connections,但这治标不治本,一般不建议只调参数不收代码。
5.5 聊天记录页越查越慢:单表几十万条后查询直接卡死
现象:系统跑了半年,管理后台的“聊天记录”页面打开要十几秒,按时间范围查询经常超时。
原因:消息表没有做分表,所有会话的聊天记录堆在一张表里。在线客服系统的消息量比普通业务系统大得多,一次活动下来可能产生几万条消息,半年就是几十万条,单表查询没有索引优势,翻页深了尤其慢。
解决:数据库初始化时就该考虑按月份分表,比如chat_message_202501、chat_message_202502。如果源码已经支持分表,确认定时任务有没有跑、查询代码有没有带上月份前缀;如果源码不支持,二次开发时优先把消息写入改成根据create_time动态选择表名。管理后台的查询接口也要改成强制传时间范围,避免全表扫描。这个改动要趁数据量还小的时候做,数据到几十万条再分表,光迁移数据就够折腾一阵。
6. 再进一步:先压测再上线,网关参数这样调
很多团队部署完在线客服系统,浏览器里测几个来回就上线了,结果大促活动一来,流量翻了几十倍,网关内存飙到 2GB,坐席端集体掉线,只能看着用户留言干着急。这是我最想提醒的一点:在线客服系统的瓶颈不在 Web 层,而在长连接网关的并发承载能力,网关的参数必须基于压测结果来调。
压测不需要太复杂的工具,思路就一条:模拟大量访客同时建立 WebSocket 连接并持续发消息,观察网关进程的 CPU、内存和消息送达延迟。常见的做法是写一个多进程脚本,每个进程模拟一个访客,连接网关后每 5 秒发一条消息,持续 10 分钟。压测前先记录基线数据,压测中采样网关进程状态:
# 找到网关主进程 PID(以 Workerman 为例) pid=$(pgrep -f 'start.php.*gateway' | head -1) # 压测过程中采样 CPU 和内存 top -p $pid -b -n 3 | tail -1 # 查看 Worker 进程数 ps aux | grep -c 'start.php.*worker'压测结果主要看三个指标:CPU 使用率、内存占用、消息延迟。建议值的参考范围如下:
| 指标 | 健康范围 | 超限后的调整方向 |
|---|---|---|
| 网关 CPU | 低于 70% | 增加 Worker 进程数或拆分网关节点 |
| 内存占用 | 稳定在总内存 50% 以内 | 检查是否内存泄漏,限制单连接缓冲区 |
| 消息延迟 | 坐席端收到消息在 500ms 内 | 排查 Redis 网络延迟,优化路由逻辑 |
如果压测发现 CPU 过高,优先调整 Worker 进程数,一般按 CPU 核心数的 2 倍设置,比如 2 核机器设 4 个 Worker。同时把心跳间隔放宽,降低心跳帧对事件循环的占用。如果内存持续上涨不回落,大概率是代码里某个数组或连接没有被释放,用memory_get_usage(true)在消息处理函数入口和出口各打一次点,找出泄漏位置。别在这个阶段省钱跳过压测,我见过太多团队把“压测”当成上线前的玄学,真到了高并发那天,网关崩了可没有后悔药。
最后养成一个习惯:把每次压测的参数调整记录在配置文件旁边,标注“多少人并发、调了什么参数、效果如何”。下次再改配置,直接翻记录,不用重新试错。希望帮到你。
本文还有配套的精品资源,点击获取