---
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 成为所有网络、所有客户共用的单点故障。用[我们自己运营的裸金属](https://www.alchemy.com/blog/the-tech-behind-cortex)取代它之后，消除了这个依赖，把整条路径都置于我们自己的运维之下。现在你发出的每一个请求都要经过 Cortex，而路由器正是负责决定这个请求去向何处的部分。

对每一个请求，路由器都要决定路径：哪个区域接收它、哪个节点为它服务，以及如果该节点在处理过程中出现异常该怎么办。所谓“到你的链的最佳路径”，实际含义是：在此时此刻，能够正确回答这一具体调用的、延迟最低的健康节点。这个答案会随着负载和健康状况的变化而逐秒改变，这也是我们按每个请求重新计算，而不是解析一次就复用结果的原因。

区域这部分的选择，如今已是业界的标准做法。来自新加坡的请求和来自法兰克福的请求，在任何一家主流服务商那里都会从不同的入口进入，也都不需要你手动配置。我们额外提供的是第二个判断，是按每个请求而不是按每条连接做出的：在请求落地的区域内可用的节点中，哪一个是健康的、数据是最新的，并且适合这一具体调用。

<ImageBlock
  src="https://media.alchemy.com/blog/cortex-router-diagram.png"
  alt="Cortex router 架构：请求进入最近的边缘区域，控制平面选择健康的服务栈，集群前的网关选择节点"
  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)。

## 我们是如何在一个区域内选出节点的？

这里有两层各自负责不同的工作。在边缘，控制面会查看哪些服务集群支持你请求的网络、其中哪些是健康的，然后把请求发送到最近的那个。但选择具体节点这一步发生在更深的一层，在集群前面的网关中。这个网关是链路中第一个了解具体机器情况的环节。

要选好一个节点，仅仅选一个近的是不够的，因此在请求被转发出去之前，还有若干因素会进一步筛选候选节点。

- **健康状态，来自实时监控。** 一个节点追踪系统会持续进行健康检查，未通过检查的节点不会成为候选。这样可以在一开始就避免选中状态异常的后端，而不是等用户发现问题。
- **对当前状态的一致性。** 各节点所处的区块高度并不相同，如果把读请求交给响应最快的那个节点，可能会返回链已经越过的旧状态。一个一致性层确保[无论由哪个节点提供服务，请求看到的都是同一个规范化的链视图](https://www.alchemy.com/blog/from-one-node-to-cortex)，而带版本号的更新意味着即使在故障切换过程中，也不会读到过期数据。
- **实测延迟，而不仅仅是地理位置。** 候选节点会根据其近期实测延迟的指数加权移动平均值打分，这与负载均衡器多年来一直采用的方法相同。由于较旧的样本会逐渐从平均值中衰减出去，一个几分钟前才开始变慢的节点，排名会自然下降，无需任何人发出告警。
- **工作负载的形态。** 不同的节点客户端在同步行为、以及擅长处理的方法上各有差异，而归档节点和全节点持有的数据也不同，因此请求中的方法和参数会进一步收窄候选池。一个繁重的历史查询和一个简单的余额读取，不应该落在同一个节点上。

所以“最低延迟”从来不是我们在某处一次性打开的一个开关。它是针对每一次调用重新计算出来的结果，取决于这次调用从哪里进来、它在请求什么，以及此刻恰好哪些机器又快又健康。

## 当选中的节点不健康时会发生什么？

故障切换是路由决策的一部分，而不是事后挂上去的报警机制。当某个节点宕机时，[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 路径如果响应时间超过这个数字，瓶颈就变成了 RPC 而不是链本身。来自任何区域的 Base 调用都会进入最近的边缘节点，被匹配到一个健康且与链保持同步的 Base 节点，并且如果该节点状态恶化，会在区域内重新路由。当链每 200ms 就预览一次新状态时，去掉一百毫秒的网络开销，其重要性只会更高，而不是更低。

一致性是更难保证的部分，也是真正值得用来评判我们的部分。[Polymarket](https://www.alchemy.com/case-studies/polymarket) 就是压力测试案例。在 2024 年美国大选期间，他们同时承载了超过 125,000 名并发用户，下注金额高达 33 亿美元，请求会根据 p99 延迟动态重新路由，以维持性能。他们的关键数据摄取流程也单独实现了 2.5 倍提速，从约 250ms 降到约 100ms。某个安静的下午在单一区域测得的中位数，几乎说明不了你的用户在铸造高峰或清算连锁反应中的实际体验。如果你更愿意自己验证而不是听我们说，我们的[基准测试方法](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) 调用同样运行在这套体系之上。

如果你需要一个固定在某个区域的节点、单租户隔离，或者路径上的自定义二进制版本，[Alchemy Dedicated Clusters](https://www.alchemy.com/dedicated-clusters) 在单租户硬件上运行同一套路由，你也可以选择在流量超出集群容量时自动故障切换到共享集群。无论哪种方式，应用背后的请求处理都会变成一件你不再需要操心的事。

## 常见问题

### 哪家 RPC 服务商在全球各区域提供最低延迟？

Alchemy 在 EVM 链上的平均响应时间，根据我们[持续更新的基准测试](https://www.alchemy.com/benchmarks)，低于 15ms，测量方式是在已建立连接上的热请求-响应时间。该页面每隔几分钟就会重新计算一次，建议以页面上的当前服务商排名为准。正常运行时间为 [99.995%](https://www.alchemy.com/blog/the-tech-behind-cortex)。任何一家认真做事的服务商都会把你路由到附近的区域。Cortex 在此基础上更进一步，在区域内部把每个请求匹配到经过健康检查、并与链的当前状态保持一致的节点上。

### 哪家 RPC 服务商对 Base RPC 提供最低延迟支持？

Alchemy 上的 Base RPC 会进入最近的边缘区域，然后被匹配到一个健康且与链保持同步的 Base 节点。这一点在 Base 上比在较慢的链上更为重要，因为 Flashblocks 每 200ms 就推送一次区块更新，即使只是略微落后的节点，也可能返回已经过期的状态。

### Cortex 是如何选择由哪个节点来处理 RPC 请求的？

这项工作由两层共同完成。边缘控制面会选出支持所请求网络的、最近的健康服务集群，然后由集群前面的网关来选择具体节点，依据健康状态、与链当前状态的一致性、近期实测延迟以及所调用的方法，逐步缩小候选范围。

### 按请求路由是否会增加延迟？

路由只占响应预算中很小的一部分，而不是决定整体表现的关键数字。低于 15ms 这一数字，是 EVM 链上的热请求-响应时间，并非路由决策本身所耗费的时间，也不同于覆盖所有链和所有产品的、平台层面低于 50ms 的平均值这一统计口径。区域选择发生在边缘、靠近用户的地方，而节点选择则发生在更深处，也就是集群前面的网关中。

### RPC 节点出现故障时会发生什么？

故障切换是路由决策的一部分，而不是你需要自己搭建的重试逻辑。当某个节点宕机时，Cortex 会自动将流量重新路由到健康的节点，其上下文感知路由同样会绕开那些拥塞、延迟滞后或运行有缺陷客户端的节点。如果整个区域状态恶化，请求会转移到备用区域，同时实时数据复制会保持数据的一致性。
