Xray API 可以把核心執行狀態、流量統計與部分管理操作提供給外部程式。若節點數量增加,只靠手動測速與切換容易漏掉短暫斷線、延遲突然升高或流量異常等問題。本文針對熟悉 Xray JSON 的進階使用者,說明如何在本機啟用 gRPC API,利用健康探測、延遲評估與流量統計建立節點管理流程,並說明自動切換時應避免的設定陷阱。
本文以 Xray 1.8.x 之後常見的 API 設計為基礎,示範使用 StatsService 讀取流量、以本機代理執行探測,再由腳本依條件選擇備援節點。你會看到 API 權限邊界、api 入站設定、健康檢查腳本、切換策略,以及如何避免把「核心正在執行」誤判成「節點可用」。
先分清 API 能做什麼與不能做什麼
Xray API 是核心提供的 gRPC 管理介面,並不是另一個代理協定,也不是訂閱服務。它通常透過一個只繫結在 127.0.0.1 的本機 API 入站接收管理請求,再由 api 設定指定可使用的服務。常見服務包括 StatsService、HandlerService、LoggerService 與 RoutingService。其中,StatsService 適合查詢入站或出站的上行、下行計數;HandlerService 可管理部分入站使用者;路由服務則可配合特定版本的路由功能進行測試或管理。
需要特別注意:API 不會自動替你判斷所有出站節點的可用性,也不會因為查到某個出站有流量,就直接把它標記為健康。節點是否正常,至少要同時觀察三個面向:核心能否建立遠端連線、代理請求是否取得預期回應,以及延遲與失敗率是否在可接受範圍內。只有把這些結果交給外部腳本,才會形成完整的自動切換流程。
結論:API 是資料與控制入口
不要把「gRPC API 可連線」當成節點健康。API 只證明本機核心正在提供管理服務;真正的健康判斷必須透過指定出站完成一次代理請求,並搭配連續測試結果。
建立安全的 Xray API 設定
以下設定將 API 服務限制在本機的 127.0.0.1:10085,避免把沒有額外驗證層的管理介面暴露到公網。API 入站的 tag 必須與 api 陣列中的值一致;若名稱不一致,核心可能能正常啟動,但外部查詢會找不到對應的 API 服務。
{
"log": {
"loglevel": "warning"
},
"api": {
"tag": "api",
"services": [
"StatsService",
"HandlerService",
"LoggerService"
]
},
"inbounds": [
{
"listen": "127.0.0.1",
"port": 10085,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
},
"tag": "api"
},
{
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
},
"tag": "socks-in"
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
}
]
},
"outbounds": [
{
"protocol": "freedom",
"tag": "api"
}
],
"stats": {},
"policy": {
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true,
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
}
}
這段範例展示必要結構,實際節點的 outbounds、路由規則與使用者設定仍要保留。stats 物件與 policy.system 讓核心收集系統層級的入站、出站流量;若想追蹤特定使用者,則需要在相應使用者設定中啟用統計標籤,並確認用戶端的 API 版本支援該欄位。
API 入站
- 協定
- dokodemo-door
- 監聽
- 127.0.0.1
- 連接埠
- 10085
- 標籤
- api
只供本機管理腳本使用,不要直接繫結 0.0.0.0。
探測代理
- 協定
- SOCKS
- 監聽
- 127.0.0.1
- 連接埠
- 10808
- UDP
- true
健康檢查必須透過實際代理入口,而不是直接連節點位址。
啟動後先驗證 API 是否可用
修改設定後,先以核心的設定檢查功能驗證 JSON,再重新啟動 Xray。不同發行版的命令列參數可能不同,但排查順序應固定:先確認 JSON 語法,再確認 10085 沒有被其他程序占用,最後才測試 gRPC 查詢。若核心日誌出現 failed to start api 或 address already in use,應先處理監聽位址與連接埠,不要直接修改節點協定。
# 以 xray 執行檔所在位置為準
xray run -test -config /etc/xray/config.json
# 查詢目前可用的統計項目
xray api statsquery \
--server=127.0.0.1:10085
如果 API 查詢成功但沒有任何統計項目,通常表示尚未有流量經過對應入站或出站,或者 policy 沒有開啟所需統計。這和 API 失效是兩件事。可以先透過本機 SOCKS 連接埠開啟一個固定網址,等待數秒後重新執行查詢,確認計數是否增加。
健康檢查要測試真實代理鏈路
健康檢查不應只使用 ICMP、TCP 連接埠或伺服器本身的 HTTP 回應。伺服器連接埠可建立 TCP 連線,並不代表 TLS、Reality、WebSocket、gRPC 或 VMess/VLESS 驗證已成功。較可靠的做法,是讓每個候選出站都對應一個本機可用的代理入口,然後從該入口請求固定的 HTTPS 端點,設定逾時、狀態碼與內容判斷。
| 指標 | 建議判斷 | 用途 |
|---|---|---|
| 連線成功率 | 最近 5 次至少成功 4 次 | 排除持續失效節點 |
| 請求逾時 | 單次超過 5 秒即記錄失敗 | 避免腳本長時間阻塞 |
| 延遲中位數 | 連續測試 3 次後取中位數 | 降低偶發尖峰影響 |
| HTTP 狀態碼 | 只接受預期的 2xx 或 3xx | 確認代理請求取得正常回應 |
| 流量增量 | 查詢 60 秒內的 uplink/downlink | 判斷節點是否仍承擔實際流量 |
測試網址應選擇內容穩定、回應時間短且不需要登入的端點,例如自建的健康檢查頁。若使用大型網頁或需要複雜 JavaScript 的網站,測試結果會混入 DNS、內容分發與瀏覽器行為,難以作為節點指標。對多個節點進行比較時,必須使用同一個網址、相同的請求方法與相同的逾時設定。
curl --silent --show-error --location \
--connect-timeout 3 --max-time 5 \
--socks5-hostname 127.0.0.1:10808 \
--output /dev/null \
--write-out "code=%{http_code} total=%{time_total}\n" \
https://health.example.net/ping
上面的指令適合驗證目前 SOCKS 入口是否能完成代理請求。若所有節點共用同一個入口,腳本必須先透過路由規則或獨立測試設定將請求送到指定出站;不能只重複請求同一入口,卻宣稱已分別測試每個節點。進階部署可為候選節點建立不同的本機連接埠,例如 11001、11002 與 11003,再讓每個入口固定對應一個出站。
用腳本整合探測、統計與切換
在動手寫腳本前,先決定節點切換的執行方式。若使用的是具備 balancer、observatory 或 burst observatory 支援的 Xray 版本,可以讓核心依觀測結果選擇出站,腳本主要負責讀取統計與告警。若目前版本或用戶端沒有穩定的動態出站管理入口,較容易維護的方案是由腳本產生一份候選設定,更新目前使用的設定檔,再進行一次受控重啟。這種方法會造成短暫中斷,但行為清楚、容易回滾。
準備候選節點
在設定中為每個節點建立唯一的
tag,例如node-hk-01、node-jp-01與node-sg-01,不要使用會因訂閱更新而改變的顯示名稱。建立測試入口
讓每個測試 SOCKS 入站只對應一個出站,並將本機連接埠限制在
127.0.0.1,避免健康檢查入口被區域網路其他裝置使用。執行五次探測
每 10 秒測試一次固定網址,保存 HTTP 狀態碼、總耗時與失敗原因,至少保留最近五筆結果,不要只記錄最後一次。
讀取流量統計
使用
xray api statsquery --server=127.0.0.1:10085取得統計,對照節點出站標籤的 uplink 與 downlink 增量。套用並可回滾
健康分數連續兩輪低於門檻才切換,保留上一份設定檔;新節點連續三輪正常後,再恢復自動選擇。
以下是簡化的 Python 邏輯,重點在判斷策略而不是直接取代你的部署工具。實際環境可將 probe() 接到不同本機連接埠,將 read_stats() 接到 Xray API 查詢結果,再由服務管理器執行設定替換與重啟。
WINDOW = 5
FAIL_LIMIT = 2
RECOVER_LIMIT = 3
def is_healthy(result):
return (
result.http_code in (200, 204, 301, 302)
and result.elapsed < 5.0
)
def should_switch(history):
recent = history[-WINDOW:]
failures = sum(not is_healthy(item) for item in recent)
return len(recent) == WINDOW and failures >= FAIL_LIMIT
def should_restore(history):
recent = history[-RECOVER_LIMIT:]
return (
len(recent) == RECOVER_LIMIT
and all(is_healthy(item) for item in recent)
)
切換判斷最好加入冷卻時間,例如每次切換後至少等待 60 秒,不要因為單次 503 或 DNS 暫時逾時就立即來回切換。可以用「失敗次數」與「延遲分數」組合排序:連續失敗的節點先淘汰,仍可用的節點再依中位數延遲、近一分鐘錯誤率與流量倍率排序。流量統計適合用來發現節點長時間沒有成功承擔流量,但不能單獨作為健康證明,因為閒置節點本來就可能沒有計數增加。
切換門檻要有遲滯
建議採用「連續兩輪失敗才切出、連續三輪成功才切回」的遲滯設計,再加上 60 秒冷卻時間。這比單次逾時立即重選節點更能避免網路尖峰造成頻繁切換。
如何讀取出站流量增量
統計項目的名稱通常會帶有入站或出站標籤,例如出站上行與下行可按照 outbound>>node-hk-01>>traffic>>uplink、outbound>>node-hk-01>>traffic>>downlink 的格式查詢。不同核心版本與呼叫工具對欄位顯示方式可能略有不同,因此不要把完整字串硬編寫成唯一格式;先執行一次統計查詢,確認實際輸出,再在腳本中以節點 tag 過濾。
流量監控欄位
- uplink
- 用戶端送往節點
- downlink
- 節點回傳用戶端
- 取樣週期
- 60 秒
使用相鄰兩次計數差值,不要把累積總量當成即時速度。
延遲監控欄位
- 測試次數
- 每輪 3 次
- 逾時
- 5 秒
- 排序值
- 中位數
中位數比單次最低延遲更適合用於自動選擇。
失效備援與排錯順序
自動切換系統最常見的問題,不是探測程式不會執行,而是把不同層級的故障混在一起。例如本機 API 連接埠被占用時,腳本會誤以為所有節點失效;本機 DNS 失敗時,所有候選節點也可能同時被判定為不可用;訂閱更新後出站標籤改變,統計查詢則會一直得到零值。每次切換前,應先檢查 API 本身、本機探測入口與單一節點,再處理候選清單。
API 查詢顯示連線被拒絕,節點都壞了嗎?
不一定。先確認 Xray 是否正在執行,再檢查 127.0.0.1:10085 是否被其他程序占用,以及設定中的 API 入站標籤是否和 api.tag 相同。
為什麼流量統計一直是零?
先透過對應入站產生一個真實請求,再確認 policy.system 的統計開關已啟用。若只查詢不存在的出站標籤,也會得到沒有資料的結果。
單次逾時就切換節點可以嗎?
不建議。將單次逾時記為一次失敗,至少累積五次測試並在兩次以上失敗時切換,同時加入 60 秒冷卻時間,避免頻繁震盪。
切換後為什麼還是使用舊節點?
確認路由規則的 outboundTag、balancer 選擇器與實際節點標籤一致;若採用重啟方式切換,也要確認服務管理器讀取的是更新後的設定檔。
建議將腳本輸出的每次判斷保存至少 24 小時,包含時間、節點標籤、測試網址、耗時、狀態碼、失敗原因、統計增量與切換結果。這些資料可以協助區分節點本身不穩定、特定時段壅塞,或只是本機網路短暫中斷。當健康檢查穩定後,再加入告警通知與定期設定備份;不要一開始就讓腳本擁有遠端修改設定的權限。