---
title: "Cortex 路由器：我們如何為每個請求挑選最快的健康節點"
description: "將請求送達鄰近區域只是基本門檻。接下來選擇由哪個節點回應，才是 Cortex 發揮作用之處。"
---

# Cortex 路由器：我們如何為每個請求挑選最快的健康節點

<ImageBlock
  src="https://media.alchemy.com/blog/cortex-router-hero-16x9-20260821.png"
  alt="Cortex router:我們如何為每個請求挑選最快的健康節點"
  width={1920}
  height={1080}
  priority
/>

你的應用程式發出的每一個 RPC 呼叫，都必須送達某個節點並取得回應後，使用者才會看到任何東西。把呼叫送到鄰近區域是每個認真經營的供應商都會自動做到的事，我們也不例外，而且我們持續增設區域，藉此進一步縮短這第一段傳輸距離。比較有意思的問題是請求抵達之後發生了什麼：究竟是該區域裡哪一個節點回應了這個請求，以及是否有人確認過該節點健康且已同步到最新區塊，才把請求送過去。

第二個決策是我們在每一次請求時都重新做出的判斷，這也是我們[跨 EVM 鏈平均回應時間低於 15ms](https://www.alchemy.com/benchmarks) 的主要原因之一。這個數字是在已建立連線的情況下量測的「熱」請求-回應時間，也就是正在運行的應用程式產生的重複流量會呈現的狀況。它不包含 DNS、連線建立與 TLS 交握，因此下面談到的邊緣處理工作是排除在這個數字之外，而非包含在內。

## Cortex 路由器實際上在決定什麼？

[Cortex](https://www.alchemy.com/blog/cortex) 是驅動整個 Alchemy 平台的引擎，從節點叢集到路由層，再到上層的 RPC 與 Data API，全都由它負責。先前的架構是在流量前面放一個第三方 CDN，這使得該 CDN 同時成為所有網路、所有客戶共用的單一故障點。換成[由我們自行營運的裸機（bare metal）](https://www.alchemy.com/blog/the-tech-behind-cortex)之後，就消除了這個依賴，讓整條路徑都在我們自己的掌控之下。現在你發出的每一個請求都會經過 Cortex，而路由器正是決定請求該往哪裡送的那一部分。

對每一個請求，路由器都會選擇路徑：哪個區域接受它、哪個節點來服務它，以及如果該節點在處理過程中發生異常該怎麼辦。所謂「到你的鏈最佳路由」，實際上指的是此時此刻能正確回應這個特定呼叫、延遲最低的健康節點。這個答案每一秒都會隨著負載與健康狀態而改變，這也是為什麼我們是針對每個請求重新計算，而不是解析一次就重複使用同一個結果。

其中區域選擇的部分，現在已經是整個產業的標準做法。在任何主要供應商上，從新加坡發出的請求和從法蘭克福發出的請求都會從不同地點進入系統，而且沒有一家會要求你自己去設定。我們額外提供的是第二個決策，而且是按每個請求、而非按每條連線來做：在請求落地的那個區域裡，可用的節點之中，哪一個是健康的、資料是最新的，而且適合處理這個特定的呼叫。

<ImageBlock
  src="https://media.alchemy.com/blog/cortex-router-diagram.png"
  alt="Cortex router 架構:請求進入最近的 edge region,control plane 選擇健康的 serving stack,再由 fleet 前方的 gateway 挑選節點"
  width={3840}
  height={2160}
/>

## 請求如何抵達正確的區域？

以地理位置為基礎的 DNS 會把端點解析到最近的邊緣區域，讓請求在靠近發出地的地方進入系統，而不是所有流量都集中到同一個地方。這一步發生在路由器檢視任何一個節點之前，因為到入口的距離就是純粹的往返時間，一旦浪費掉就再也追不回來。連線建立與 TLS 協商也是在這裡完成，在最近的節點處理，而不是深入到堆疊的下層才處理。

這個入口就是我們自己的 [Alchemy Edge Proxy](https://www.alchemy.com/blog/alchemy-edge-proxy)，一個由我們自行運作的裸機入口層，取代第三方 CDN。推出這個架構，是整個重建過程中延遲改善幅度最大的一環。亞太地區的 P99 從 150ms 降到 20ms，全部網路的 P95 大約減半，從約 200ms 降到約 100ms。這些數字是這次遷移前後的對比，而非目前的實際數值；P95 與 P99 分別代表最慢的 5% 與 1% 的請求，也正是使用者實際會感受到的部分。邊緣層只是我們在延遲上持續投入的其中一層，這項努力從[預測性擴容一路延伸到機群自動化](https://www.alchemy.com/blog/how-we-built-low-latency-rpc-infrastructure)。

## 我們如何在一個區域內挑選節點？

這裡有兩層各司其職。在邊緣層，控制平面會先看哪些服務堆疊支援你請求的網路、其中哪些是健康的，然後把請求送到最近的那一個。但挑選節點這件事發生在更深一層，也就是位於節點叢集前方的閘道（gateway）。這個閘道是路徑上第一個知道個別機器狀況的環節。

要選好一個節點，光靠「近」是不夠的，因此在請求被轉送出去之前，有好幾項條件會先篩掉一些候選節點。

- **健康狀態，來自即時追蹤。** 一套節點追蹤系統會持續執行健康檢查，沒通過的節點就不列入候選。這樣可以在一開始就避免選到狀態不佳的後端，而不是等使用者自己發現。
- **對目前狀態的一致性。** 各節點的區塊高度並不一致，如果把讀取請求交給反應最快的那個節點，可能會拿到鏈已經前進過的舊狀態。有一個一致性層，確保[不論由哪個節點服務，請求看到的都是同一份正典（canonical）鏈狀態](https://www.alchemy.com/blog/from-one-node-to-cortex)，而版本化的更新機制則確保即使在容錯移轉（failover）進行中，也不會讀到過時的資料。
- **實測延遲，而不只是地理位置。** 候選節點會依據近期實測延遲的指數加權移動平均來評分，這和負載平衡器多年來使用的方法一樣。由於較舊的樣本會逐漸從平均值中淡出，一個幾分鐘前開始變慢的節點，排名會自然下滑，不需要任何人發出警報。
- **工作負載的形態。** 不同的節點客戶端在同步行為以及擅長服務哪些方法上各有差異，而封存節點（archival）與全節點所保存的資料也不同，因此請求的方法與參數會進一步縮小可用節點的範圍。一個沉重的歷史查詢和一個簡單的餘額讀取，不該落在同一個節點上處理。

所以「最低延遲」從來不是某處可以開關的設定。它是針對每一次呼叫重新計算出來的，依據請求從哪裡進來、它要求的是什麼，以及當下哪些機器恰好又快又健康。

## 當選中的節點狀態不健康時會發生什麼？

容錯移轉是路由決策的一部分，而不是事後附加上去的警報機制。當某個節點下線時，[Cortex 會自動把流量改送到健康的節點](https://www.alchemy.com/blog/from-one-node-to-cortex)。更有意思的情況是那些還沒完全故障、但正處於壅塞、落後，或執行著已知有臭蟲的客戶端版本的節點，同樣的情境感知路由也會繞開這些節點。你的應用程式不會看到錯誤，這裡也沒有需要你自己撰寫的重試邏輯。

同樣的原則也適用於更高的層級。如果整個區域都出現問題，請求會改路由到次要區域，而次要區域之後還有進一步的備援。即時的全球複寫機制，會在這些轉換過程中維持每個區域的資料一致，所以最終落到較遠的區域，並不代表讀到的是較舊的資料。

Shuffle sharding 與冗餘節點池是這一切之下的基礎，確保故障能被隔離、限制在局部，而不會擴散到整個機群。路由層把狀態不佳的節點視為應該避開的路徑，這正是支撐我們[零停機 Solana gRPC 串流](https://www.alchemy.com/blog/zero-downtime-zero-gaps-solana-grpc-streaming)，以及在[加密貨幣史上最大規模清算事件](https://www.alchemy.com/blog/best-uptime-biggest-liquidation-event-in-crypto)中機群仍維持運作的同一套容錯機制。

## 為什麼自行掌控邊緣層能讓路由更聰明？

RPC 流量大多是動態的。`eth_call` 或交易送出這類請求，沒辦法像靜態圖片那樣被快取，所以一般 CDN 的主要手法（把快取內容放在使用者附近提供）大多派不上用場，請求終究還是得送達一個活躍的節點。與其租用 CDN，自行掌控邊緣層讓我們能針對這種特定的流量型態進行調校，也讓路由能利用第三方層永遠看不到的訊號，例如：此時此刻哪個節點對這個方法與這條鏈來說，是已同步、健康且最快的。

這也代表我們不會和不相干的流量共用這一層。在共用 CDN 上，一場與你的應用程式毫無關係的事故，仍然可能表現成你這邊的延遲飆升，而修復的時程也不由你掌控。

供應商之間真正產生分歧的地方，其實是在區域選擇之下的每一個環節，因為那正是路由層究竟知道每台機器在做什麼、還是只能用猜的分水嶺。

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Approach", dataType: "object" },
      {
        key: "2",
        width: 260,
        title: "How a request gets routed",
        dataType: "object",
      },
      {
        key: "3",
        width: 260,
        title: "What the routing layer knows",
        dataType: "object",
      },
    ],
    data: [
      {
        "1": {
          title: "<p>Brokered across third-party operators</p>",
          tooltip: "",
          icon: "",
        },
        "2": {
          title:
            "<p>A balancer scores independent operators and forwards each request to one of them</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title:
            "<p>Health and latency as the operators expose it, at endpoint level rather than node level</p>",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        "1": {
          title:
            "<p>Own fleet, geo-routed and then selected per request (Cortex)</p>",
          tooltip: "",
          icon: "",
        },
        "2": {
          title:
            "<p>A single endpoint resolves to the nearest healthy serving stack for that network, and the gateway in front of the fleet picks the node</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title:
            "<p>Every node's health, whether it agrees with the current state of the chain, recent measured latency, and which client it runs</p>",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
    ],
  }}
/>

我們選擇自行營運，是因為這是唯一能確保路徑上每一段都不屬於別人的做法。這需要我們付出實際的工程成本，作為交換，我們能夠調校整條路徑，而不必為了處理慢的那一段去向別人開工單求助。

## 這對像 Base 這樣的鏈意味著什麼？

Base 是一個很好的壓力測試對象，因為這條鏈本身就很快。它透過 [Flashblocks 提供 200ms 增量區塊更新](https://docs.base.org/base-chain/flashblocks)，因此如果 RPC 路徑的回應時間比這還長，瓶頸就會出在路徑上，而不是鏈本身。從任何區域發出的 Base 呼叫都會進入最近的邊緣節點，被配對到一個資料為最新狀態的健康 Base 節點，一旦該節點狀態變差，也會在該區域內重新路由。當鏈每 200ms 就預覽一次新狀態時，減少一百毫秒的網路開銷，重要性只會更高，而不是更低。

一致性是比較難達成的保證，也是最值得用來評斷我們的地方。[Polymarket](https://www.alchemy.com/case-studies/polymarket) 就是壓力測試的案例。在 2024 年美國大選期間，他們同時應付了超過 125,000 名並發使用者以及 33 億美元的下注金額，並依據 p99 延遲動態重新路由請求，以維持效能。他們的關鍵資料擷取（ingestion）流程也單獨提速了 2.5 倍，從大約 250ms 降到大約 100ms。在某個安靜的下午、於單一區域量測到的中位數，幾乎無法告訴你在鑄造（mint）熱潮或連環清算期間，使用者實際會遇到什麼狀況。如果你不想只聽我們說，而想親自查證，我們的[基準測試方法](https://www.alchemy.com/blog/how-we-benchmark-rpc-performance)會把延遲和成功率、失敗請求數一併量測，而[供應商基準測試結果](https://www.alchemy.com/benchmarks)是公開的。

## 在同一條路由路徑上開發

Alchemy 上的每一個應用程式已經在使用這套引擎進行路由，不需要任何設定。申請一個[免費 RPC 端點](https://www.alchemy.com/rpc-api)，你的讀取與寫入就會走這裡所描述的同一條逐請求路徑。不需要簽約，不用排候補名單，也不必更換端點。你的 [Data API](https://www.alchemy.com/docs/data) 呼叫也運行在同一套路由上。

如果你需要固定在單一區域的節點、單租戶（single-tenant）隔離，或是路徑上使用自訂二進位檔，[Alchemy Dedicated Clusters](https://www.alchemy.com/dedicated-clusters) 會在單租戶硬體上執行相同的路由機制，而且你也可以選擇在流量超出叢集容量時，自動容錯移轉到共用機群。無論哪種方式，你應用程式背後的請求處理，都會變成你不必再去操心的部分。

## 常見問題

### 哪個 RPC 供應商在全球各區域提供最低延遲？

根據我們[持續更新的基準測試](https://www.alchemy.com/benchmarks)，Alchemy 跨 EVM 鏈的平均回應時間低於 15ms，這是在已建立連線上量測的「熱」請求-回應時間。該頁面每隔幾分鐘就會重新計算一次，請以上面的即時供應商排名為準。運行正常時間（uptime）為 [99.995%](https://www.alchemy.com/blog/the-tech-behind-cortex)。每個認真經營的供應商都會把你導向鄰近區域。Cortex 在這個區域內部做得更深入，把每個請求配對到經過健康檢查、並確認其與目前鏈狀態一致的節點。

### 哪個 RPC 供應商能以最低延遲支援 Base RPC？

Alchemy 上的 Base RPC 會從最近的邊緣區域進入，接著被配對到一個資料為最新狀態的健康 Base 節點。這一點在 Base 上比在較慢的鏈上更重要，因為 Flashblocks 每 200ms 就推送一次區塊更新，就算節點只是稍微落後，也可能回傳已經過時的狀態。

### Cortex 如何選擇由哪個節點服務一個 RPC 請求？

這項工作由兩層共同分擔。邊緣控制平面會先挑出支援所請求網路、且最近的健康服務堆疊，接著由節點叢集前方的閘道挑選實際節點，依據健康狀態、與目前鏈狀態的一致性、近期實測延遲，以及所呼叫的方法，逐步縮小候選範圍。

### 按請求路由會增加延遲嗎？

路由只佔回應時間預算中很小的一部分，而不是那個headline 數字的主要成因。低於 15ms 的數字，是跨 EVM 鏈的「熱」請求-回應時間，並不是路由決策本身的成本，也不同於涵蓋所有鏈與所有產品、平台整體低於 50ms 的平均值。區域選擇發生在邊緣，靠近使用者；節點選擇則發生在更深層、位於機群前方的閘道處。

### 當一個 RPC 節點故障時會發生什麼？

容錯移轉是路由決策的一部分，而不是需要你自己實作的重試機制。當某個節點下線時，Cortex 會自動把流量改送到健康的節點，而其情境感知路由也會繞開壅塞、落後，或執行有臭蟲客戶端的節點。如果整個區域狀態變差，請求會轉移到次要區域，並由即時複寫機制維持資料一致性。
