- 游戏开发
- 云原生
【免费下载链接】agones
Dedicated Game Server Hosting and Scaling for Multiplayer Games on Kubernetes
导读
本篇指南围绕 Agones(Kubernetes 上的专用游戏服务器托管与扩缩容系统)的 Client SDK 展开,系统讲解游戏服务器如何通过 SDK 与 Agones 协同工作:从 SDK 的架构定位、连接方式,到Ready()、Allocate()、Reserve()、Shutdown()等核心生命周期函数,再到标签注解、计数器(Counters)与列表(Lists)等高级能力。读完本文,你将掌握各语言 SDK 的统一调用模型、如何利用 SDK 服务器(SDK Server)与本地开发工具,以及如何自行编写或验证一个全新的 SDK。
SDK 在 Agones 中的角色
客户端 SDK 是游戏服务器与 Agones 建立协作的必备集成点。一个游戏服务器若要被 Agones 托管和调度,必须通过 SDK 上报自身状态(就绪、健康、分配、关闭等)。目前 Agones 官方支持的 SDK 覆盖以下语言/平台:
- Unreal Engine
- Unity
- C++
- C#
- Node.js
- Go
- Rust
- Python
- REST
此外,社区还维护了一些第三方 SDK,可参考 Third Party Content 一节。
从架构上看,这些 SDK 都是围绕 gRPC 生成客户端的相对较薄的封装;在 gRPC 客户端生成和编译支持不佳的语言上,则实现 REST API(由 grpc-gateway 暴露)。SDK 连接的是 Agones 协调部署在游戏服务器所在 Kubernetes Pod 内的一个小型进程——即SDK Server(sidecar)。这种"薄封装 + sidecar"的设计意味着未来支持更多语言的成本极低(欢迎通过 Pull Request 贡献)。
得益于上述架构,即便不启动完整的 Kubernetes 集群,你也可以借助 本地开发工具 在本地直接与 SDK 对接调试。
连接 SDK Server:端口与环境变量
从 Agones 1.1.0 起,SDK Server 监听 gRPC 与 HTTP 请求的端口可配置,这在默认端口与游戏服务器自身所需端口冲突时非常有用。
Agones 会在所有游戏服务器容器上自动设置以下环境变量(定义见 pkg/gameservers/controller.go):
| 环境变量 | 作用 | 默认值 |
|---|---|---|
AGONES_SDK_GRPC_PORT | gRPC 服务器监听端口 | 9357 |
AGONES_SDK_HTTP_PORT | grpc-gateway 监听端口 | 9358 |
各语言 SDK 会自动发现并连接环境变量中指定的 gRPC 端口。如果你的游戏服务器需要使用 REST 客户端,强烈建议从环境变量中读取端口,否则当 SDK Server 被配置为使用非默认端口时,REST 客户端将无法与之通信。
SDK Server 的核心服务定义位于 proto/sdk/sdk.proto,其 gRPC 方法与 REST 路径映射如下:
| gRPC 方法 | HTTP 方法/路径 | 说明 |
|---|---|---|
Ready | POST /ready | 标记就绪 |
Allocate | POST /allocate | 自分配 |
Shutdown | POST /shutdown | 关闭 |
Health | POST /health(双向流) | 健康心跳 |
GetGameServer | GET /gameserver | 获取 GameServer 配置 |
WatchGameServer | GET /watch/gameserver | 订阅 GameServer 变更 |
SetLabel | PUT /metadata/label | 设置标签 |
SetAnnotation | PUT /metadata/annotation | 设置注解 |
Reserve | POST /reserve | 保留指定时长 |
函数总览与异步语义(重要前提)
虽然每种语言的 SDK 都有各自惯用语法,但所有 SDK 都实现了以下核心职责函数,用于改变 GameServer 状态或设置:
Ready()Shutdown()SetLabel()SetAnnotation()Allocate()Reserve()Beta().SetCounterCount()Beta().IncrementCounter()Beta().DecrementCounter()Beta().SetCounterCapacity()Beta().AppendListValue()Beta().DeleteListValue()Beta().SetListCapacity()
提示:最终一致性与异步批处理Agones 和 Kubernetes 本身都是"最终一致、自愈"的系统。因此,上表所列的所有状态变更函数的调用,都会被 SDK Server按间隔批处理、异步排队(既为性能也为韧性)。其结果是:调用这些函数后,状态变更不会立即生效。如需验证变更结果,请通过
WatchGameServer()的回调来观察目标状态是否已达成。
这一语义在源码中有直接体现:以 Go 实现为例,pkg/sdkserver/sdkserver.go 中Ready()、Allocate()、Shutdown()均不直接改状态,而是通过enqueueState()将状态变更请求投入 workerqueue,由后台 worker 异步执行updateState();SetLabel()/SetAnnotation()同样只是把键值写入本地缓存并入队,最终由updateLabels()/updateAnnotations()批量以 JSON Patch 形式应用到 Kubernetes 上的 GameServer 记录。
生命周期管理
Ready()
通知 Agones 该游戏服务器已可接受玩家连接。一旦游戏服务器调用Ready(),Kubernetes 中的 GameServer 记录将进入Ready状态,其公网地址(Address)与连接端口等细节也会被填充。
Agones 倾向于在一局游戏结束后调用Shutdown()来删除 GameServer 实例;但如果你希望将一个已Allocated的 GameServer重新变为Ready以复用,也可以再次调用本方法完成状态回迁。
Health()
发送一个 ping 表示游戏服务器存活且健康。若未能在配置的阈值内持续发送心跳,GameServer 将被标记为Unhealthy。健康检查的完整配置可参考 examples/gameserver.yaml:
health: # 是否禁用健康检查,默认 false,可设为 true disabled: false # 容器启动后多少秒开始健康检查,默认 5 秒 initialDelaySeconds: 5 # 健康检查周期(秒),默认 5 periodSeconds: 5 # 连续失败多少次判定为不健康,默认 3 failureThreshold: 3从源码看,pkg/sdkserver/sdkserver.go 中Health()持续接收流式心跳并记录"最后一次心跳时间"(touchHealthLastUpdated()),由后台定时器checkHealth()依据阈值判断是否进入Unhealthy。
Reserve(seconds)
在某些匹配(matchmaking)场景中,需要保证一个 GameServer不被删除,但又不触发 FleetAutoscaler 扩容——这正是Reserve(seconds)的用途:
Reserve(seconds)将 GameServer 移入Reserved状态,持续指定秒数(0 表示永久保留),到期后自动回到Ready状态;- 处于
Reserved状态期间,GameServer不会被缩容删除,也不会因 Fleet 更新而删除,同时无法被 GameServerAllocation 分配; - 典型用法:游戏服务器进程需要向外部系统(如匹配器)注册自己"在某个时间段内可被用于对局",会话开始后再调用
SDK.Allocate()标记玩家已在其上活跃。
源码层面,pkg/sdkserver/sdkserver.go 的Reserve()会记录保留时长(gsReserveDuration),并设置Status.ReservedUntil时间戳,同时启动resetReserveAfter()定时器,到期后自动复位回Ready。
注意:调用其他状态变更类命令(如Ready或Allocate)会关闭定时器——Ready将 GameServer 复位到Ready状态,Allocate则直接将其提升为Allocated状态。
Allocate()
某些匹配器/匹配策略需要游戏服务器自己标记为Allocated,此时可使用本 SDK 功能。
需要理解的是:由于异步批处理的存在,调用后 GameServer有可能并未真正进入Allocated状态(请参考上文"函数总览与异步语义"的说明)。无论 GameServer 是否已处于Allocated状态,Allocate()都会在 GameServer 上写入agones.dev/last-allocated注解,值为 RFC3339 格式的时间戳——该注解键在源码中定义为LastAllocatedAnnotationKey(见 pkg/gameserverallocations/allocator.go)。
时钟同步注意:如果同时混用SDK.Allocate()与 GameServerAllocation,当 Agones 控制器与游戏服务器 Pod 的时钟不同步时,agones.dev/last-allocated时间戳可能出现回退。
建议:除上述特殊场景外,其余场景优先使用 GameServerAllocation。它让 Agones 掌控 GameServer 在集群内的打包(packing)调度;而使用
Allocate()则把控制权让渡给外部服务,后者通常掌握的信息不如 Agones 全面。
Shutdown()
通知 Agones 关闭当前运行的游戏服务器:GameServer 状态将被置为Shutdown,底层 Pod 进入 Terminated 流程。
以下几点值得留意:
- 建议阅读 Kubernetes 官方文档中关于 Pod 终止流程 的内容,理解终止过程及相关配置;
- 经验法则:游戏服务器进程收到来自 Kubernetes 的TERM 信号(即底层 Pod 进入终止状态)时,应实现优雅关闭;
- 如果在调用
SDK.Shutdown()后又执行类似System.exit(0)的操作,游戏服务器容器可能会短暂重启,这与 健康检查策略 的行为一致; - 如果 SDK Server 在调用
SDK.Shutdown()之前收到了 TERM 信号,SDK Server 会保持存活terminationGracePeriodSeconds时长,直到SDK.Shutdown()被调用。
副作用容器模式(Beta,需开启SidecarContainers特性门控)
启用SidecarContainers特性门控后,Agones SDK Server 将以同一 Pod 内的 sidecar 容器运行,容器重启与健康检查规则也会相应简化:
- 由于 SDK Server 是 sidecar 容器,且 GameServer Pod 的默认
PodRestartPolicy为Never(除非另行配置),主容器默认不会重启; - 主容器被终止时,SDK Server 也随之终止,因此 SDK Server 在整个 GameServer Pod 主容器的生命周期内都是可访问的。
配置获取
GameServer()
返回底层 GameServer 的配置与状态信息(大部分字段),例如:健康检查配置、GameServer 当前分配到的 IP 与端口等。
由于 GameServer 包含整个 PodTemplate 中的message GameServer定义,其结构包括:
ObjectMeta:name、namespace、uid、resource_version、generation、creation/deletion timestamp(Epoch 秒)、annotations、labels;Spec.Health:disabled、period_seconds、failure_threshold、initial_delay_seconds;Status:state、address、addresses、ports、players(Alpha/PlayerTracking)、counters、lists(Beta/CountsAndLists)。
该字段子集在源码 pkg/sdkserver/sdk.go 的convert()函数中由 Kubernetes GameServer CRD 对象映射而来,其中 Counters/Lists/Players 等字段仅在对应特性门控启用时填充。
如果你认为某些字段缺失,欢迎 提交 issue 或 Pull Request。
WatchGameServer(function(gameserver){...})
每当底层 GameServer 配置更新时,执行传入的回调并携带最新的GameServer详情。可用于追踪GameServer > Status > State的变化、metadata(标签和注解)的变更等。
标签与注解是外部向运行中的游戏服务器进程传递信息的有效手段——结合WatchGameServer(),你可以从 Pod 外部(例如通过 GameServerAllocation 的 applied metadata 机制)向游戏进程推送数据,进程内通过 watch 回调实时感知。返回对象的字段子集同上(以sdk.proto的message GameServer为准)。
元数据管理
SetLabel(key, value)
为 Kubernetes 中存储的底层 GameServer 记录设置 Label。
为了隔离,key会被自动添加"agones.dev/sdk-"前缀,原因有二:
- 可辨识性:前缀让开发者永远清楚某个值是否可能来自/会被客户端 SDK 修改,类似编程语言中
private与public作用域的区别——Agones SDK 只允许写入 GameServer 上标签与注解集合的一部分; - 攻击面收敛:若 GameServer 容器被攻破,前缀能有效缩小可被篡改的范围。游戏容器通常对外暴露,且 Agones 项目无法控制其内部运行的二进制,因此在"限制暴露面"与"额外开发摩擦"之间,选择限制暴露面是值得的。
警告:字符限制Kubernetes 对标签键与值有字符集限制(详见 标签语法与字符集 中
SetLabel()会对键做validation.IsQualifiedName校验、对值做IsValidLabelValue校验,非法输入会直接返回InvalidArgument错误。
设置 GameServer 标签,适合让运行中游戏进程的信息通过 Kubernetes API可观测、可检索。
SetAnnotation(key, value)
为底层 GameServer 记录设置 Annotation 值。
与SetLabel()相同,key会自动添加agones.dev/sdk-前缀(原因同上)。隔离尤为重要,因为Agones 自身会在内部处理中大量使用 GameServer 上的注解——前缀隔离可以避免 SDK 写入与 Agones 内部注解发生冲突。设置注解适合让运行中游戏进程的信息通过 Kubernetes API 可观测(但不一定可检索)。
前缀常量在源码中定义为metadataPrefix = "agones.dev/sdk-"(见 pkg/sdkserver/sdk.go)。
计数器与列表(Beta,需开启CountsAndLists特性门控)
Counters与Lists为 SDK 提供了灵活追踪玩家、房间、会话等实体的能力:
- Counter/List 的声明键与默认值定义于
GameServer.Spec.Counters与GameServer.Spec.Lists(见 agones.dev/v1.GameServerSpec); - 修改后的 Counter/List值与容量会更新到
GameServer.Status.Counters与GameServer.Status.Lists(见 agones.dev/v1.GameServerStatus)。
关于一致性的说明SDK 出于性能原因每 1 秒批量执行一次变更操作;但由于这些值在 SDK Server sidecar 进程内被本地追踪,通过 SDK 写入再通过 SDK 读取的值在 SDK 内是原子准确的。 而通过 Allocation 或 Kubernetes API 对
GameServer.Spec.Counters/Spec.Lists的修改,经 SDK 读取时是最终一致的。同时,由于 SDK Server 异步批处理Status.Counters/Status.Lists的更新,若你同时通过 SDK 与 Allocation/Kubernetes API 两路更新GameServer.status,批处理可能静默地将部分值截断到该 Counter/List 的容量上限。
共同约束:以下所有函数若传入的key未在GameServer.Spec.Counters(或Spec.Lists)中预先定义,都会返回错误。
Counters
注意:Counters 的默认容量预设为 1000。建议避免将容量配置为
max(int64),否则可能引发 JSON Patch 操作问题(参见 issue #3636)。
Beta().GetCounterCount(key):返回GameServer.Status.Counters[key].Count与 SDK 待批量处理值中最新的一个;Beta().SetCounterCount(key, amount):将Counters[key].Count设为指定值。该操作覆盖任何先前的值,且新值不能超过 Counter 容量;Beta().IncrementCounter(key, amount):按传入的非负值递增 Count。若操作时 Counter 已达容量,返回错误且不发生递增;Beta().DecrementCounter(key, amount):按传入的非负值递减 Count。若 Count 已为 0,返回错误;Beta().SetCounterCapacity(key, amount):将最大容量设为传入的非负值。容量为 0 表示无上限;Beta().GetCounterCapacity(key):返回Counters[key].Capacity与 SDK 待批量处理值中最新的一个。
Lists
Beta().AppendListValue(key, value):将指定字符串追加到Lists[key].Values。若字符串已存在或列表已达容量,返回错误;Beta().DeleteListValue(key, value):从Lists[key].Values中移除指定字符串。若字符串不存在,返回错误;Beta().SetListCapacity(key, amount):设置列表最大容量。容量值必须在 0 到 1000 之间;Beta().GetListCapacity(key):返回Lists[key].Capacity与 SDK 待批量处理值中最新者;Beta().GetListValues(key):返回Lists[key].Values与 SDK 待批量处理值数组中最新者;Beta().ListContains(key, value):便捷函数,判断指定字符串是否存在于GetListValues(key)的结果中;Beta().GetListLength(key):便捷函数,返回GetListValues(key)结果的长度。
对应 gRPC 服务端实现可参考 pkg/sdkserver/sdkserver.go 中的GetCounter、UpdateCounter、GetList、UpdateList、AddListValue、RemoveListValue等方法,以及 proto/sdk/beta/beta.proto 中的消息定义。
自行编写 SDK
如果现有 SDK 不满足你的语言/平台需求,有两条可行路径:
gRPC 客户端生成
如果目标语言的 gRPC 客户端生成支持良好,则从 proto/sdk 目录下的 proto 文件生成客户端,并参考现有 sdks 目录中各语言封装(wrapper)的实现方式,简化用户与 SDK Server 的交互。
REST API 实现
如果目标语言对 gRPC 客户端生成支持不佳,或有其他复杂因素,可通过REST(HTTP+JSON)接口实现 SDK——既可以手写,也可以基于 sdks/swagger 中的 Swagger/OpenAPI 规范生成(gRPC 服务的 HTTP 路径映射表见上文"连接 SDK Server"一节)。
如果你构建了可供社区使用的东西,欢迎提交 Pull Request!
SDK 一致性测试(Conformance Test)
仓库提供了一个SDK Server Conformance 检查工具:它会在本地运行 SDK Server,并记录你的客户端执行的全部请求,随后与期望请求集合比对。
测试步骤:
- 编写一个简单的 SDK 测试客户端,使用你 SDK 中的所有方法;
- 为了验证客户端能接收到合法的 GameServer 数据,你的二进制程序还应做到:将
Label值设置为GameServer()调用返回的创建时间戳(creation timestamp),将Annotation值设置为 Watch GameServer 回调收到的 GameServer UID; - 测试客户端必须覆盖的完整端点列表为:
ready,allocate,setlabel,setannotation,gameserver,health,shutdown,watch(从 build/includes/sdk.mk 的DEFAULT_CONFORMANCE_TESTS定义可见,实际默认还额外包含reserve;启用CountsAndLists后还会追加getcounter,updatecounter,setcountcounter,setcapacitycounter,getlist,updatelist,addlistvalue,removelistvalue等测试项。)
在本地运行一致性测试:
SECONDS=30 make run-sdk-conformance-localDocker 容器会在 30 秒后超时,并给出"收到的请求"与"期望请求"的对比结果。
例如运行 Go SDK 的一致性测试:
SDK_FOLDER=go make run-sdk-conformance-test若要为自己的 SDK 添加测试客户端,需要编写sdktest.sh与Dockerfile,目录结构可参考 build/build-sdk-images/go。从 build/includes/sdk.mk 可以看到各语言 SDK 的 conformance 目标均已就绪(如run-sdk-conformance-test-cpp、run-sdk-conformance-test-go、run-sdk-conformance-test-rest等),且run-sdk-conformance-tests可一键批量运行全部语言测试。
从源码构建 SDK 相关二进制
如需从源码构建二进制,make目标build-agones-sdk-binary会为**所有受支持的操作系统(64 位 Windows、Linux 与 macOS)**编译必要二进制。从 build/Makefile 可见其实际由 linux-amd64、linux-arm64、windows、darwin-amd64、darwin-arm64 等子目标组合而成。
编译完成后,二进制文件位于 cmd/sdk-server 目录下的bin文件夹中。
更多开发、测试与构建细节,请参见 build/building-testing.md。
- 游戏开发
- 云原生
【免费下载链接】agones
Dedicated Game Server Hosting and Scaling for Multiplayer Games on Kubernetes
相关推荐
超强Agones SDK生态:多语言游戏服务器集成终极指南
超强Agones SDK生态:多语言游戏服务器集成终极指南 还在为不同编程语言的游戏服务器集成而头疼?Agones SDK生态为你提供一站式解决方案!无论你的技
游戏开发云原生MyBatis Generator与Maven集成:自动化构建流程全攻略
MyBatis Generator与Maven集成:自动化构建流程全攻略 MyBatis Generator(简称MBG)是一个强大的代码生成工具,能够根据数据
代码生成开发工具如何用AI一键生成专业演示文稿:PPT Master完整指南
如何用AI一键生成专业演示文稿:PPT Master完整指南 在信息爆炸的时代,一份出色的演示文稿能让你的观点脱颖而出。PPT Master是一款AI驱动的SV
AI 技能人工智能AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考