进阶技巧 预计阅读 17 分钟

Xray API节点自动测速与故障切换进阶配置指南

本文面向能够读写 Xray JSON 配置的技术用户,系统拆解 Xray-core gRPC API 的节点管理与状态统计机制,并通过实际脚本完成延迟检测、故障摘除和自动恢复。你将获得一套可用于无头服务器与生产环境的节点高可用方案。

Xray-core 的自动切换不能简单理解为“定时给所有节点测速,然后选择延迟最低的一条”。在无头服务器、旁路由或容器环境中,真正可靠的高可用逻辑至少要同时处理四件事:通过 gRPC API 读取节点运行状态,使用独立探针确认端到端可用性,对连续失败的节点执行摘除,并在恢复后避免立即把不稳定节点重新放回生产流量。

本文面向能够阅读 Xray JSON 配置、理解 gRPC 调用并维护脚本的技术用户。示例使用 Xray-core 的 API 入站、StatsService 与 HandlerService,监听地址统一设为本机 127.0.0.1:10085,本地 SOCKS 入站使用 127.0.0.1:10808。节点切换采用“固定代理出站标签 + 动态修改出站内容”的方式,便于路由规则始终指向同一个标签。

本文速览

本文从 Xray API 配置开始,解释 StatsService 如何提供流量统计、为什么延迟测试不能只看单次结果,并给出基于失败阈值、恢复阈值和冷却时间的自动摘除与恢复方案。读完后可以把同一套逻辑部署到无头 Linux 主机、旁路由或容器服务中。

先建立 Xray API 的管理边界

Xray 的 API 通过专用的 api 入站暴露 gRPC 服务。它不是 HTTP JSON 接口,也不应直接暴露到公网。常用服务包括 HandlerServiceStatsServiceRoutingService 和日志相关服务。本文只依赖前两个:HandlerService 负责运行时增删或修改入站、出站,StatsService 负责读取指定标签的流量计数。

一个容易维护的配置,应把 API 入站、统计对象和业务入站分开。下面是可作为基础合并到主配置中的片段。api 标签必须同时出现在 inboundsrouting.rules 中,否则 API 请求可能被错误地送入代理出站。

