记一次服务器大并发下高延迟问题的定位

最近一段时间,项目增加了小游戏平台的支持,而小游戏的网络要求是 websocket,因此服务器也对 websocket 网络进行了支持。

最近,开始压测新的架构,发现了一个很有意思的问题:

  1. 当并发较少时,如100人同时在线,一切正常
  2. 当并发增大,比如增加到1500人,客户端的延迟显著增加,甚至将正常玩的玩家踢下线。

实际感受下来,客户端的延迟能达到1.5s左右,基本不可玩。

于是开始定位这个并发造成的延迟问题。

在现有的架构中,消息流转的方向如下:

client → LB → server → LB → client

整个流程涉及三方:客户端client、负责负载均衡的LB、以及服务器server本身。

这三方都可能会有问题,于是我设计了几个 case 来依次排除:

1)第一个可能就是客户端问题,比如客户端队列调度延迟或者其他bug。排除的方法就是用压测机器人和真实客户端来对比,如果压测机器人也有较高的延迟,那就可以排除client问题。测试下来,机器人统计的延迟也是很高,90%的耗时为900多ms,因此排除客户端client问题。

2)第二个可能是LB问题,有时候如果LB限制了带宽,也可能造成较高的延迟。排除的方法是将LB替换成其他不走LB的形式,在我们K8S服务器集群中是将LB换成 NodePort 的形式供客户端链接。测试下来,换成 NodePort 方式连接后,延迟依然还在,因此排除LB问题。

3)最后一种可能就是服务器server本身的问题。但是之前非小游戏的情况下,压测是正常的,因此我的怀疑点就落到websocket 网络层实现上,当然,还有一个可能是服务器的业务逻辑层的处理比较耗时。

先来看看是否是逻辑层处理比较耗时。

这个比较简单,就是在逻辑处理的入口和出口处增加时间统计。结果是逻辑处理非常快,基本是毫秒级别的耗时,因此排除了逻辑层的问题。

那最后的怀疑点就落到了网络层上。为了证实自己的猜想,我将压测机器人还原为 tcp 网络,发现延迟显著下降到正常水平(10ms以内),于是根据逻辑推理,问题出在 websocket 服务上。

现在的网络层 loop 是这样的流程:

c++ 复制代码
while (IsRunning())
{
    // 处理 websocket 网络IO
    _ProcWebSocket();
    
    // 处理TCP网络IO (内部TCP通信)
    _ProcTcpEpoll();
    
    // 处理逻辑层过来的消息(消息队列)
    _ProcNetQueue()
}

根据以往的经验,很可能是调度循环 loop 里处理消息发送的流程积压的比较多,也就是 _ProcWebSocket 调度时间不足导致的。

查看代码,发现是调用的是 RunOnce, 它的实现也比较简单:

arduino 复制代码
void CWSServer::RunOnce()
{
    m_endpoint.poll_one();
}

翻看 websocket 库文档后,发现除了 poll_one 之外还有个 poll,他们的区别就是一次处理多少 websocket 的IO 事件:

很明显,一次循环调度一次 poll_one 造成了底层IO 的挤压,我调试时候发现,并发超过1500人之后,poll_one 调用10次都不够。

最终的修改方案也比较简单,使用 poll 来尽量多的调度触发的事件,有多少调用多少,类似 epoll(获取有多少个事件,然后依次处理):

arduino 复制代码
void CWSServer::RunOnce()
{
    // Start the Asio io_service run loop
    // 非阻塞调用,会一次处理多个事件
    m_endpoint.poll();
}

修改完毕之后,大并发下,延迟下降到10ms,基本满足需求。

更进一步:这次问题的定位还是比较耗时的,如果引入一下消息追踪的工具,能较快的定位到延迟出在哪个环节; 另外,如果能增加一些消息延迟的监控或者统计,能更快发现问题。

相关推荐
我是小妖怪,潇洒又自在2 小时前
springcloud alibaba(九)Nacos Config服务配置
后端·spring·spring cloud
Victor3563 小时前
Netty(26)如何实现基于Netty的RPC框架?
后端
Victor3563 小时前
Netty(25)Netty的序列化和反序列化机制是什么?
后端
qq_12498707533 小时前
重庆三峡学院图书资料管理系统设计与实现(源码+论文+部署+安装)
java·spring boot·后端·mysql·spring·毕业设计
桦说编程3 小时前
并发编程高级技巧:运行时检测死锁,告别死锁焦虑
java·后端·性能优化
无限大64 小时前
为什么"软件测试"很重要?——从 Bug 到高质量软件的保障
后端
健康平安的活着4 小时前
springboot+sse的实现案例
java·spring boot·后端
程序员鱼皮5 小时前
从夯到拉,锐评 28 个后端技术!
后端·计算机·程序员·开发·编程经验