外贸游戏部署在日本服务器时,如何用连接池和进程模型判断并发瓶颈

要回答“日本服务器适合部署外贸游戏、API 还是网站?按延迟需求判断”,不能只看服务器所在地区,也不能把“同时在线人数”直接当成应用并发能力。正确做法是先区分业务类型,再把延迟拆成网络往返、连接建立、进程排队、线程或事件循环等待、连接池等待、数据库执行、下游调用和响应发送等环节,最后用实际目标用户网络与真实业务负载验收。
外贸游戏通常先受长连接数量、文件描述符、事件循环、会话队列或共享状态影响;API 更容易受工作进程、线程、数据库连接池和下游调用限制;网站则必须把静态资源与动态页面分开测试。日本服务器是否适合,取决于目标用户的连接建立时间、抖动、丢包、接口响应时间和长连接稳定性,不能在没有实测数据时承诺固定延迟或固定并发数。
先按延迟目标确定测试对象
先不要调整进程数和连接池。把准备验收的业务路径列出来,否则扩容连接池可能只是把等待从应用队列转移到数据库,无法证明并发能力提高。
| 业务对象 | 应优先验收的路径 | 重点观察资源 | 常见先发瓶颈 |
|---|---|---|---|
| 外贸游戏 | 登录、长连接建立、心跳、状态同步、关键操作、存档或结算 | 文件描述符、事件循环、会话队列、进程间通信、共享状态 | 事件循环阻塞、会话队列堆积、单进程状态冲突 |
| API | 真实业务接口,而非仅健康检查接口 | 工作进程、线程、连接池、数据库锁、下游调用 | 工作进程耗尽、连接池等待、慢查询或下游超时 |
| 网站 | 静态资源、动态页面、登录和写入页面分别测试 | 入口服务、文件读取、应用进程、模板渲染、动态请求连接池 | 动态请求排队、模板渲染或数据库连接不足 |
这里必须区分三个量:
- 并发连接数:同时保持的 TCP 或长连接数量,游戏尤其关注这一项。
- 每秒请求数或消息数:单位时间内真正进入应用处理的请求或消息数量,不能用连接数替代。
- 连接池并发数:同一时间占用数据库、缓存或其他下游连接的数量,不等于前端在线人数。
在稳定运行、平均响应时间用秒表示时,API 的在途请求量可以用近似关系辅助判断:
在途请求数 ≈ 每秒请求数 × 平均响应时间(秒)
这只是排查和估算关系,不能替代真实压测。游戏还要单独记录消息速率:
每秒游戏消息数 ≈ 长连接数 × 单连接平均消息速率
但并非每条游戏消息都访问数据库,因此不能再把这个结果直接当成数据库连接数。登录、存档、结算和查询可能短暂使用数据库连接,而心跳和状态同步可能只经过事件循环或内存状态。
准备验收指标和回滚条件
在测试前记录以下内容:
- 登录、心跳、状态同步、关键操作、写入和查询等业务路径。
- 目标并发连接数、每秒请求数或消息数、请求和消息大小、长连接保持时间。
- 允许的 p95、p99 延迟、错误率、连接建立失败率和超时率。
- 应用采用单进程事件循环、多进程、多线程,还是进程与线程混合模型。
- 数据库、缓存、外部接口和消息队列分别使用什么连接池,以及连接池是按进程、按线程还是全局共享。
- 测试使用的认证流程、数据规模、读写比例、消息顺序和长连接保持策略。
压测必须尽量使用与生产相同的协议和业务流程。普通 HTTPS 请求不能代替游戏长连接测试,健康检查接口也不能证明真实 API 的数据库连接池没有问题。
涉及服务重载、重启或可能中断连接的修改,应提前保存应用配置、服务管理配置、Nginx 配置和连接参数,并确认维护窗口、客户端重连方式、未完成写入处理方式和回滚负责人。生产环境中的采样命令也应避免在高峰期持续高频执行。
先拆开一次请求和一条会话
可以按下面的路径记录各阶段耗时:
客户端
-> Nginx 或其他入口服务
-> 应用进程
-> 线程或事件循环
-> 应用内队列
-> 数据库、缓存、外部接口或消息队列连接池
-> 返回应用进程
-> 返回客户端
每个阶段至少记录两类时间:
- 排队时间:等待工作进程、线程、应用内队列或连接池的时间。
- 服务时间:真正执行代码、查询数据库、调用下游服务和发送响应的时间。
排队时间持续增加而服务时间基本稳定,通常应先检查进程、线程、队列和连接池容量;服务时间本身变长,则应检查慢查询、锁、下游服务、序列化和共享资源。此时直接增加连接池,可能会让更多请求同时进入已经繁忙的下游。
在 Linux 服务器上建立基线
以下命令适用于使用 systemd 管理服务的 Linux 系统。game-api.service 是占位服务名,执行前必须替换为实际服务名。查询命令不会修改配置,但 pidstat、vmstat 和 iostat 会持续采样,应控制采样时间。
systemctl status game-api.service --no-pager
systemctl show game-api.service \
-p MainPID \
-p LimitNOFILE \
-p CanReload
ss -s
ss -lntp
确认主进程、子进程和线程模型:
PID="$(systemctl show game-api.service -p MainPID --value)"
ps -o pid,ppid,nlwp,%cpu,%mem,stat,etime,cmd -p "$PID"
# 系统安装了 pstree 时执行;未安装可跳过
pstree -ap "$PID"
pidstat -p "$PID" -t 1 10
vmstat 1 10
# 系统安装了 iostat 时执行
iostat -xz 1 10
将这些结果与业务压测时间对齐:
- 主进程只有一个,且某个线程持续占用较高 CPU,可能是单事件循环或单线程任务成为边界。
- 子进程增加后,每个子进程都建立独立数据库连接,说明连接池可能按进程复制。
- CPU 使用率不高,但接口响应时间和连接池等待上升,应检查数据库、锁、下游调用和池容量。
- 建立连接数增加而应用请求数没有同步增加,可能只是长连接或保活连接增加,不能据此判断数据库并发增加。
LimitNOFILE接近当前文件描述符使用量时,长连接业务可能先触及文件描述符限制。应先确认峰值、进程实际继承的限制和系统级限制,再决定是否调整,不能直接提高限制。
如果使用 Nginx 作为入口,可以记录请求、上游连接和响应时间。修改前先备份配置;语法错误会导致重载失败,因此不能跳过检查。
在 Nginx 的 http 配置上下文中增加:
log_format app_timing
'$time_iso8601 '
'request="$request" '
'status=$status '
'request_time=$request_time '
'upstream_connect_time=$upstream_connect_time '
'upstream_header_time=$upstream_header_time '
'upstream_response_time=$upstream_response_time';
access_log /var/log/nginx/access.log app_timing;
然后检查并平滑加载:
sudo nginx -t
sudo systemctl reload nginx
如果 request_time 明显高于 upstream_response_time,时间可能主要消耗在客户端传输、入口排队或响应发送;如果 upstream_response_time 较高,则继续在应用内部拆分进程、线程、连接池和下游执行时间。
测试 API 或网站基础时间时,应从实际目标用户网络发起请求,而不是只在日本服务器本机执行:
curl -sS -o /dev/null \
-w 'dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' \
https://example.com/health
该命令只能帮助观察 DNS、连接建立、首字节和总时间,不能替代真实业务接口。游戏需要使用真实客户端或协议专用测试程序,分别记录登录、心跳、状态同步和关键操作的往返时间。
根据进程和线程模型定位并发边界
单进程事件循环
单进程事件循环适合大量等待 I/O 的连接,但前提是事件循环中的任务不能执行长时间同步操作。一次阻塞式数据库查询、文件操作、复杂计算或长时间锁等待,都可能暂时阻塞同一进程内的其他会话。
以下现象同时出现时,应优先查事件循环,而不是增加数据库连接池:
- CPU 总使用率不高,但游戏消息延迟成批增加。
- 事件循环延迟升高,而数据库连接池没有耗尽。
- 心跳和状态同步同时变慢,并且延迟与某类操作同时出现。
- 日志中存在同步调用、长时间锁等待或大批量序列化。
处理顺序应是找出阻塞任务,将其改为非阻塞调用,拆分计算,或放入有上限的后台队列。后台队列必须有明确的消费速率、最大长度和失败处理,不能用无限队列掩盖处理能力不足。
多进程模型
多进程可以利用多个 CPU 执行请求,但每个进程可能拥有独立的连接池、缓存和应用队列。连接池总量不能只看配置文件中“每进程池上限”这一项。
如果每个进程分别创建连接池,数据库连接上限可按以下关系估算:
数据库连接上限
≈ 工作进程数 × 每进程连接池上限
+ 管理连接
+ 迁移、任务进程和其他保留连接
如果连接池实际上按线程创建,还要乘以每个进程中的线程数;如果所有线程共享同一个进程级连接池,则不能重复相乘。必须通过框架文档、启动参数和运行时连接数确认实际模型,不能仅凭配置项名称判断。
多进程游戏还要确认会话状态归属:
- 会话状态只保存在进程内存中时,同一玩家的后续消息必须回到能够访问该状态的进程。
- 会话状态放在共享存储中时,要观察共享存储的锁、连接池和序列化开销。
- 状态同步通过进程间队列传递时,要记录队列长度、消费延迟、发送失败和丢弃策略。
增加进程后如果出现玩家状态不一致、重复登录或消息顺序错误,应立即停止扩容,恢复到上一个已验证的进程模型,先确认会话所有权和共享状态方案。
多线程模型
线程适合存在阻塞 I/O 的应用,但线程数不等于数据库并发数。至少要确认:
- 线程是否共享同一个连接池。
- 一个线程是否会长期占用一个数据库连接。
- 线程等待数据库时是否仍占用工作线程。
- 应用是否存在需要加锁的共享对象、全局队列或共享缓存。
线程增加后,CPU 使用率不高但连接池等待变长,可能是大量线程集中等待有限的数据库连接;线程增加后数据库 CPU、锁等待或 I/O 等待升高,则线程和连接池可能已经超过数据库实际处理能力。此时不应继续同时增加线程和连接池。
用单变量测试确认连接池是否是瓶颈
测试时一次只改变一个变量,避免把进程数、线程数和连接池上限同时修改,导致结果无法解释。推荐顺序如下:
- 使用当前配置记录低并发基线。
- 保持请求内容、数据规模、读写比例和连接保持时间不变,逐步增加并发连接或每秒请求速率。
- 同时记录入口响应时间、应用队列长度、工作进程状态、连接池活跃数、空闲数、等待数和获取连接超时次数。
- 恢复到基线后,单独调整工作进程数,观察延迟、数据库连接总数和错误率是否同步变化。
- 再恢复进程数,单独调整连接池上限,观察连接池等待时间和数据库负载。
- 对游戏单独增加长连接数量、消息速率和状态同步负载,不能用普通 API 压测结果代替。
连接池参数名称因框架而异,不能直接照抄其他软件的配置名。需要核对的含义包括:
| 参数含义 | 调整价值 | 必须同时观察 |
|---|---|---|
| 每进程池上限 | 限制单个进程同时占用的下游连接数 | 进程数乘积后的总连接数、数据库负载 |
| 最小连接数 | 控制启动后预建立或保持的连接 | 启动耗时、空闲资源和突发流量恢复 |
| 获取连接超时 | 池耗尽时请求最多等待多久 | 是否能及时暴露瓶颈,避免无限排队 |
| 空闲回收时间 | 控制长期不用的连接是否释放 | 长连接稳定性和下游回收行为 |
| 队列上限 | 限制池或业务任务最多积压数量 | 内存增长、延迟失控和拒绝策略 |
| 工作进程数 | 控制同时执行应用任务的进程数量 | CPU、内存、总连接数和状态一致性 |
三种关键结果的处理方式
连接池等待高,但数据库执行时间正常。
先检查事务是否及时提交、连接是否按路径归还、结果集是否读取完毕、请求是否持有连接执行了无关操作。确认没有连接泄漏后,再小幅调整池上限,并重新计算所有进程、线程和保留任务的连接总量。
连接池全部占用,数据库执行时间也高。
这通常不是简单的“连接池不够大”。如果数据库已经受到 CPU、I/O、锁或慢查询限制,增加连接只会让更多请求进入数据库,可能进一步增加排队和上下文切换。应优先减少单次连接占用时间、处理慢查询或锁等待,必要时降低入口并发。
连接池有空闲,但接口仍然排队。
检查工作进程、线程、应用内队列、事件循环和外部调用。工作进程如果全部忙于慢任务,数据库连接空闲也不能改善响应时间。
连接获取超时不宜设置成无限等待。API 可以在超时后返回可识别的服务繁忙错误;游戏则应按消息类型区分关键操作和已经过期的同步消息,但任何丢弃策略都必须符合业务数据一致性要求。
分别验证游戏、API 和网站
外贸游戏:先验证会话,再验证数据库操作
游戏验收至少要记录:
- 长连接建立成功率和保持时间。
- 心跳超时率、状态同步延迟和消息顺序。
- 单个进程的事件循环延迟。
- 每个进程的会话数、消息队列长度和内存变化。
- 登录、存档、结算等数据库操作是否影响实时消息处理。
- 进程间队列的长度、消费延迟、发送失败和重试行为。
如果只有结算或存档时延迟升高,而普通同步正常,瓶颈更可能在数据库连接池、事务或写入队列;如果所有玩家的心跳和同步同时延迟,应优先检查事件循环、进程 CPU、共享锁和进程间队列。
长连接数量只能说明会话和文件描述符压力,不能直接说明数据库连接池需要同样数量的连接。只有在业务确实让每个会话长期占用下游连接时,二者才可能接近,还要通过运行时连接数验证。
API:区分工作进程排队和池等待
使用真实接口比例进行测试,至少验证:
- 请求是否先在工作进程或线程队列中等待。
- 连接池获取等待时间是否随并发上升。
- 数据库连接总数是否按进程数成倍增加。
- p95、p99 延迟是否主要由池等待造成。
- 5xx、连接超时和连接池获取超时是否同时上升。
- 下游服务变慢时,应用线程和进程是否被长期占用。
健康检查接口通常不访问数据库,不能用它证明真实业务接口的连接池没有问题。
网站:静态和动态请求必须分开
网站应至少分为三类测试:
- 静态请求:观察入口服务连接、文件读取和响应发送。
- 动态页面:观察模板渲染、应用进程和数据库连接池。
- 登录、搜索、写入页面:单独验证会话、查询、事务和失败处理。
如果静态页面很快而动态页面明显变慢,优先检查应用进程、模板渲染和动态接口,不要直接增加入口连接数或数据库连接池。
常见失败现象与处理顺序
| 现象 | 证据组合 | 处理方向 |
|---|---|---|
| CPU 高、连接池等待低 | 工作进程或线程持续繁忙 | 检查代码和计算任务,谨慎调整进程数,不先增加数据库池 |
| CPU 低、连接池等待高 | 池获取等待高,数据库执行时间不高 | 检查连接归还、事务时长、池上限以及线程与池的关系 |
| 数据库连接全满且负载高 | 活跃连接高,查询或锁等待升高 | 限制总池容量,处理慢查询和锁,不继续扩大并发 |
| 连接数高但请求量低 | 大量长连接或保活连接,应用请求不高 | 区分文件描述符、入口连接和数据库连接 |
| 进程增加后状态异常 | 会话跨进程、共享状态缺失或队列乱序 | 停止扩容,恢复已验证模型,确认会话所有权 |
| 队列长度持续增长 | 消费速率小于生产速率 | 设置有界队列和明确降级策略,避免无限占用内存 |
| Nginx 请求时间高、应用时间低 | request_time 明显高于上游时间 | 检查客户端传输、入口连接和响应发送 |
| 应用频繁重启 | 内存持续增长或进程被系统终止 | 检查队列滞留、连接泄漏和单进程状态,先保护现网容量 |
排查应由外到内、由低风险到高风险进行:先确认目标用户网络和入口时间,再检查进程、线程、队列和连接池,最后处理数据库、共享锁和应用代码。没有对应监控证据时,不要仅凭“并发高”判断某一层就是瓶颈。
变更失败时的回滚
Nginx 配置修改属于入口层变更。修改前备份,语法检查通过后再重载:
sudo cp -a /etc/nginx/nginx.conf \
"/etc/nginx/nginx.conf.bak.$(date +%Y%m%d%H%M%S)"
sudo nginx -t
sudo systemctl reload nginx
如果重载后延迟、错误率或连接建立失败率异常,应使用对应备份恢复,再执行语法检查和重载。不要直接删除当前配置,也不要在未验证的情况下连续重载。
应用配置回滚前,先确认服务是否支持平滑重载:
systemctl show game-api.service -p CanReload
systemctl status game-api.service --no-pager
如果支持重载,优先使用应用自身的平滑重载方式;如果只支持重启,应明确影响范围:已有请求、游戏长连接和正在执行的事务可能中断。重启前暂停继续加压,并确认客户端重连、会话恢复和未完成写入的处理方式。
当数据库连接数接近上限时,回滚顺序应为:
- 停止继续增加进程数、线程数和连接池上限。
- 恢复到上一个已验证的应用配置。
- 观察连接数、错误率、池等待时间和队列长度是否回落。
- 只有在系统恢复稳定后,才重新进行单变量测试。
不要强制断开未知业务连接,也不要在没有备份和维护窗口的情况下修改数据库全局连接上限。
上线或验收清单
- [ ] 已从实际目标用户网络测量连接建立、首字节和总响应时间。
- [ ] 已分别测试游戏长连接、API 动态请求和网站动态页面。
- [ ] 已确认应用是单进程、多进程、多线程还是混合模型。
- [ ] 已明确区分并发连接数、每秒请求数或消息数,以及连接池并发数。
- [ ] 已计算“进程数 × 每进程池上限”以及管理、迁移和任务进程的保留连接。
- [ ] 已记录连接池获取等待、数据库执行时间、工作进程状态和应用队列长度。
- [ ] 已确认长连接数量没有被直接当成数据库并发。
- [ ] 已验证多进程下的会话状态、共享资源、进程间队列和消息顺序。
- [ ] 已设置有界队列、连接获取超时和可识别的失败处理。
- [ ] 已保存变更前配置,并确认服务重载或重启的影响范围。
- [ ] 压测结束后,p95、p99、错误率、队列长度、数据库连接数和进程重启次数均回到可接受范围。
- [ ] 已能根据数据指出瓶颈位于网络、入口、进程、线程、连接池、队列或共享资源,而不是只记录一个“并发数”。