最近看了个 gRPC 的入门,本着学习(水commit)的目的,自己写个 gRPC 的 demo.
然后被 Java 毒害的程序员的通病就犯了: 开始各种往上面堆逻辑,想造个功能完备的框架出来。
以下是使用 gRPC 做游戏服务器的一点想法,记录下来,看看有没有机会慢慢实现。
使用 vtprotobuf 插件生成 pb 代码,解决反射的性能问题。
可以生成 proto 对应的 service 结构体和代码,反正是结构化的代码,能用代码生成就用代码生成。
ChatGPT推荐了 gRPC + TLS + zstd 的方案。
之前在做长链接游戏服务器的网关的流程是这样的:
为什么用 RC4 呢?因为 RC4 是流加密不是块加密,块加密需要事先知道数据块有多大,一块完整的数据才能加解密,而流加密来多少处理多少。
看到各种长连接服务器demo,上来就是先从头几个字节取数据包长度,太不安全过于玩具了,上面的方法是游戏工业界的做法,也是比较传统的做法。
而在 gRPC 的世界里,这类底层加密行为已经不再必要,gRPC 推荐的安全方案是使用 TLS(HTTPS),且 gRPC 本身也是建立在 TCP 长连接上的 HTTP2 协议,只需在建立连接时握手1次,AES-GCM 是 TLS 默认的加密算法(现代硬件有指令集加速),每次发送/接收数据都会加解密,但加解密速度远大于网速瓶颈。
使用 TLS 需要额外的证书管理,在开发过程中可以自签证书,客户端和服务器各维护证书或密钥文件,或者启用 InsecureSkipVerify 跳过证书检测。
而在线上环境中,就要用 Let’s Encrypt + Certbot 或 k8s cert-manager 来定时更新证书,注意需要自己实现 gRPC 热加载证书,参考 certwatcher.go , 爆栈上的这篇回答 也能直接用。
网关要做负载均衡,可以用 nginx steam 做 L4 负载均衡,只转发 TLS 流量,一个 nginx 挂3到5个网关实例,而网关可以没有公网 ip ,但 Let’s Encrypt 不能为内网 ip 生成证书,这时要为 nginx 的公网域名生成证书,然后给网关使用。对于证书的定时更新,可以采用 rsync 命令同步证书文件,在 k8s 环境下则能将 Secret 挂载到各个网关程序的容器内。
现代云原生的做法则是用 Envoy Proxy 或 Istio 来做负载均衡甚至接管每个服务进出的流量,如果用 Envoy 来做负载均衡,则证书最好放在 Envoy 层,专业的说法叫” TLS 终结”,Envoy 的优点在于对业务没什么侵入性,只需修改 gRPC 连接的 URL 即可,但代价是配置比 nginx 复杂的多。
zstd(Zstandard)是由 Facebook 开发的一种无损压缩算法,兼具高压缩速度、高压缩率、低 CPU 开销的优点,看了下使用起来也挺方便,聊胜于无。
在协议层方面,gRPC 的 Stream 功能应该能派上用场,客户端和网关的交互,在业务协议外需要再包一层,正好对应了 Stream 只能用一个消息类型,这个消息类型应该包括协议号和字节数组类型的 payload ,这样就可以通过协议号路由到对应的服务上去,将字节数组转为对应的 proto 进行处理,想象非常美好,这一步可以搭配代码生成减少更多机械化的操作。
总的来说,方案有额外的运维成本,但这种诉诸外物的做法才有“现代”的醍醐味。但另一方面说,一套服务器程序,如果非要先安装一整套环境才能跑起来,我觉得也是挺不合格的,应该既要支持复杂的服务编排,也要能在本地开箱即用,要做到这点很难。
网关的雏形,这里只有客户端和网关层的通信部分,但还有网关与业务服务器的交互,网关自己的集群处理,这里可能涉及到分布式的设计,以后想到了再补充。
2025-05-16 补充:
在传统做法下,后端业务服务与网关的交互,是用配置中写上网关的地址,然后在后端服务启动时,主动向网关进行注册,这样网关才能持有业务服的连接,但在网关本身有多个的情况下,就需要考虑网关自身可能要做一套主从或者集群管理,业务服不可能在配置中写上所有的网关地址,我之前项目的做法是起了另一个 http 服务器用来被网关注册,然后业务服请求这个 http 服务器获取所有网关地址后全部连接上,听上去真的很套娃。
这相当于自己实现了一个中心服务发现系统,如果自己实力不够,最好还是拥抱现代云原生的方案:使用 k8s 或 consul 之类的服务发现系统,业务服注册到服务发现系统,网关从服务发现系统拉地址,再连接后端服务。
当然,自己写 demo 的情况下,一个网关,多种业务服,直接写死到配置文件就可以了。