{
  "log": {
    "loglevel": "warning"
  },
  "api": {
    "services": [
      "HandlerService",
      "StatsService"
    ],
    "tag": "api"
  },
  "inbounds": [
    {
      "tag": "api",
      "listen": "127.0.0.1",
      "port": 10085,
      "protocol": "dokodemo-door",
      "settings": {
        "address": "127.0.0.1"
      }
    },
    {
      "tag": "socks-in",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "routing": {
    "rules": [
      {
        "type": "field",
        "inboundTag": ["api"],
        "outboundTag": "api"
      }
    ]
  },
  "outbounds": [
    {
      "tag": "api",
      "protocol": "freedom"
    },
    {
      "tag": "proxy-active",
      "protocol": "vless",
      "settings": {
        "vnext": [
          {
            "address": "node-a.example.net",
            "port": 443,
            "users": [
              {
                "id": "00000000-0000-0000-0000-000000000000",
                "encryption": "none",
                "flow": "xtls-rprx-vision"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "serverName": "www.example.com",
          "fingerprint": "chrome",
          "publicKey": "REPLACE_PUBLIC_KEY",
          "shortId": "REPLACE_SHORT_ID"
        }
      }
    }
  ]
}

示例中的 UUID、公钥、短 ID、服务器名称和地址均为占位值,不能直接使用。实际部署时,脚本只应修改节点池中明确允许修改的字段。VLESS、VMess、Trojan 和 Shadowsocks 的 JSON 结构不同,不能把一个协议的 settings 对象直接套用到另一个协议上。

10085
本机 API 端口
10808
SOCKS 探测端口
3 次
建议失败阈值
60 秒
最短恢复冷却

StatsService 能说明什么,不能说明什么

StatsService 记录的是 Xray 内部观察到的流量计数。要让出站统计有意义,出站必须拥有稳定的 tag,并且统计配置要打开出站流量计数。常用统计名称格式如下:

可以使用 Xray 自带的 API 命令查询单项统计。不同发行版的命令参数可能略有差异,先运行 xray api --help 确认本机版本支持的语法。典型查询方式如下:

xray api statsquery \
  --server=127.0.0.1:10085 \
  -pattern='outbound>>>proxy-active>>>traffic'

统计值适合回答“这个出站最近是否真正承载流量”,不适合直接回答“这个节点当前延迟是多少”。如果某节点没有用户流量,计数长时间不增长并不代表节点故障;如果连接已经建立但远端持续阻塞,计数也可能继续增加。因此,自动切换必须另行发起主动探测。

定时器触发查询运行统计代理请求探测更新节点状态执行摘除切换

主动探测最好通过本地 SOCKS 入站完成,而不是在脚本进程中直接连接节点地址。这样测试路径会经过 Xray 的真实路由、DNS 和传输配置。Linux 环境可使用 curl 进行 HTTP 探测:

curl --proxy socks5h://127.0.0.1:10808 \
  --connect-timeout 5 \
  --max-time 12 \
  --retry 0 \
  -fsS https://www.example.com/health.txt

使用 socks5h 可以让域名解析交给 SOCKS 代理路径,减少脚本自身 DNS 结果与 Xray 路径不一致的问题。探测目标应选择内容稳定、响应体较小且允许频繁访问的地址。不要只使用 ICMP ping,也不要把公共测速站的大文件下载作为每分钟一次的健康检查,否则测速本身会消耗节点流量并影响正常业务。

节点池、状态机与切换条件

自动切换的核心不是“找到最小延迟”,而是维护每个节点的状态。建议至少使用 healthysuspectdowncooldown 四种状态。单次超时只进入 suspect;连续三次失败才进入 down;节点恢复时连续两次成功后才重新标记 healthy。这样可以避免偶发丢包造成频繁抖动。

推荐方案:控制面与数据面分离

控制面
  • 监听 127.0.0.1:10085
  • 读取 StatsService
  • 维护失败与恢复计数
  • 调用 HandlerService
数据面
  • 固定 proxy-active 标签
  • SOCKS 端口 10808
  • 路由始终指向活动出站
  • 切换后重新建立新连接

业务路由不需要知道当前节点地址,脚本只负责替换活动出站并记录切换原因。

节点选择可以采用加权评分,而不是单纯按延迟排序。一个实用的评分公式是:成功探测得分为 延迟毫秒 + 丢包惩罚 + 最近切换惩罚。例如延迟为 180 毫秒、最近一次失败次数为 1 的节点,可能比延迟 150 毫秒但刚刚恢复的节点更适合继续观察。生产环境中稳定性通常比几十毫秒的差异更重要。

状态 进入条件 业务处理 离开条件
healthy 连续两次探测成功 允许选为活动节点 单次失败进入 suspect
suspect 出现一次超时或 5xx 继续观察,不立即切换 成功回 healthy,连续失败进 down
down 连续三次失败 禁止新连接使用 冷却结束后进入恢复探测
cooldown down 节点等待 60 秒 低频探测,不参与选主 连续两次成功回 healthy

切换还需要设置最短保持时间。例如当前节点刚完成切换后的 120 秒内,即使另一个节点只快了 20 毫秒,也不要再次切换。只有当前节点探测失败、返回连续错误,或候选节点的评分明显低于当前节点时,才允许执行切换。这个“滞回”设计是避免生产环境频繁重连的关键。

结论:先防抖,再追求低延迟

自动切换系统最常见的问题不是不会找到可用节点,而是切换过于频繁。将失败阈值、恢复阈值和最短保持时间写成明确参数,比继续增加测速频率更能提高可用性。

用脚本完成探测、摘除与恢复

下面给出一套便于改造成生产脚本的实现流程。脚本可以使用 Python 的 gRPC 生成代码调用 HandlerService,也可以由现有运维程序封装 API 调用。由于 Xray API 是 protobuf gRPC 服务,不能把它误写成普通的 POST /api/stats 请求。部署前应准备与当前 Xray-core 版本匹配的 API protobuf 代码,并固定依赖版本。

  1. 固定出站标签

    让所有业务路由指向 proxy-active,节点池中的真实地址、端口、用户标识和传输参数保存在脚本可读的配置文件中。

  2. 执行代理探测

    每 30 秒通过 socks5h://127.0.0.1:10808 请求小型健康检查资源,记录耗时、HTTP 状态、异常类型和时间戳。

  3. 更新节点状态

    单次失败只增加失败计数,连续 3 次失败才标记 down;恢复探测连续成功 2 次后才重新允许选主。

  4. 调用运行时 API

    通过 HandlerService 的 AlterOutbound 修改 proxy-active 的出站配置,成功后重新发起一次探测确认生效。

  5. 记录切换结果

    将旧节点、新节点、触发原因、API 返回结果和切换耗时写入结构化日志,便于回溯而不是只记录“切换成功”。

伪代码可以先把状态机验证清楚,再接入具体的 gRPC stub。以下逻辑强调三个细节:当前活动节点失败时才触发切换;候选节点必须已经通过恢复阈值;切换失败不能直接把节点标记为健康。

INTERVAL = 30
FAIL_LIMIT = 3
RECOVER_LIMIT = 2
COOLDOWN = 60
MIN_HOLD = 120

for node in node_pool:
    result = probe_through_xray(node)
    if result.ok:
        node.fail_count = 0
        node.success_count += 1
        if node.state in ("down", "cooldown") and node.success_count >= RECOVER_LIMIT:
            node.state = "healthy"
    else:
        node.success_count = 0
        node.fail_count += 1
        if node.fail_count >= FAIL_LIMIT:
            node.state = "down"
            node.cooldown_until = now + COOLDOWN

current = state.active_node
if current.state == "down" and now - state.last_switch >= MIN_HOLD:
    candidate = choose_lowest_score(
        node for node in node_pool
        if node.state == "healthy" and node.cooldown_until <= now
    )
    if candidate:
        payload = build_outbound_config(candidate)
        alter_outbound("proxy-active", payload)
        confirm = probe_through_xray(candidate)
        if confirm.ok:
            state.active_node = candidate.name
            state.last_switch = now
            write_event("switch", current.name, candidate.name)

AlterOutbound 修改的是运行中的出站配置,不会神奇地把已经建立的 TCP 连接迁移到新节点。切换完成后,旧连接可能继续维持,也可能因旧链路故障而报错;新建连接才会使用新出站。因此,如果业务要求快速恢复,应在切换后让上层连接池或应用主动重试,而不是期待 Xray 把既有连接无损搬迁。

如果当前 Xray-core 版本或封装语言不方便调用 AlterOutbound,可以采用“生成完整 JSON、校验、受控重启”的兜底方案,但这会中断所有连接,恢复时间也更长。无论使用哪种方式,切换前都应保存当前配置快照,并先进行 JSON 语法检查。不要让脚本直接覆盖唯一配置文件,更不要在多个并发探测任务中同时重载核心。

生产环境中的权限、并发与回滚

API 端口只监听回环地址是最低要求。若 Xray 运行在容器中,127.0.0.1 的含义是容器内部回环地址,宿主机上的脚本可能无法访问;此时应使用同一容器内的控制进程,或通过受限的容器网络连接,不要把 10085 直接映射到公网。

脚本需要加入单实例锁。一次探测尚未完成时,下一轮不能再次发起切换,否则两个任务可能分别选择不同候选节点,最后状态文件与 Xray 实际出站不一致。可以使用文件锁、systemd 服务的单进程约束,或在脚本内部使用互斥锁。节点状态文件也应采用临时文件写入后原子替换,避免主机断电留下半截 JSON。

日志至少记录以下字段:

切换失败时应立即回滚到上一个已验证配置,而不是继续遍历节点并快速重载核心。若旧节点仍能建立连接,可以保留它作为临时回退;若旧节点已经确认 down,则应进入“无可用节点”状态并报警。报警阈值可以设置为连续 5 分钟没有健康节点,或 3 次切换均未通过切换后的确认探测。

常见问题与故障判断

StatsService 没有返回 proxy-active 统计怎么办?

先确认出站标签确实是 proxy-active,再检查统计配置是否启用了出站流量计数。没有真实流量时计数可能不存在或保持为零,先通过 SOCKS 入站发起一次测试请求。

节点延迟很低,为什么仍然被脚本摘除?

延迟只代表探测请求的耗时。脚本还会检查 DNS、TLS、HTTP 状态和完整响应;如果返回 5xx、证书错误或响应体校验失败,节点仍应进入 suspect 或 down。

调用 AlterOutbound 后旧连接没有断开正常吗?

正常。运行时修改主要影响新建连接,已经建立的连接不会自动迁移。需要快速恢复时,让应用连接池重建连接,或者在明确接受中断的情况下重启对应服务。

恢复节点刚上线又马上被切走怎么办?

提高恢复阈值并增加冷却时间,例如连续成功 2 次、冷却 60 秒、最短保持 120 秒。恢复探测应与生产探测分开,不能一次成功就立即参与选主。

下载 v2rayN