基于 gRPC 的网关层的想法

05/13/2025    gamedev golang

最近看了个 gRPC 的入门,本着学习(水commit)的目的,自己写个 gRPC 的 demo.

然后被 Java 毒害的程序员的通病就犯了: 开始各种往上面堆逻辑,想造个功能完备的框架出来。

以下是使用 gRPC 做游戏服务器的一点想法,记录下来,看看有没有机会慢慢实现。

使用 vtprotobuf 插件生成 pb 代码,解决反射的性能问题。

可以生成 proto 对应的 service 结构体和代码,反正是结构化的代码,能用代码生成就用代码生成。

ChatGPT推荐了 gRPC + TLS + zstd 的方案。

之前在做长链接游戏服务器的网关的流程是这样的:

  1. 服务器收到客户端连接后,生成 RSA 公钥私钥,将公钥发送给客户端
  2. 客户端收到服务器的公钥后,自己也生成 RSA 公钥私钥,将公钥发给服务端
  3. 服务器收到客户端的公钥后,随机生成一个 key 用于 RC4 加密,将这个 key 用服务器私钥加密后发给客户端
  4. 客户端收到加密的 key 后,用服务器的公钥解密,设置到 RC4 解码器,客户端自己也随机生成个 key 用客户端私钥加密发给服务器
  5. 服务器收到加密的 key 后,用客户端的公钥解密,设置到 RC4 解码器,到这里相当于握手结束,可以开始发送业务协议了

为什么用 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 的情况下,一个网关,多种业务服,直接写死到配置文件就可以了。