当前位置:网站首页>游戏思考14:对cache_server缓冲服务器的问题思考(读云峰博客有感)
游戏思考14:对cache_server缓冲服务器的问题思考(读云峰博客有感)
2022-06-24 06:42:00 【谢白羽】
文章目录
一、游戏服务器的作用类别
引用链接:MMORPG服务器类别介绍
二、原本cache_server的设计
结构
cache server 的协议设计非常简陋。就是顺序的提交请求,然后每个请求会有序的得到一个回应。这些请求要么是获取 GET 文件,要么是上传 PUT 文件。其中 PUT 文件在协议上不必回应。我的理解
想一般的MMORPG游戏会根据客户端提供的id(雪花生成的),判断这个ID是不是人物的ID、工会的ID、NPC的ID等等(其实就是根据宏定义,用联合体强转,用前6位或7位做对比),然后根据客户端的请求信息返回给他对应的消息。(不清楚为啥云风的服务器需要GET\PUT大量文件?)
三、问题展现
- 前提
这些请求要么是获取 GET 文件,要么是上传 PUT 文件。其中 PUT 文件在协议上不必回应。 - 问题
1)问题一:PUT的问题
由于 PUT 文件没有回应,所以客户端无法直接确定文件是否全部上传完毕;如果必须确认,只能在 PUT 文件结束后,再提交一个 GET 请求。如果收到了后续 GET 的回应,可以理解为前一个 PUT 已经结束。实际上,Unity 客户端没想去确认 PUT 是否结束,从 log 分析,它只是简单的在最后一个 PUT 结束后等待了一段时间再断开连接。
2)问题二:这种依赖严格次序的协议,在面对两边数据量不对等、网络速度不对等的近况时,很难有一个健壮的实现。
四、假设是阻塞网络
- 伪代码体现
while true do
local req = get_request(fd)
local resp = handle_request(req)
put_response(fd, resp)
end
注释
即用一个死循环,依次获取网络请求,针对请求生成回应数据,然后将回应数据经网络发回。可能导致死锁的原因
1)假设 get_request 是阻塞读网络,put_response 是阻塞写网络,那么就要求客户端也是严格的配合:客户端也必须提起一个请求后,等待回应,然后再提下一个请求。否则,若客户端连续提两个请求,服务器在处理第一个请求后,推送的回应客户端不去接收(因为客户端还在提第二个请求),就可能会死锁。
2)死锁发生时,客户端在推送第二个请求(写操作),而服务器在推送第一个回应(写操作);两边都没在收取对方的数据,两侧的 api 都等待在写网络上(因为对端不读)。
五、读写分离带来的OOM(内存溢出)的问题
现在服务器的基本做法
一般会将网络读写分离到独立线程中,死锁不会发生。服务器收到新请求就能处理,产生出回应数据。而回应数据将缓存在网络线程中,等待客户端接收,而不会阻塞住上面的业务循环。那里的 put_response 是非阻塞的。基本做法的缺点
因为请求和回应是不对等的,客户端可以轻易的发起大量的 GET 请求,一条几十字节的 GET 请求,很可能需要几十上白兆的回应包。巨量的回应包积压在网络线程的发送队列中,很快就会吃光所有的内存。做法优化
所以,put_response 这个函数必须在内存耗光前阻塞住,前面的问题就会回来。所以,合理的服务器设计必须分离 get_request 和 put_response 到两个执行序列里。
六、早期unity的缓冲服务器的设计和现在unity的设计
只有一个简单的 js 文件,跑在 nodejs 服务中。nodejs 是基于回调机制的,请求处理放在了 socket 的 data 事件回调中,每个请求都会生成一个新的对象,这个对象会进入一个队列,由 socket 的可写事件触发出队列操作,将文件 pipe 到 socket 上。因为回应操作是由文件的 pipe 到 socket 依次完成的,这个过程可能很慢(取决于对端的接收进度),那么新请求非常可能积压在队列中。假设客户端一直推送请求,而疏于处理回应的话,这个队列将一直增长,直到 OOM 发生。
- 现在unity的做法
现在的 cacheserver 版本已经变得非常复杂,不太容易看清楚。我简单浏览了一下,觉得依旧存在这个隐患:在 server/command_processor.js 文件中,_onGet 函数会把要回应的 item 压入队列(this[kSendFileQueue].push(item) 这个队列可能无限增长。
七、最终优化的缓冲服务器方法
云风现在的实现也是类似的机制,伪代码如下:
-- request thread
while true do
local req = get_request(fd)
push_queue(q, req)
end
-- response thread
while true do
local req = pop_queue(q)
local resp = handle_request(req)
put_response(fd, resp)
end
云风做法
这里的 push_queue 在达到队列预设的容量后,是会阻塞等待另一个线程的 pop_queue 取走再继续工作的。我们在做此修改后,把 queue 的容量设置为 8192 ,实际运行时,客户反馈以前正常的打包过程(其实会让服务器濒临 OOM 崩溃),现在有时会卡在和 cache server 的通讯上。经过线上观察(使用 skynet 预留的 debug console 的 debug 功能进入服务查看内部状态),发现这个 queue 很容易就满了,等待 pop_queue ;而能执行 pop_queue 的线程却阻塞在 put_response 上,也就是 unity 客户端拒绝接收前面那 8000 个请求产生的回应。选择点
针对这种情况的合理推测是, unity 在某些极端情况下,一口气发了上万(甚至十万个)请求,它在这些请求全部从网络发出之前,没有跑网络接收的业务,导致数据全部堵在网络层;而服务器为了避免自己内存耗尽,只能暂停接收新的请求,结果就卡了。换句话说,针对客户端不合理的使用:不断地发送请求,拒绝处理回应,那么服务器若想一直服务下去,只能在内存耗尽和卡住间二选一。当然还有拒绝服务的第三条路,即在异常情况(卡住)后,踢掉客户端。客户端发现断线,就会重连服务器再来一次。最终对策
我们最终的对策是,优化队列,让队列中保存的数据足够的少(这里可以只讲客户端请求 id 保留在队列中,每个请求所需内存在 100 字节以下)然后增加队列的容量上限到百万级;当队列满时踢掉客户端。原博文传送们
传送门做法建议
1)非阻塞 API + 流式读写 + 线程池(这里流式读写啥意思,不懂?)
2)可以记录一下客户端发送但没有接收的请求数,超出一个限额之后就不再把请求放进队列,而是往队列放进一个需踢掉客户端的标记(但不立即踢除)。这样该客户端能保证顺序接收到限额内的文件再被踢掉——使用者如果发现被踢掉,多跑几遍就是了,这种实现每次总能多接受到一点数据的。
边栏推荐
猜你喜欢

Leetcode probability interview shock series 11~15

JVM debugging tool -arthas

Huawei Cloud Database Advanced Learning

大厂不是衡量能力的唯一出路,上财学姐毕业三年的经验分享

原神方石机关解密

【图像分割】基于形态学实现视网膜血管分割附matlab代码

setInterval里面的函数不能有括号

Maui uses Masa blazor component library

Application configuration management, basic principle analysis

【愚公系列】2022年6月 ASP.NET Core下CellReport报表工具基本介绍和使用
随机推荐
How do I check the IP address? What is an IP address
0 foundation a literature club low code development member management applet (II)
mysql中的 ON UPDATE CURRENT_TIMESTAMP
Arduino raised $32million to enter the enterprise market
Introduction to game design and development - layered quaternion - dynamic layer
Tencent host security captures Yapi remote code execution 0day vulnerability for wild exploitation. The attack is spreading and can be intercepted by firewall
毕业季进击的技术
On update current in MySQL_ TIMESTAMP
Can the small fire Chunfeng tea make its debut by "keeping fit"?
Why use lock [readonly] object? Why not lock (this)?
c#:互斥锁的使用
智能视觉组A4纸识别样例
Decryption of the original divine square stone mechanism
Bay area enterprises quick look! The focus of the data regulations of Shenzhen Special Economic Zone just released is coming!
Clickhouse source code note 6: exploring the sorting of columnar storage systems
. Net7 miniapi (special part):preview5 optimizes JWT verification (Part 1)
FreeRTOS MPU使系统更健壮!
JVM调试工具-jps
Graduation season advance technology
EasyDSS_ The dash version solves the problem that the RTSP source address cannot play the video stream