消息队列分发

游戏后台的最关键的地方就在于如何快速分发客户端发过来的消息. 有一点需要考虑在设计中的就是区分权重, 哪些消息是关键地方需要分配更多的 CPU, 哪些可以少分配一点. 我思考这个问题缘由就是我所管理的游戏由于某些客户端的频繁请求导致服务器内部 redis 连接获取不到连接. 其中原因我猜想跟 common-pool 的实现有 bug 有关, 我们所用的版本是 1.x 在获取连接时有非常多加锁的地方, 并且在其中更改空闲和使用的对象, 我怀疑这个工作没有做好导致其实有空闲, 但是却显示没有, 内部状态不一致. 因为我们配置的最大连接数是 50 个. 当我使用 lsof 命令观察连接时, 还不到 10. 后来改成了无限连接, 并且当空闲到一定时自动关闭. 这些还仅仅只是 redis 连接的问题. 更深层次的问题在于我们的队列, 之前的队列设计是有一条总的消息队列, 由 15 条线程去不停获取并且处理. 结果就是消息是没有顺序的, 并且处理过快. 那么当我们发现了非法数据时准备丢弃下一个消息或者关闭客户端时, 其实其它的线程已经在处理下一个消息了, 这样即便是有一些防止用户攻击的措施, 也是没有作用的. 这就是上次困扰我多日的宕机问题的症结.

游戏所需要的队列一定是要某些协议必须顺序处理的. 比如捕鱼项目中, 发子弹、击中鱼必须是顺序处理的, 或者支付的兑入兑出必须是顺序处理的. 这个我在 HTTP 里边实现过. 假如出现了多线程并发问题, 可想而知子弹还没发出就已经鱼被打死了, 或者还没兑入完毕加金币, 就已经兑出完毕减掉了金币. 那整个逻辑就乱套了. 并且单一队列的坏处就在于负载重的请求和负载轻的请求是一样对待的, 并且没有分离. 那么负载重的请求就会影响到负载轻的, 而且即便是负载重也得不到更加好的对待.

基于如上一些问题, 我将协议进行了归类, 给同一类协议根据权重分配若干队列. 每条消息来了会根据 TCP 连接 id 和协议号定位到指定的队列, 这样来自同一连接的所有同一类的协议请求只会被添加到一条队列, 保证处理是顺序的. 另外所有这些队列又被放入到全局队列中, 每次一条线程同步的从全局队列中拿出一条消息队列来处理, 这样一个队列在任意时刻只会被一个线程处理. 每条线程是有权重的, 这样当它获取到一条消息队列之后就决定自己一次处理多少条消息, 并且权重值一定是由低到高再到低, 这样当仅启动少数队列时, 可以保证每个消息队列都会被处理, 而当压力增大时可以保证消息多的队列可以得到充分的服务, 最后压力再增大时保证服务器不会过载. 在从消息队列中拿取消息时, 如果发现队列已经为空了就不会再将队列放回到全局队列, 下一次客户端又发来消息就会重新添加到全局队列. 假如整个全局队列都为空, 那么所有线程将会等到消息的到来. 只要有一条消息到来并且发现没有一条线程在工作, 就唤醒其中一条, 唤醒是按照线程等待的顺序 FIFO 进行的. 最后每隔 25 秒, 系统会在发现有任何一条线程处于等待状态时唤醒下一条线程. 这样既可以保证当消息多时可以得到有效服务, 消息少时可以节省 CPU. 我之前遇到的问题就是没有用等待, 而是空转, 导致 CPU 到达 80% 以上.

这整个设计基本上是 skynent 的消息队列的方式是重叠的, 我将其移植到了 java 环境来处理客户端请求. 并且对负载重的服务进行了均衡. 代码就不贴了, 我打算下一次做得更加通用一点, 弄成游戏的基础工具开源出来.

Leave a Reply

Your email address will not be published. Required fields are marked *