当前位置: 首页 > 工具软件 > Open Match > 使用案例 >

open-match匹配流程

鞠隐水
2023-12-01

open-match匹配流程

(金庆的专栏 2019.1)

https://github.com/GoogleCloudPlatform/open-match

open-match 是一个通用的游戏匹配框架。
由游戏提供自定义的匹配算法(以docker镜像的方式提供)。

分为多个进程,各进程之间共享一个 redis.

  • 前端, 接收玩家加入 redis,成功后通知玩家房间服地址
  • 后端,设置一局游戏的匹配规则,设置房间服地址
  • MMFOrc,启动匹配算法(MMF)
  • MMF, 自定义匹配算法,读取 redis 获取玩家,匹配成功就将结果写入 redis. 仅匹配一局就退出。

游戏服中连接 open-match 的前端与后端的进程,分别称为 frontendclient 和 Director。
输入分2部份,一是玩家信息,二是对局信息。
Director 向后端输入对局信息,就会收到一个接一个的对局人员列表.
Director 需要为每个对局开房间,然后通知后端房间地址。
后端将房间地址写入 redis, 然后前端读取到房间地址,就通知 frontendclient,让玩家进入房间。

test/cmd/frontendclient

模拟大厅服或组队服,连接前端API, 请求匹配玩家/队伍。成功后将收到房间服(DGS)的地址(Assignment)。

Player 实际上是一个队伍,其中ID字段是用空格分隔的多个ID.
虽然参数类型都是 Player, CreatePlayer() 参数为整个队伍,而 GetUpdates() 参数是单个玩家。

main() 中创建多个玩家,每个玩家调用 GetUpdates() 以获取结果,go waitForResults() 中处理结果。
waitForResult() 读取流中的匹配结果,压入 resultsChan(但好像 resultsChan 仅用于打印)。
所有玩家合并到 g 实例中,然后调用 CreatePlayer() 请求匹配。

cleanup() 调用 DeletePlayer() 来删除匹配请求,不仅需删除整个队伍,也需要删除单个玩家。

好像最后取结果没取对地方,应该从 resultChan 中获取 Assignment, 并用该地址 udpClient().

看了该示例就可以理解 frontend.proto

examples/backendclient

MatchObject.Properties 是从 testprofile.json 读取的,应该改名为 Profile 是否更好点?
pbProfile 是 MatchObject,Profile 等同于 MatchObject?
Profile 的定义是 MMF 所需的所有参数。
pbProfile.Properties = jsonProfile 重复了2遍。

ListMatches()列出这个Profile的所有匹配。
收到一个匹配后,须用CreateAssignments()将房间服地址, 称为 Assignment, 发送到所有游戏客户端。

cmd/frontendapi

CreatePlayer() 将 Player 对象写入 redis, 键值为 Player.Id, 类型为 HSET。
对 Player 的每个 attribute,添加到 ZSET 中去。
此处 Player 是一组玩家。

GetUpdates() 每隔2s读取redis, Player数据有变化时就发送。此处 Player 是单个玩家。

如果CreatePlayer()中队伍只有一个玩家,
则写入的Player与GetUpdates()中读取的玩家是同一个redis键。

cmd/backendapi

CreateMatch() 中 profile 类型为 MatchObject, 是一个比赛的限制条件。
profile 先写入 redis, 键为 profile.Id.
requestKey := xid() + "." + profile.Id,
并将 requestKey 加入 redis 集合 “profileq”。
然后每2s查询 redis, 看是否有 requestKey 键出现,并返回该值。

ListMatch() 每2s调用一次 CreateMatch().

DeleteMatch() 仅仅删除 Id 这个键。

CreateAssignments() 为多个队伍设置Assignment, 即房间地址。
遍历所有Roster中的Player对象,在redis中设置Assignment.
(Assignment 更改后,会触发前端更新。)
将所有 Player.Id 从 “proposed” 移到 “deindexed”,这两个是 ZSET, 分值为加入时间。
Roster 应该是比赛中的阵营,如红方,蓝方,每个阵营中可有多个队伍。

DeleteAssignments() 仅仅遍历所有 Player 对象来删除 Assignment 字段。

cmd/mmforc

匹配流程是由 mmforc (matchmaking function orchestrator) 控制的。

mmforc 每秒从 redis 的 profileq 中取出 100 个成员, 其中 profileq 是个set类型,
使用命令为SPOP profileq 100.

对每个 profile, 创建一个 k8s 任务:

	// Kick off the job asynchrnously
	go mmfunc(ctx, profile, cfg, defaultMmfImages, clientset, &pool)

每隔10s, 还有所有匹配任务都完成后,需要 checkProposals, 即创建 evaluator 任务。

profileq 中的元素 profile 为字符串,matchObjectID.profileID。
以 profileID 为键,可以从 redis 读取 profile 的内容, profile 是个 MatchObject 对象。

profile 的内容为 json 串,其中 “jsonkeys.mmfImages” 为 mmf (matchmaking function) 镜像。

如果profile读取失败,或者 mmfImages 为空,则使用默认的镜像。mmfImages 未来会支持多个镜像。

通过 MMF_* 环境变量传入各种参数.

mmf

示例:examples\functions\golang\manual-simple

从环境变量 “MMF_PROFILE_ID” 解析出 profileID, 并向 redis 查询(HGETALL) profile,HSET 类型。

从 profile 中取 pools 字段,即匹配条件。
pools 分为多个 pool, 每个 pool 中有多个 filter, 每个 filter 向 redis 取符合的 Player.

profile 用到以下字段:

  • “properties.playerPool”
    json串,是一些过滤条件,如“mmr: 100-999”
  • “properties.roster”
    json串, 是多个队伍大小,如 “red: 4”

示例见:examples\backendclient\profiles\testprofile.json

简单匹配过程

simple mmf 的匹配过程如下:

  1. 从 redis 查询 profile,获取过滤条件和各队伍大小
  2. 每个过滤条件向 redis 查询,所有结果的交集为可选成员
  3. 去除 ignoreList, 即最近 800s 内已匹配成功的成员,即 proposal 和 deindexed ZSET 列表。
  4. 如果可选成员个数太小,则 insufficient_players 并退出
  5. 分配各个队伍成员
  6. 向 redis 记录结果

结果

profile 中添加 roster,即各阵营成员名单,存入 prososalKey.
保存不分队伍的成员名单。
然后向 “proposalq” 添加 prososalKey

细节

poolRosters 以 (pool名, filter attribute) 为键,值为 Player ID 列表.
保存从 redis 查询的符合条件的 Player ID.

overlaps 以 pool 名为键,保存符合该pool中所有filter的 Player ID 列表,去除 ignore list.

rosters 是 profile 中的 “properties.rosters” 字段。不知何用?
遍历 rosters, 为每个阵营的每个player找到对应pool的PlayerID, 保存到 mo.Rosters.
其中 profileRosters 好像没用。

 类似资料: