写点什么

[Go WebSocket] 为什么我选用 Go 重构 Python 版本的 WebSocket 服务?

作者:HullQin
  • 2022 年 8 月 31 日
    广东
  • 本文字数:1932 字

    阅读完需:约 6 分钟

我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。


前言:标题其实有 2 重含义:为什么重构、为什么选 Go。

背景

如果你读过《我做了个《联机桌游合集: UNO+斗地主+五子棋》无需下载,点开即玩!叫上朋友,即刻开局!不看广告,不做任务,享受「纯粹」的游戏!》这篇文章,就知道我联机桌游的后端选型是 python,用了ASGI协议,用了Daphne实现。


但是 Python,众所周知,效率有限,为了让我的小破服务器承受更多并发压力,我需要让 CPU 更高效的运行,我希望针对每一个请求,CPU 花的时间更短,这样可以增加并发量。

应用瓶颈

其实很多 websocket 应用瓶颈在 CPU 性能和内存上。如果请求量大,CPU 处理不及时,源源不断的客户端请求会堆积在内存中,导致内存占用增加,进一步导致处理时间变慢,CPU 高负荷运转。这样显著的特点是:CPU 打满、内存占用持续上升。用户的感受就是:点击按钮后没反应,要等一会才有反应,或者一直没响应。


当然,以上只是常见理论。具体应用瓶颈需要你做一下压测,寻找那个「短板」。这样当流量突增时,你知道应该优先给哪里「扩容」。

为什么重构

自研的 WebSocket 框架,架构非常简洁,没有任何外部组件依赖(不依赖 Redis、MySQL、MQ 等数据库或队列)。使得我们更容易去分析。


整个链路是:用户浏览器 -> OpenResty -> Daphne(Python)。


其中 OpenResty 最大连接数我配置了 6w,近一段时间内都不会成为「短板」。如果以后用户量够高,我们再想办法。


瓶颈最可能出现在 Python 服务上。


目前存在如下问题:


  • 代码执行效率可优化。Python 是动态解释,效率不如编译成机器码的语言(C++、Go)高;有个核心逻辑是用 Python 序列化和反序列化 Protocol Buffer 二进制,目前是用纯 Python 实现,可优化。

  • 目前是基于协程处理各个连接,由于 PIL 存在,Python 无法基于多线程充分利用多核 CPU。

  • 所有数据存在内存中,随着时间增长,内存占用会越来越高。

  • 无法容灾+难以扩容。现在扩容只能对单台机器扩容,无法将程序运行在多台机器上。而且单机宕机后,100%用户受影响。


解决方案:


  • 针对问题一和问题二:放弃 Python,选用其它语言实现功能。选用的语言,要解决这两个问题。

  • 针对问题三和问题四:我们以后再聊。


这一段,解答了我为什么要重构,其实核心就是:降本增效。没钱买贵的服务器,所以只能在不影响开发效率的前提下,压榨性能。

为什么选用 Go

Goroutine

大家可以了解下 C10K 的并发问题,Linux 引入 epoll 解决了这个问题。随后又有了 C10M 等问题。


如果靠传统的多线程模型,每个线程处理 1 个连接。那么当连接太多时,线程调度就会花费很多时间,导致效率低。


所以有了协程(coroutine)这个概念。我之前使用 Python,就是利用协程处理的,每 1 个连接就开启 1 个协程。整体只是 1 个线程,减少了操作系统调度多个线程的成本。


此外,我又了解到了 Go,它提出了 goroutine,goroutine 不是协程、也不是线程。


  • 线程特点:调度完全由操作系统调度,开发者无法直接控制,只能通过加锁来管理多个线程的执行顺序。

  • 协程特点:调度完全由开发者掌控,但是某个协程主动放弃执行权之前,其它协程都必须挂起。

  • goroutine 特点:调度不是操作系统直接执行,但开发者也无法控制,因为 goroutine 的调度是由 go 的 runtime 调度的,一个比操作系统线程更轻量级的调度器。某个 goroutine 主动放弃执行权之前,其它 goroutine 也有机会执行(例如有多个 goroutine 时,限制每个 goroutine 最多连续执行 10ms,就得让出执行权)。此外开发者可以通过 channel 控制多个 goroutine 的执行顺序。


总结一下:


  1. Goroutine 效率高于线程。跟协程相似。

  2. Goroutine 开发成本比线程低,使用 channel 可很大程度降低并发编程的心智负担。但有时候还是需要依赖锁,这点不如协程。

  3. Goroutine 避免了协程的这个问题:单一协程如果遇到了计算量大的任务,会阻塞其它协程。

开发效率与运行效率的权衡

Go 在保证开发效率的同时,也保证了优秀的运行效率,被称为下一代 C++。


这是网上的一张压测数据图,可以看到,综合来看,在 Python、NodeJS、Java 中,Golang 是比较全面的选手。


写在最后

我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

发布于: 刚刚阅读数: 2
用户头像

HullQin

关注

公众号【线下聚会游戏】 2020.10.07 加入

game.hullqin.cn 我做了一些联机桌游网页:支持2-10人联机的UNO、2-4人联机的斗地主、2人联机的五子棋。无需下载,点开即玩!叫上朋友,即刻开局!不看广告,不做任务,享受「纯粹」的游戏!

评论

发布
暂无评论
[Go WebSocket] 为什么我选用Go重构Python版本的WebSocket服务?_Go_HullQin_InfoQ写作社区