> For the complete documentation index, see [llms.txt](https://docs.edgegap.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.edgegap.com/zh/learn/fu-wu-qi-liu-lan-qi.md).

# 服务器浏览器

快速上手服务器浏览器，并探索适用于各种类型的示例场景。

服务器浏览器是一项托管服务，用于 [部署](/zh/learn/bian-pai/deployments.md#match-bound) 和 [持久化](/zh/learn/bian-pai/chi-jiu-hua.md) 服务器：

* **帮助玩家搜索并加入合适的服务器** ，基于容量、延迟或游戏参数；
* **预热新服务器** 以便按规模为全球用户提供服务，并防止令人沮丧的排队；
* **简化服务器运维** 包括更新、重启、持久化、Mesh 等。

{% hint style="success" %}
如果你希望基于严格规则匹配玩家，而不允许选择服务器，可以考虑 [匹配](/zh/learn/pi-pei.md).
{% endhint %}

## ✔️ 准备工作

**测试此服务完全免费，无需信用卡。**

免费额度允许在我们的共享测试集群上每次重启后最多运行 3 小时。

本教程假定您已经：

* [理解 Edgegap 的部署模型](https://docs.edgegap.com/zh/learn/pages/5e7e2169ca3822647d4607dfc1d3487ebcc0836c#id-1.-just-in-time-deployment-dedicated-servers),
* 已在 Edgegap 上发布您的服务器应用（[Unreal Engine](/zh/unreal-engine.md), [Unity](/zh/unity.md)),
* 已从游戏客户端成功连接到您在 Edgegap 上的服务器。

### 功能与流程

<figure><img src="/files/f0ec3b2982968b574e907b650e12ae4068dce02b" alt=""><figcaption><p>服务器浏览器：流程与层次结构</p></figcaption></figure>

服务器浏览器提供两项主要功能：

[#start-browsing](#start-browsing "mention") 与游戏客户端配合用于：

* 发现并找到合适的服务器实例，查看席位，并预留可用容量。
* 在实例席位中预留座位，获取连接详情，并连接到服务器。
* 使用以下方式在部署中对玩家连接进行身份验证 联合身份认证[^1].
* 更新实例席位的可用容量和/或元数据，以修改发现条件。

[#automated-scaling](#automated-scaling "mention") （可选）与扩缩容策略配合用于：

* 监控可用的服务器实例、席位、容量——按区域和/或其他条件。
* 通过预热或即时扩容部署服务器以增加容量。
* 通过适用于演示、更新、测试、QA、锦标赛等场景的特殊策略自动化运维。

{% hint style="info" %}
发布后， **你的服务器浏览器需要 24/7 运行** 以确保全球各地的玩家都能加入服务器。
{% endhint %}

## ▶️ 开始浏览

了解服务器/玩家生命周期及其职责，以确保高效使用服务器。

{% embed url="<https://youtu.be/P8xWrD4UCxg>" %}

### 身份验证

所有请求必须发送一个 `Authorization（授权）`  HTTP 头并包含您的密钥 **认证令牌：**

<pre><code>授权： <a data-footnote-ref href="#user-content-fn-2">xxxxxxxx-e458-4592-b607-c2c28afd8b62</a>
</code></pre>

{% hint style="warning" %}
**请将您的令牌保密并妥善保存！Edgegap 员工绝不会向您索要令牌。**
{% endhint %}

服务器浏览器会自动生成两种令牌：

* **服务器令牌** - 适用于 [服务器 API](#server-lifecycle) 方法，可 [作为应用版本变量注入](/zh/learn/bian-pai/application-and-versions.md#injected-variables).
  * 可访问所有 API 方法，适用于测试、运维或自定义编排。
* **客户端令牌** - 适用于 [监控 API 和席位预留 API](#player-lifecycle) 由游戏客户端使用。
  * 我们建议将此令牌存储在第三方密钥存储中，以便更轻松地轮换令牌。

### 发现实例

{% hint style="warning" %}
**新的** [部署](/zh/learn/bian-pai/deployments.md) **必须创建一个新的实例** 在初始化时，用于跟踪新增容量。
{% endhint %}

{% hint style="info" %}
参见 [#automated-scaling](#automated-scaling "mention") 了解扩缩容策略，并自动开始部署。
{% endhint %}

**所需信息** 对于每个服务器实例包括：

* 初始化实例时至少定义一个席位，
* 服务器连接详情——URL、IP、端口信息和位置。

**可选的自定义元数据参数** 用于玩家筛选、排序和浏览；例如：

* 席位信息——团队容量和团队特定元数据（例如团队名称），
* 名称和标签——可自定义、唯一、可读且可搜索的标签；
* 兼容性数据——服务器版本或支持的客户端版本；
* 延迟限定词——城市和区域标识符，以及分配的 [Ping 信标](/zh/learn/bian-pai/ping-beacons.md) 详情；
* 游戏参数——关卡/场景/地图、游戏模式、难度、使用的 Mod；
* 任何其他自定义参数，帮助玩家筛选并找到合适的服务器。

{% hint style="info" %}
上面的元数据参数只是示例，你可以根据需要定义任意数量的参数。
{% endhint %}

{% hint style="success" %}
要序列化嵌套对象，可以尝试在键中将其访问路径编码为 `"object.child.property"`.
{% endhint %}

服务器可以 **随时更新实例或席位元数据** 以修改其可发现性条件。更新元数据时，所有已索引的键都必须提供有效值（即使未修改）。

**服务器实例必须定期发送保活心跳** 以验证其持续可用性，并防止玩家加入已崩溃或离线的服务器。在配置的过期时间内如果缺少心跳，将自动删除该实例及任何待处理的席位预留。

{% hint style="info" %}
参见 [持久化](/zh/learn/bian-pai/chi-jiu-hua.md) 用于管理持久化世界状态，并 [应用与版本](/zh/learn/bian-pai/application-and-versions.md#active-caching) 用于更快部署。
{% endhint %}

### 分配容量

实例和席位容量可以通过两种方式分配，可单独使用或组合使用：

* [#auto-assigned-reservation](#auto-assigned-reservation "mention") 选择使用特定扩缩容策略启动的服务器，
* [#search-and-browse](#search-and-browse "mention") 让玩家定义筛选条件并浏览合适的服务器供其选择。

{% hint style="success" %}
我们建议从 [#auto-assigned-reservation](#auto-assigned-reservation "mention") 开始，因为它更简单。
{% endhint %}

#### 自动分配预留

{% hint style="info" %}
如果你希望 **自动选择服务器**，基于区域容量。
{% endhint %}

玩家可以创建自动分配预留，只需提供玩家 ID 和扩缩容策略名称。服务器浏览器会自动找到一个具有足够可加入容量的实例席位并预留座位，立即返回实例连接详情。

如果没有适合此预留的实例席位，则响应：

* **状态码表明该策略是否正在扩容** 并将增加更多容量，
* **头部 `Retry-After`  表示重试前的等待时间（秒）**，如果可重试。

一旦预留完成，你可以直接跳到 [#connect-to-server](#connect-to-server "mention").

#### 搜索与浏览

{% hint style="info" %}
如果你希望 **向用户展示服务器列表并允许自定义预留**.
{% endhint %}

玩家可以列出服务器实例并 [对结果进行分页](#pagination) 以找到他们想加入的服务器。

实例和席位可以使用内置参数或 [已索引元数据](#configuration):

<table><thead><tr><th width="400">属性</th><th width="140">数据类型</th><th width="105">实例</th><th width="105">席位</th></tr></thead><tbody><tr><td><code>request_id</code></td><td><code>字符串</code></td><td>✅</td><td>❌</td></tr><tr><td><code>total_joinable_seats</code>, <code>total_available_seats</code></td><td><code>整数</code></td><td>✅</td><td>❌</td></tr><tr><td><code>name</code></td><td><code>字符串</code></td><td>❌</td><td>✅</td></tr><tr><td><code>available_seats</code>, <code>reserved_seats</code></td><td><code>整数</code></td><td>❌</td><td>✅</td></tr><tr><td><code>created_at</code>, <code>updated_at</code></td><td><code>字符串</code></td><td>✅</td><td>✅</td></tr><tr><td><code>metadata.{index}</code> （自定义）</td><td><code>字符串</code>, <code>整数</code>, <code>浮点数</code>, <code>布尔值</code></td><td>✅</td><td>✅</td></tr></tbody></table>

可用的筛选运算符取决于被筛选属性的数据类型：

<table><thead><tr><th width="125">参数</th><th width="135">运算符</th><th>示例过滤器（基于简单示例）</th></tr></thead><tbody><tr><td><code>字符串</code></td><td><p><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  或 <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a> 或 </p><p><a data-footnote-ref href="#user-content-fn-5"><code>lt</code></a>  或 <a data-footnote-ref href="#user-content-fn-6"><code>le</code></a> 或 </p><p><a data-footnote-ref href="#user-content-fn-7"><code>gt</code></a>  或 <a data-footnote-ref href="#user-content-fn-8"><code>ge</code></a>  或<br><code>包含</code></p></td><td><pre><code>?$filter=metadata.custom_name contains 'my game'
and metadata.server_version le '1.1.0'
and metadata.server_version ge '1.0.0'
&#x26;$order=metadata.custom_name asc
</code></pre></td></tr><tr><td><code>整数</code>, <code>浮点数</code></td><td><p><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  或 <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a> 或 </p><p><a data-footnote-ref href="#user-content-fn-5"><code>lt</code></a>  或 <a data-footnote-ref href="#user-content-fn-6"><code>le</code></a> 或 </p><p><a data-footnote-ref href="#user-content-fn-7"><code>gt</code></a>  或 <a data-footnote-ref href="#user-content-fn-8"><code>ge</code></a>  </p></td><td><pre><code>?$filter=metadata.xp_multiplier gt 1.0
&#x26;$order=metadata.xp_multiplier desc
</code></pre></td></tr><tr><td><code>布尔值</code></td><td><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  或 <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a></td><td><pre><code>?$filter=metadata.allows_new_connections eq true
</code></pre></td></tr></tbody></table>

{% hint style="success" %}
按区域和/或城市元数据进行筛选，以便在测量服务器延迟之前缩小选择范围。
{% endhint %}

{% hint style="info" %}
了解基于游标的 [#pagination](#pagination "mention") 以便让用户获取更多结果。
{% endhint %}

#### 预留席位

在加入服务器之前，需要进行席位预留，以确保实例提供足够的可用容量。预留可以包含一组玩家或单个玩家。

联合身份认证：玩家必须在其预留中提供一个唯一的第三方玩家 ID。发送同一个 ID 后， [#connect-to-server](#connect-to-server "mention") 服务器即可验证其身份。

一旦预留成功完成（[200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/200)），玩家应立即尝试连接。待处理的 **预留将在 30 秒后过期（可配置），除非被** 你的服务器确认。

**超过席位可加入座位容量的预留将被自动拒绝** ([409 Conflict](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/409)）。可加入座位是指尚未被其他玩家预留的所有可用座位。

{% hint style="info" %}
服务器可以强制更改任何席位的容量，添加、删除或更新任何席位。 **如果任何待处理预留超过新的可用席位容量，则给定席位的所有预留都将被移除。**
{% endhint %}

### 连接到服务器

一旦玩家找到合适的实例， **便从中获取所需的连接详情** （URL 或 IP， [外部端口](/zh/learn/bian-pai/application-and-versions.md#port-mapping)）。席位预留一旦完成， **玩家即可连接到你部署中的游戏服务器，并传递其玩家 ID**.

{% tabs %}
{% tab title="Unreal Engine" %}
要 **从 PIE（编辑器）连接** 在开发和测试期间，按波浪号键 `~`  并输入 `open {URL}:{port}` 然后等待编辑器加载地图。

{% hint style="success" %}
如果连接失败或出现黑屏，请查阅我们的 [故障排查指南](/zh/unreal-engine.md#troubleshooting-and-faq-1).
{% endhint %}
{% endtab %}

{% tab title="Unity" %}
要 **将你的 Unity 编辑器** 或 **游戏客户端** 连接到你的云部署，输入：

* **部署** **URL** 指向服务器的 IP，通常在 `NetworkManager`  组件中。
* **外部端口** 映射到 [服务器的内部监听端口](https://docs.edgegap.com/learn/advanced-features/application-and-versions#port-mapping)，通常在 Transport 组件中。

{% hint style="success" %}
如果连接超时或出现其他问题，请查阅我们的 [故障排查指南](/zh/unity.md#troubleshooting-and-faq-4).
{% endhint %}
{% endtab %}
{% endtabs %}

要验证新连接， **你的服务器必须发送一条批量预留确认** 请求，其中包含所有新玩家的 ID，并在确认响应中接收信息：

* 将已接受的玩家预留分配到其首选席位，
* 将已过期的玩家预留分配到其首选席位，
* 未知玩家 ID 列表。

你的 **服务器可以决定如何处理每一组玩家** 以及是否允许或踢出/封禁过期或被拒绝的用户。每个 **实例的席位都必须立即更新新的可用座位数** 以确保未来的预留不会超过席位容量。

### 放弃服务器

当玩家离开时，你的服务器必须增加已分配席位的可用座位容量。

{% hint style="success" %}
如果你的游戏设计允许重连窗口，你的服务器可以在更新席位之前等待一段时间。
{% endhint %}

阅读 [持久化](/zh/learn/bian-pai/chi-jiu-hua.md#recovery-objectives) 以防止令人沮丧的持久服务器回滚。

## 🚀 自动扩缩容

服务器浏览器兼容多种不同的自动扩容方式：

* **预热方式** - 严格通过服务器浏览器扩缩容策略启动服务器，
* **即时方式** - 通过 [匹配](/zh/learn/pi-pei.md) 和 [使用服务器浏览器填充](#allocate-capacity),
* **自定义自动扩容器** - 通过自定义游戏后端和 [使用服务器浏览器填充](#allocate-capacity).

以下指南将重点介绍 **使用扩缩容策略进行预热** 作为主要方法。

{% hint style="success" %}
了解如何在 [Unreal Engine](/zh/unreal-engine.md#stop-deployments), [Unity](/zh/unity.md#stop-deployments)中停止部署，或 [使用 API](/zh/docs/api/zhuan-yong-fu-wu-qi.md#delete-v1-self-stop-request_id-access_point_id) 来可靠地管理生命周期。
{% endhint %}

### 监控容量

扩缩容策略会持续刷新你的服务器实例列表（已发现的部署），每隔 [`monitoring_interval`](#user-content-fn-9)[^9] 。每个策略都需要使用相同的 [筛选语法](#search-and-browse) 作为玩家搜索实例时所使用的语法——按区域、容量或其他条件。

你配置的 [`minimum_active_instances`](#user-content-fn-10)[^10]  数量可以被视为以下任一项：

* **固定容量** 你希望始终保持运行的部署数量，
* **预热待命** 部署缓冲，用于掩盖初始化延迟。

#### 固定容量

为具有 [持久化](/zh/learn/bian-pai/chi-jiu-hua.md)的游戏保持固定数量的活跃服务器，尤其是在这类游戏允许玩家进行预置时。 [持久化](/zh/learn/bian-pai/chi-jiu-hua.md#community-servers).

这种策略配置有时也用于质量保证、锦标赛、封闭 Alpha、发行商演示或其他受限容量的活动和运维。

{% hint style="info" %}
扩缩容策略可帮助你自动重启并实时回收崩溃的服务器。
{% endhint %}

#### 预热待命

在玩家需求出现之前提前启动服务器，如果：

* 你正在发布大型版本，并预计在短时间内大量玩家涌入，
* 或者服务器初始化需要超过 30 秒（不包括部署时间[^11]),
* 或者游戏实现了需要分层或环形网络依赖的 Mesh 策略。

### 部署服务器

当受监控的服务器实例数量*s* 低于配置的最小活跃实例数时，将自动启动新的部署。所有部署请求会立即发出，并在每个监控周期后无限重试，直至 [`deployment_registration_period`](#user-content-fn-9)[^9]  经过。

{% hint style="warning" %}
验证新的部署 [执行自动发现并正确创建实例](#discover-instance) 与你的策略筛选条件匹配，或者 **你的策略可能会无限循环并创建大量未使用的部署**!&#x20;
{% endhint %}

策略会以 [私有舰队](/zh/learn/bian-pai/si-you-jian-dui.md) （使用溢出到云）或直接到云的方式启动部署。

可用参数包括（[参见 API 规范](/zh/docs/api/zhuan-yong-fu-wu-qi.md#private-fleets)):

* [**应用程序和版本**](/zh/learn/bian-pai/application-and-versions.md) - 构建版本、资源以及其他编排参数，
* **用户** - 一组用于首选 [服务器部署位置](/zh/learn/bian-pai/deployments.md#regional-standby),
* [**私有主机 ID**](/zh/learn/bian-pai/si-you-jian-dui.md) - 留空表示云端，或指定所需区域内的主机，
* [**标签**](/zh/learn/bian-pai/deployments.md#dashboard-monitoring) - 用策略名称进行标记，以便之后查找使用此策略启动的部署，
* [**环境变量**](/zh/learn/bian-pai/deployments.md#custom-variables) - 向服务器传递自定义参数和密钥，
* [**webhook**](/zh/learn/bian-pai/deployments.md#webhooks-and-postbacks) - 向你的游戏后端（或匹配器）通知部署生命周期事件，
* [**需要缓存位置**](/zh/learn/bian-pai/application-and-versions.md#active-caching) - 如果你只偏好在缓存位置进行更快的部署。

### 示例策略

按需测试和修改这些策略。大多数游戏会使用多个策略。

{% tabs %}
{% tab title="🍀 QA 池" %}
一个简单的策略，用于始终保持一台服务器处于部署状态以供测试。

<pre class="language-json" data-title=""><code class="lang-json">{
  "name": "sb-qa-pool",
<strong>  <a data-footnote-ref href="#user-content-fn-12">"filter"</a>："metadata.policy_name eq 'sb-qa-pool'",
</strong>  "deployment_request": {
    "private_host_ids": [],
    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-qa-pool"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-qa-pool",
</strong>        "is_hidden": false
      }
    ]
  },
<strong>  "minimum_active_instances": 1
</strong>}
</code></pre>

{% endtab %}

{% tab title="🌡️ 预热" %}
在发布前提前启动 10 倍部署，以应对需求。每个区域都复制一份。

<pre class="language-json" data-title=""><code class="lang-json">{
  "name": "sb-v1.0.0-chicago",
<strong>  <a data-footnote-ref href="#user-content-fn-16">"filter"</a>: "total_joinable_seats gt 0 and metadata.policy_name eq 'sb-v1.0.0-chicago'",
</strong>  "deployment_request": {
    "private_host_ids": [],
    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-v1.0.0-chicago"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-v1.0.0-chicago",
</strong>        "is_hidden": false
      }
    ]
  },
<strong>  <a data-footnote-ref href="#user-content-fn-17">"minimum_active_instances"</a>: 10
</strong>}
</code></pre>

{% endtab %}

{% tab title="🔒 MMO" %}
当可用容量低于阈值时，每个区域都会增加部署。

<pre class="language-json" data-title=""><code class="lang-json">{
<strong>  "name": "sb-mmo-chicago",
</strong><strong>  <a data-footnote-ref href="#user-content-fn-18">"filter"</a>: "total_joinable_seats gt 5 and metadata.policy_name eq 'sb-mmo-chicago'",
</strong>  "deployment_request": {
<strong>    <a data-footnote-ref href="#user-content-fn-19">"private_host_ids"</a>: ["alpha-north-america-95fab093"],
</strong>    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-mmo-chicago"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-mmo-chicago",
</strong>        "is_hidden": false
      }
    ],
<strong>    <a data-footnote-ref href="#user-content-fn-20">"webhook_on_terminated"</a>: {
</strong>      "url": "https://my-webhook.com"
    }
  },
<strong>  "minimum_active_instances": 3
</strong>}
</code></pre>

{% endtab %}

{% tab title="🔑 社区" %}
每个服务器所有者一条策略，传入用于服务器认证的自定义密码。

<pre class="language-json" data-title=""><code class="lang-json">{
<strong>  "name": "sb-owner-jnjnc8mid",
</strong><strong>  <a data-footnote-ref href="#user-content-fn-21">"filter"</a>: "metadata.policy_name eq 'sb-owner-jnjnc8mid'",
</strong>  "deployment_request": {
<strong>    <a data-footnote-ref href="#user-content-fn-19">"private_host_ids"</a>: ["alpha-north-america-95fab093"],
</strong>    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["community", "sb-owner-jnjnc8mid"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-owner-jnjnc8mid",
</strong>        "is_hidden": false
      },
      {
<strong>        "key": "SB_SERVER_PASSWORD",
</strong><strong>        "value": "password1234",
</strong>        "is_hidden": false
      }
    ],
<strong>    <a data-footnote-ref href="#user-content-fn-22">"webhook_on_ready"</a>: {
</strong>      "url": "https://my-webhook.com"
    },
<strong>    <a data-footnote-ref href="#user-content-fn-23">"webhook_on_error"</a>: {
</strong>      "url": "https://my-webhook.com"
    },
<strong>    <a data-footnote-ref href="#user-content-fn-20">"webhook_on_terminated"</a>: {
</strong>      "url": "https://my-webhook.com"
    }
  },
  "minimum_active_instances": 1
}
</code></pre>

{% endtab %}

{% tab title="❄️ 网格组" %}
每组服务器一条策略。游戏后端启动一个主节点，由其生成副本。每个节点读取注入的网格组 ID，并搜索其他节点进行联网。

<pre class="language-json" data-title=""><code class="lang-json">{
<strong>  "name": "sb-meshgroup-pqyt8sxcb",
</strong><strong>  <a data-footnote-ref href="#user-content-fn-12">"filter"</a>: "metadata.policy_name eq 'sb-meshgroup-pqyt8sxcb'",
</strong>  "deployment_request": {
    "private_host_ids": [],
    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-meshgroup-pqyt8sxcb"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-meshgroup-pqyt8sxcb",
</strong>        "is_hidden": false
      },
      {
<strong>        "key": "SB_MESH_GROUP_ID",
</strong><strong>        "value": "pqyt8sxcb",
</strong>        "is_hidden": false
      }
    ],
<strong>    <a data-footnote-ref href="#user-content-fn-22">"webhook_on_ready"</a>: {
</strong>      "url": "https://my-webhook.com"
    },
<strong>    <a data-footnote-ref href="#user-content-fn-24">"webhook_on_terminated"</a>: {
</strong>      "url": "https://my-webhook.com"
    }
  },
<strong>  <a data-footnote-ref href="#user-content-fn-25">"minimum_active_instances"</a>: 9
</strong>}
</code></pre>

{% endtab %}
{% endtabs %}

## ⚙️ 配置

Server Browser API 由你在创建新的（或快速重启的）Server Browser 时指定的 JSON 配置生成。你可以指定服务器和席位的过期时间，以及自定义元数据：

{% tabs %}
{% tab title="🍀 简单示例" %}

<pre class="language-json" data-title="sb-simple-example-v1-0-1.json"><code class="lang-json">{
	"version": "1.0.1",
	"server_instances": {
		"expiration_period": "1m",
		"<a data-footnote-ref href="#user-content-fn-26">索引</a>": {
			"policy_name": "string",
			"name": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-26">索引</a>": {}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "1m"
	},
  "rate_limits": {
    "per_client_ip": 5
  }
}
</code></pre>

{% endtab %}

{% tab title="🎈 社交游戏" %}

<pre class="language-json" data-title="sb-social-example-v1-0-1.json"><code class="lang-json">{
	"version": "1.0.1",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-26">索引</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"level": "string",
			"mode": "string",
			"difficulty": "string",
			"seed": "string",
			"max_players": "int",
			"app_version": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-26">索引</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"avg_latency": "int",
			"player_ids": "string"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "30s"
	},
  "rate_limits": {
    "per_client_ip": 5
  }
}
</code></pre>

{% endtab %}

{% tab title="🤝 合作游戏" %}

<pre class="language-json" data-title="sb-cooperative-example-v1-0-1.json"><code class="lang-json">{
	"version": "1.0.1",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-26">索引</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"level": "string",
			"mode": "string",
			"difficulty": "string",
			"avg_rank": "int",
			"max_players": "int",
			"app_version": "string",
			"tags": "string",
			"match_id": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-26">索引</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"player_ids": "string"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "30s"
	},
  "rate_limits": {
    "per_client_ip": 5
  }
}
</code></pre>

{% endtab %}

{% tab title="⚔️ 竞技游戏" %}

<pre class="language-json" data-title="sb-competitive-example-v1-0-1.json"><code class="lang-json">{
	"version": "1.0.1",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-26">索引</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"avg_rank": "int",
			"max_players": "int",
			"is_ranked": "bool",
			"app_version": "string",
			"cpu_frequency": "int",
			"match_id": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-26">索引</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"avg_rank": "int",
			"avg_latency": "int"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "15s"
	},
  "rate_limits": {
    "per_client_ip": 5
  }
}
</code></pre>

{% endtab %}
{% endtabs %}

{% hint style="info" %}
为获得最佳性能，请避免为不用于筛选或排序的元数据指定索引。未索引的参数仍然可以通过服务器实例或席位详情 API 方法进行设置和读取，参见 [#api](#api "mention").
{% endhint %}

## ☁️ 托管集群

Server Browser 由 Edgegap 提供便捷的 7x24 小时托管与管理。

选择最适合你目标的托管选项：

* **免费集群（共享）** 用于测试所有功能并探索与您的设计的协同效应，
  * 会在 3 小时后自动关闭，需要重启才能继续测试。
* **私有集群** **（专用）** 以确保为您的生产需求提供稳定的环境，
  * 选择您的区域并为实时游戏获得 24/7 支持，以便自信发布。

#### 私有集群层级

我们目前提供 [3 个私有集群等级](https://edgegap.com/resources/pricing#managed-infrastructure) 以满足每个人的需求：

<table><thead><tr><th width="160">等级</th><th align="right">爱好者等级</th><th align="right">工作室等级</th><th align="right">企业等级</th></tr></thead><tbody><tr><td>最适合用于</td><td align="right">爱好者，<br>独立开发者</td><td align="right">商业发布</td><td align="right">高流量上线</td></tr><tr><td>资源</td><td align="right">1 vCPU + 2GB 内存</td><td align="right">6 vCPU + 12GB 内存</td><td align="right">18 vCPU + 48GB 内存</td></tr><tr><td>冗余</td><td align="right">1 个虚拟节点</td><td align="right">3 个虚拟节点</td><td align="right">3 个虚拟节点</td></tr><tr><td>限流（请求/秒）</td><td align="right">200</td><td align="right">750</td><td align="right">2,000</td></tr><tr><td>价格，每小时</td><td align="right">$0.0312</td><td align="right"> $0.146</td><td align="right">$0.548</td></tr><tr><td><strong>价格，30 天</strong><br>（持续使用）</td><td align="right"><strong>$22.464</strong></td><td align="right"><strong>$105.12</strong></td><td align="right"><strong>$394.56</strong></td></tr></tbody></table>

只需点击一次即可升级到私有集群，享受由 Edgegap 团队维护的高可用托管，以及面向公开发布游戏的 7x24 实时支持。

你的实例资源需求取决于以下因素：

* **玩家数量** — 更多玩家会导致更多 API 请求，
* **每位玩家的请求次数** — 更快的重试会增加服务负载并消耗资源，
* **服务器数量** — 更多服务器会带来更多存储数据和更多 API 请求，
* **客户端重试回退逻辑** — 使用带抖动的退避重试有助于分散流量突发高峰，
* **平均对局时长** — 更短的会话需要更频繁地与 Server Browser 交互。

{% hint style="info" %}
我们的集群使用配备 AMD/Intel CPU 的云主机，主频为 2.4 - 3.2 GHz。
{% endhint %}

## 📗 API

**可考虑使用我们的 SDK 来** [**Unreal Engine**](/zh/unreal-engine/developer-tools.md) **或** [**Unity**](/zh/unity/fu-wu-qi-liu-lan-qi.md) **通过预构建示例快速上手。**

游戏客户端和专用服务器在其生命周期中会向 Server Browser 发送 API 请求。

{% hint style="info" %}
Unity/Android - 考虑 [使用原始字符串插值](https://www.c-sharpcorner.com/article/convert-string-to-json-in-c-sharp/) 以防止对硬编码 JSON 的代码剥离。
{% endhint %}

{% hint style="success" %}
**Swagger Web UI**：部署您的服务将生成一个 OpenAPI 规范和一个便捷的网页 UI。在浏览器中打开该 URL 以查看和测试所有 API 端点，并审查示例负载。
{% endhint %}

{% file src="/files/7d2c918178c58f8dedab23c3bb87a44a7fd8ae8a" %}

将 API 规范导入到 [Scalar API Web 客户端](https://client.scalar.com/workspace/default/request/default) 或 [Swagger Editor](https://editor.swagger.io/) 以查看详细信息。

### 速率限制

为保护你的集群避免超过突发容量并崩溃，我们会按每个客户端的公网 IP 地址限制每秒客户端请求数。

该限制由以下参数配置： [#configuration](#configuration "mention") 参数 `rate_limits.per_client_ip`.

{% hint style="warning" %}
如果你的游戏客户端在收到响应后不重试请求 `429 Too Many Requests` **某些玩家可能无法加入服务器** 在短时流量激增和峰值流量期间。
{% endhint %}

{% hint style="success" %}
**我们建议在开发期间使用更低的速率限制（1 req/s）测试应用行为。**
{% endhint %}

#### 负载测试

在类似生产环境中进行负载测试会产生部署托管成本。请参见各层级对应的资源和价格 [我们的定价页面](https://edgegap.com/resources/pricing#matchmaker).

{% hint style="warning" %}
**使用** [**私有集群**](#private-cluster-tiers) **用于压力测试。** 免费实例严格仅限于开发测试。
{% endhint %}

在设计负载测试时， **请考虑真实的玩家行为模式**:

| 真实场景                                  | 不现实的流量模式                       |
| ------------------------------------- | ------------------------------ |
| ✅ 玩家逐步加入游戏，在数小时内逐渐提升请求/秒。             | ❌ 所有玩家协调一致，在完全相同的一秒内访问 API。    |
| ✅ 玩家在重试之间等待的时间逐步增加（例如 1s-5s-10s-10s）。 | ❌ 所有玩家在收到后立即重试 `429 请求过多`  响应。 |
| ✅ 大多数玩家会在较短时间内（10-60 秒）收到分配并停止轮询。     | ❌ 所有玩家在收到分配后仍会继续轮询一段固定时间。      |
| ✅ 大多数玩家在重新开始新会话之前会先完成当前游戏（需要一些时间）。    | ❌ 所有玩家在收到服务器分配后立即重新开始新的会话。     |
| ✅ 峰值流量每天持续约 6 小时，之后部分时区的流量会下降。        | ❌ 峰值流量全天 24 小时持续，所有玩家日夜不停地玩。   |

#### 负载下的行为

如果任何客户端达到配置的按 IP 速率限制，他们将收到一个 `429 Too Many Requests`  响应，并应在递增的退避时间后重试。

如果扩展策略触发的部署数量超过你组织允许的 req/s 限制，你的 server browser 将在每个监控间隔自动重试，并根据计划部署数量采用加权轮询策略，尝试将可用部署配额在所有扩展策略之间平均分配。

### 分页

**Server Browser 提供游标分页，以特定顺序逐步获取筛选后的数据。** 与传统的 limit-offset 分页不同，这种方式在获取更多结果时需要传递游标（起始点）和页面大小（响应条数）。

{% hint style="info" %}
结合我们为游戏服务器元数据开发的专有数据库索引系统，游标分页为筛选高度动态的数据提供快速、一致且灵活的用户体验。
{% endhint %}

我们的目标是让用户在第一页就找到合适的服务器。为获得最佳体验，我们建议显示前几页的缓存结果，并仅在用户点击搜索时刷新结果。

## 🔖 更新日志

#### 语义化版本控制

我们的开发者工具和托管服务使用官方 [语义化版本控制](https://semver.org/)，表明哪些更新是✅安全的（次要、补丁），哪些可能包含⚠️破坏性更改（主要）。

**一旦某个版本发布，它将永远不会被修改/更改**.

{% hint style="info" %}
**Server Browser 的最新版本是 `1.0.0`** 。请留意 [更新和公告](/zh/docs/release-notes.md).
{% endhint %}

[^1]: 第三方玩家标识符

[^2]: 示例值

[^3]: 等于

[^4]: 不等于

[^5]: 小于

[^6]: 小于或等于

[^7]: 大于

[^8]: 大于或等于

[^9]: 参见配置

[^10]: 参见示例策略

[^11]: 使用主动缓存可轻松减少部署时间

[^12]: * 固定容量
    * 假设实例会在元数据中通过注入变量提供策略名称

[^13]: 替换为你自己的应用程序名称

[^14]: 替换为你自己的应用程序版本

[^15]: 芝加哥坐标

[^16]: * 当找到少于 10 个可加入实例时部署
    * 假设实例会在元数据中通过注入变量提供策略名称

[^17]: 我们预计芝加哥区域至少有 10 个部署

[^18]: * 当找到少于 3 个且可加入座位数不超过 5 的实例时部署
    * 假设实例会在元数据中通过注入变量提供策略名称

[^19]: 如果有可用容量，则优先使用私有集群

[^20]: 当服务器重启时通知游戏后端

[^21]: * 不监控容量
    * 假设实例会在元数据中通过注入变量提供策略名称

[^22]: 准备就绪时通知游戏后端

[^23]: 当部署失败时通知游戏后端

[^24]: 停止时通知游戏后端

[^25]: 3x3 网格 = 每个世界 9 台服务器

[^26]: 索引包含用于筛选或排序的自定义元数据参数
