Xray 的節點自動切換,不只是定時執行「測試網址」再更換設定檔。比較可靠的做法,是讓 Xray 透過 API 暴露流量統計與出站管理能力,由外部腳本讀取每個節點的上行、下行計數,再按照錯誤率、延遲、流量增長或連續失敗次數決定是否切換。這樣可以避免頻繁重啟核心,也能保留目前的路由規則、DNS 設定與入站連接。
本文以 Xray-core 1.8.x 以上版本的 API 結構為基準,重點說明 StatsService 與 HandlerService 的關係、API 監聽器的安全邊界、統計名稱的組成方式、節點切換腳本的設計,以及切換失敗時如何回復、記錄和限制頻率。範例會使用本機 127.0.0.1:10085 作為 API 入口;實際部署時應依目前設定檔與用戶端產生的連接埠調整。
本文適合已能使用 Xray、VLESS 或 VMess 節點,並希望以腳本自動選擇較穩定出站的使用者。內容從 API 設定、StatsService 計數規則與 HandlerService 切換方法開始,逐步建立一個具備門檻、冷卻時間、鎖定檔、回復節點與 JSON 記錄的生產環境方案。
Xray API 的服務分工與切換邏輯
Xray API 本身不是一個面向公網的管理網站,而是由 gRPC 提供服務介面。設定檔中的 api 區塊會指定一個 API 入站標籤,例如 api;api.services 再決定此入站可以提供哪些服務。StatsService 負責查詢統計資料,HandlerService 則負責在核心執行期間管理入站或出站。兩者可以同時啟用,但用途完全不同。
StatsService 回傳的是計數器,不是完整的即時品質報告。常見項目包括某個出站的 uplink 與 downlink 位元組數,也可能包含入站或使用者層級的統計。腳本需要在兩個時間點讀取同一個計數器,使用差值計算期間流量;不能把累計值直接當成最近一分鐘的傳輸量。若核心重啟,計數器可能歸零,腳本必須把這種情況視為重新建立基準,而不是誤判節點突然變得非常健康或非常異常。
HandlerService 的作用是改變目前核心中的處理器狀態。例如在多個出站都已經預先寫入設定檔時,腳本可以選擇某個出站作為目前使用的項目,而不必每次切換都重寫完整 JSON 或重新啟動 Xray。這種方式的中斷時間通常比重啟核心短,但能否使用指定方法,仍取決於 Xray-core 版本、API protobuf 定義與目前路由結構,部署前應先在測試環境驗證。
結論:統計負責觀察,切換仍要由策略決定
StatsService 只能告訴腳本計數器如何變化,不能直接證明某個節點適合所有流量。應將統計、主動探測、連續失敗次數與冷卻時間合併成決策,避免一次短暫延遲就觸發全域切換。
建立受限的 API 服務設定
最小可用的設定通常需要一個 API 入站、一個具備 API 標籤的本機入口,以及 StatsService、HandlerService 服務。API 入站的位址應限制為 127.0.0.1,不要監聽 0.0.0.0 或伺服器的公網 IP。Xray API 一旦暴露到外部網路,取得連線能力的人可能修改出站、加入入站,甚至將服務變成未授權的轉發器。
{
"api": {
"tag": "api",
"services": [
"StatsService",
"HandlerService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 10085,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"stats": {},
"policy": {
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true,
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
}
}
上面的設定只展示與 API 和統計相關的部分,不是可以直接覆蓋現有設定檔的完整配置。policy.system.statsOutboundUplink 與 statsOutboundDownlink 是觀察出站流量的關鍵;若沒有開啟,腳本即使能連上 StatsService,也可能查不到所需的出站計數器。若要統計特定使用者,還要在對應的 policy.levels 和用戶設定中啟用使用者統計,不能只開啟 system 統計。
API 入站本身通常不應被一般代理流量使用。路由規則要將 api 入站標籤送往 api 出站,或依 Xray 版本與配置方式使用內建 API 對應機制;重點是確保外部請求不能透過普通代理入口間接存取管理服務。修改後先用設定檔測試功能檢查,再重啟測試實例,最後才套用至正式程序。
StatsService 計數器名稱與 JSON 查詢
統計查詢最容易出錯的地方,是腳本使用了錯誤的名稱。Xray 統計資料通常以「類型 + 標籤」組成,例如出站 uplink 和 downlink 常見形式為 outbound>>>edge-a>>>traffic>>>uplink 與 outbound>>>edge-a>>>traffic>>>downlink。這裡的 edge-a 必須與出站設定的 tag 完全一致,大小寫、連字號和底線都不能自行簡化。
可先使用 Xray 提供的 API 命令列工具查詢目前的統計項目。不同發行版本的命令列參數名稱可能略有差異,執行前可查看本機核心的說明;常見查詢形式如下:
xray api statsquery --server=127.0.0.1:10085
xray api stat query --server=127.0.0.1:10085 \
-name "outbound>>>edge-a>>>traffic>>>uplink"
如果第一個命令可以列出統計項目,先把完整名稱保存到測試記錄,再讓腳本使用完全相同的名稱。查不到資料時,依序檢查出站標籤、system policy 是否開啟、流量是否真的經過該出站,以及 Xray 是否在修改設定後成功重新載入。不要直接把「回傳 0」解讀成節點沒有流量;有些情況是名稱不存在,有些情況才是計數器確實為零。
出站計數器
- 上行項目
- outbound>>>edge-a>>>traffic>>>uplink
- 下行項目
- outbound>>>edge-a>>>traffic>>>downlink
- 識別依據
- 出站 tag
- 資料型態
- 累計位元組數
以兩次查詢的差值計算指定期間流量,不能直接使用累計總量。
健康判斷欄位
- 採樣週期
- 30 秒
- 失敗門檻
- 連續 3 次
- 切換冷卻
- 至少 300 秒
- 回復條件
- 連續 2 次成功
流量統計適合當作輔助訊號,延遲與連線結果仍應由主動探測補足。
用腳本完成節點評分與自動切換
腳本不要只比較單次延遲。更穩定的設計是為每個出站保存狀態,例如最近一次成功時間、連續失敗次數、最近統計值、目前評分與是否處於冷卻期。每 30 秒讀取一次 StatsService,並對候選節點執行固定的 HTTPS 或 TCP 測試;當目前節點連續三次失敗,且候選節點至少連續兩次成功,才進入切換程序。
-
確認 API 可用
在 Xray 所在主機執行 statsquery,確認
127.0.0.1:10085可以回應,並記錄每個出站的完整統計名稱。若 API 無法連線,腳本應直接退出,不可在未知狀態下切換節點。 -
建立狀態檔
在例如
/var/lib/xray-switch/state.json的受限目錄保存上一次計數、健康結果、目前出站與切換時間。狀態檔權限只允許執行腳本的系統帳號讀寫。 -
計算採樣結果
連續兩次查詢相減得到流量增量,再將主動探測延遲、失敗次數和候選節點權重合併計分。計數器歸零或小於上次數值時,先重設基準,不要產生負的流量增量。
-
鎖定切換程序
使用檔案鎖避免 systemd timer、手動執行和監控服務同時修改核心。取得鎖後再次讀取目前出站,若其他程序已完成切換,當次操作就取消。
-
呼叫管理服務
使用 HandlerService 的出站選擇或管理方法切換至已存在的候選 tag。呼叫後等待 2 至 5 秒,重新查詢 API 與執行探測,只有驗證成功才更新狀態檔。
-
保留回復入口
若切換後候選節點也失敗,回復上一個已知可用的 tag;若所有節點都失敗,停止反覆切換並保留最後結果,交給告警系統處理。
以下是可供腳本使用的狀態資料格式。它不是 Xray 的 API 請求,而是外部程序自己的記錄;將「目前節點」與「上一個節點」分開保存,有助於切換失敗時回復。時間建議使用 UTC 的 ISO 8601 格式,避免夏令時間或跨時區造成冷卻判斷錯誤。
{
"current": "edge-a",
"previous": "edge-b",
"last_switch_at": "2026-08-29T09:30:00Z",
"cooldown_until": "2026-08-29T09:35:00Z",
"nodes": {
"edge-a": {
"uplink": 1843200,
"downlink": 73400320,
"failures": 0,
"successes": 4,
"latency_ms": 182
},
"edge-b": {
"uplink": 921600,
"downlink": 12582912,
"failures": 1,
"successes": 3,
"latency_ms": 96
}
}
}
在實作 HandlerService 時,建議使用與 Xray-core 版本相符的 gRPC protobuf 定義,建立服務連線後呼叫對應的出站操作。不要把未確認的 HTTP 路徑當成 API 端點,也不要把一般的 curl http://127.0.0.1:10085 當成可用測試;這個連接埠提供的是 gRPC 服務,不是普通的 REST JSON 介面。若目前客戶端只支援重新載入設定,則可將「產生新設定、語法檢查、原子替換、優雅重啟」作為退回方案。
HandlerService 與多出站架構的取捨
要做到低中斷切換,通常先在 Xray 設定檔中準備多個帶有清楚 tag 的出站,再由路由將需要自動調整的流量送入可管理的選擇結構。這與直接修改單一出站的伺服器位址不同:前者保留多個候選節點,切換時只改選擇結果;後者每次都要重建部分設定,若處理不當容易遺失 TLS、傳輸層或路由欄位。
將 edge-a、edge-b、edge-c 都放在核心設定中,由 API 選擇目前出站。切換速度較快,且不需要在執行期間拼接完整節點 JSON。
適合:固定節點池、低中斷切換
腳本產生新的出站設定,先通過配置檢查,再以原子方式替換並重啟核心。相容性較直觀,但會帶來短暫中斷與設定遺失風險。
適合:節點經常變動、API 不可用
按照 uplink 或 downlink 是否增加判斷節點健康。實作最簡單,但閒置時沒有流量並不代表節點故障,容易造成錯誤切換。
適合:僅作為輔助訊號
多節點架構還要考慮訂閱更新。若節點是由訂閱產生,更新程序可能重建出站 tag;假設腳本固定尋找 edge-a,而客戶端更新後將 tag 改成另一個名稱,StatsService 查詢和 HandlerService 切換都會失敗。因此應固定 tag 生成規則,或在每次更新後重新產生節點對照表,再執行一次 API 健康檢查。
生產環境的回復、記錄與故障限制
自動切換最危險的情況不是一次切換失敗,而是腳本在失敗後不斷切換,形成節點震盪。至少要設定 300 秒冷卻時間、單次執行鎖,以及每天或每小時的最大切換次數。切換前保留目前 tag、切換原因和統計快照;切換後若連續兩次探測失敗,就嘗試回復上一個節點,並將事件標記為 rollback,不要把回復動作當成新的正常切換。
- 檢查 API 狀態:連不上
127.0.0.1:10085時停止所有切換,保留現有連線,不要自行重啟核心。 - 檢查候選節點:候選節點必須先通過至少兩次探測,才可成為切換目標;單次成功不足以排除偶發連通。
- 檢查切換後流量:等待 2 至 5 秒,再確認新的 outbound 計數器有增加,並執行一次固定目標測試。
- 限制變更範圍:HandlerService 只操作預先允許的出站 tag,拒絕來自未簽名或不在白名單的節點名稱。
- 保留原子記錄:先寫入暫存檔,再使用原子替換更新 JSON,避免程序中止時留下半份狀態資料。
{
"event": "switch",
"time": "2026-08-29T09:30:00Z",
"from": "edge-a",
"to": "edge-b",
"reason": "probe_failures=3;latency_ms=410",
"stats_delta": {
"edge-a": {
"uplink": 20480,
"downlink": 163840
}
},
"verified": true,
"rollback": false
}
錯誤:rpc error: code = Unavailable desc = connection refused
原因與解法:API 入站沒有啟動、連接埠填錯或只監聽其他位址;先檢查 Xray 啟動記錄與 127.0.0.1:10085 的監聽狀態,確認服務可用後再執行腳本。
錯誤:stat not found
原因與解法:統計名稱與出站 tag 不一致,或未開啟 system outbound 統計;複製 statsquery 列出的完整名稱,並核對 policy 與目前實際流量。
錯誤:failed to alter outbound handler
原因與解法:指定的出站不存在、API 未啟用 HandlerService,或目前核心版本不支援該操作;先確認 tag 白名單與 protobuf 版本,必要時改用驗證後重載設定的退回方案。
錯誤:節點不斷在 edge-a 與 edge-b 之間切換
原因與解法:缺少冷卻時間、連續失敗門檻或探測結果穩定期;加入至少 300 秒冷卻、連續 3 次失敗才切換,以及連續 2 次成功才允許回復。
最後,建議將腳本執行記錄送往既有的系統日誌或集中式記錄服務,至少保留時間、目前節點、目標節點、觸發原因、StatsService 差值、探測延遲、API 回應結果與是否回復。不要在日誌中寫入 UUID、私鑰、完整訂閱網址或其他認證資訊。自動化的目標是降低人工操作,不是把敏感節點資料複製到更多位置。