Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Solución de problemas de RPC14 min de lectura

Conmutación por error de RPC multirregión: enrutamiento basado en latencia y conmutación por error basada en salud

Diseña la conmutación por error de RPC multirregión y multiendpoint para que el fallo de un proveedor, una región o un nodo degrade la latencia en lugar de romper tu aplicación.

TL;DR

La conmutación por error de RPC multirregión combina la conmutación por error basada en salud, el enrutamiento basado en latencia y los cortacircuitos para que el fallo de un solo proveedor, región o nodo degrade la latencia en lugar de romper la aplicación. El mecanismo central es un enrutador del lado del cliente que mantiene varios endpoints independientes, mide la salud y el tiempo de ida y vuelta (RTT) y despacha las solicitudes al endpoint más saludable mientras expulsa los que fallan. La salud debe verificarse tanto con liveness (eth_blockNumber/getHealth) como con una comprobación de retraso de cabeza, porque un nodo puede estar activo pero desactualizado. Las lecturas críticas para la corrección deben fijar un nivel de compromiso o finalidad, y los nuevos endpoints deben introducirse mediante una transición con sombra/división de tráfico. Todas las cifras de latencia y disponibilidad son medidas por el lector o documentadas/varían según el proveedor; este artículo no afirma cifras de región o tiempo de actividad específicas de OnFinality.

Por qué un RPC de un solo endpoint se rompe bajo carga de producción real

Un único endpoint de RPC es un punto único de fallo. Cuando el proveedor, la región o el nodo de ese endpoint se degrada, todas las solicitudes de tu aplicación se degradan con él: las lecturas de la billetera se estancan, los envíos de transacciones expiran y los indexadores se quedan atrás. El objetivo de la conmutación por error de RPC multirregión es hacer que ese fallo degrade la latencia en lugar de romper la aplicación.

El mecanismo es un enrutador del lado del cliente que mantiene varios endpoints independientes y elige uno por solicitud según la salud medida y el tiempo de ida y vuelta (RTT). Este es el mismo patrón que utilizan las arquitecturas genéricas de nube y API: AWS documenta patrones de diseño multirregión y Azure documenta el equilibrio de carga global y Traffic Manager, y ambos describen el enrutamiento basado en salud y en latencia como conceptos de primera clase. Para RPC de Web3 específicamente, la referencia de ingeniería de chainstack 'Plasma: RPC failover with eRPC' describe una capa de conmutación por error que se sitúa delante de múltiples endpoints de RPC y enruta por salud y latencia.

Antes de diseñar, decide qué topología se ajusta a tus requisitos. Un solo endpoint está bien para prototipos. Activo-pasivo con conmutación por error basada en salud es el mínimo para producción. Activo-activo entre regiones con enrutamiento basado en latencia es para cargas de trabajo sensibles a la latencia y de alto rendimiento. Un enrutador del lado del cliente que mantiene varios endpoints independientes y elige por salud y RTT medidos es el más flexible y es lo que construye este artículo.

Si todavía estás eligiendo un proveedor, empieza con Elegir un proveedor de RPC (RPC Assistant) y Endpoints de RPC públicos vs dedicados para producción. Para una cadena concreta, consulta Base y Latencia de RPC de Base: medición y optimización.

  • Un solo endpoint: lo más simple, sin redundancia, aceptable solo para prototipos.
  • Activo-pasivo: un primario, un standby, conmutación por error basada en salud ante fallos.
  • Activo-activo: múltiples regiones sirviendo tráfico, enrutamiento basado en latencia.
  • Enrutador del lado del cliente: varios endpoints independientes, elige por salud y RTT medidos.

Conmutación por error, balanceo de carga y hedging son herramientas diferentes

La conmutación por error reacciona a la salud: cuando el primario falla una comprobación de estado, el tráfico se mueve a un standby. El balanceo de carga distribuye por capacidad: las solicitudes se reparten entre endpoints para evitar saturar uno solo. El hedging compite por la latencia de cola: la misma solicitud se envía a dos endpoints y gana la primera respuesta. Los sistemas de producción suelen combinar los tres porque resuelven problemas diferentes.

Un error común es tratar el balanceo de carga como conmutación por error. El round-robin entre endpoints no ayuda si todos los endpoints comparten el mismo proveedor o región: fallan juntos. A la inversa, una conmutación por error estricta activo-pasivo no ayuda cuando el primario está sano pero es lento; necesitas enrutamiento basado en latencia o hedging para proteger la latencia de cola.

La combinación práctica es: conmutación por error basada en salud para disponibilidad, enrutamiento basado en latencia para rendimiento y hedging opcional para el percentil más lento. El artículo Monitoreo de nodos RPC, métricas y conmutación por error cubre el lado del monitoreo; este artículo se centra en la mecánica de enrutamiento y conmutación por error.

  • Conmutación por error: reacciona a la salud, mueve el tráfico ante fallos.
  • Balanceo de carga: distribuye por capacidad, evita la saturación.
  • Hedging: compite por la latencia de cola, gana la primera respuesta.
  • Combina los tres: disponibilidad + rendimiento + protección de cola.

Diseño de señales de salud: resultados pasivos y sondas activas

Las señales de salud vienen en dos formas. La salud pasiva observa los resultados reales de las solicitudes: éxito, error, timeout y latencia. La salud activa envía sondas según un calendario. La pasiva es barata y refleja el tráfico real; la activa detecta problemas antes de que las solicitudes de los usuarios lleguen a un endpoint defectuoso. Usa ambas.

Una sonda de liveness ingenua no es suficiente. Un endpoint puede estar 'activo' — responde a eth_blockNumber o getHealth — pero estar desactualizado, es decir, su cabeza está por detrás de la punta de la cadena. Un nodo por detrás de la punta de la cadena pasa una comprobación de liveness ingenua pero devuelve estado obsoleto, que es el modo de fallo más peligroso porque parece saludable. Añade siempre una comprobación de retraso de cabeza: compara el número de bloque reportado por el endpoint con una referencia (otro endpoint o una fuente confiable) y trata un retraso superior a un umbral como no saludable.

Conjunto de sondas concreto: eth_blockNumber para liveness y altura de cabeza, getHealth donde esté soportado, y una comparación de retraso de cabeza contra la altura máxima observada del pool. Para lecturas críticas para la corrección, fija también un nivel de compromiso o finalidad (por ejemplo, 'finalized' o una etiqueta de bloque específica) para que un endpoint ligeramente desactualizado no pueda devolver una respuesta diferente.

El artículo Latencia de RPC: causas, medición y soluciones explica cómo medir el RTT correctamente; reutiliza ese método para el bucle de sondas.

  • Pasiva: observa éxito, error, timeout y latencia de solicitudes reales.
  • Activa: sondas programadas para liveness y altura de cabeza.
  • Comprobación de retraso de cabeza: compara la altura del endpoint con el máximo del pool.
  • Fija el compromiso/finalidad para lecturas críticas para la corrección.

Verificar la independencia: el mismo proveedor o región no es redundancia

Dos endpoints en el mismo proveedor o en la misma región comparten un radio de impacto. Si ese proveedor tiene un incidente o esa región tiene un evento de red, ambos endpoints fallan juntos. No son redundancia real. Independencia significa diferentes proveedores, diferentes regiones e idealmente diferente infraestructura subyacente.

Una comprobación práctica de independencia: enumera el proveedor, la región y el ASN de cada endpoint. Si dos endpoints comparten alguno de esos, trátalos como un solo dominio de fallo para la planificación. Tu pool de conmutación por error debe tener al menos dos dominios de fallo independientes, y preferiblemente tres para el enrutamiento basado en latencia.

Esto también explica por qué un enrutador del lado del cliente que mantiene varios endpoints independientes es más robusto que un único balanceador de carga gestionado: el enrutador puede imponer la independencia y medir la salud directamente. Consulta Servicio de API para ver cómo se exponen normalmente los endpoints gestionados.

El enrutado por estado de salud, el circuit breaker y la eyección aquí descritos siguen el patrón de router de failover documentado por Chainstack en Plasma: RPC failover with eRPC; para los compromisos generales de topología multirregión, consulta el whitepaper de arquitecturas multirregión de AWS. Ambas son referencias de los conceptos, no endosos ni afirmaciones de rendimiento de OnFinality.

  • Mismo proveedor o región = radio de impacto compartido, no redundancia.
  • Independencia = diferente proveedor, región e infraestructura.
  • Planifica al menos dos dominios de fallo independientes, preferiblemente tres.
  • Un enrutador del lado del cliente puede imponer la independencia y medir la salud.

Mecánica del enrutamiento basado en latencia y la advertencia del geo-proxy

El enrutamiento basado en latencia envía cada solicitud al endpoint con el RTT medido más bajo. El mecanismo es un pequeño bucle de sondas que mide el RTT por endpoint y una función de puntuación que combina el RTT con la salud. Esto supera a la geografía estática porque la geografía es un proxy burdo del RTT real: las rutas de red, el peering y la congestión cambian la latencia real.

Azure Traffic Manager y AWS Global Accelerator ofrecen enrutamiento basado en geografía, pero la documentación los describe como métodos de enrutamiento burdos. Un pequeño bucle de sondas medidas supera a la geo estática porque refleja la ruta real en el momento de la solicitud. La referencia de chainstack 'Plasma: RPC failover with eRPC' describe un enfoque similar de salud medida para RPC.

La advertencia: medir el RTT desde tu cliente mide la ruta de tu cliente, no la de todos los usuarios. Si tus usuarios están distribuidos globalmente, mide desde el edge o desde una región representativa. Para la mayoría de las aplicaciones, un enrutador del lado del cliente que mide desde la propia región de la aplicación es suficiente y mucho más simple.

  • Enrutamiento basado en latencia = gana el RTT medido más bajo.
  • La geografía es un proxy burdo; el RTT medido es la señal real.
  • Mide desde la región de la aplicación o el edge, no desde un solo portátil.
  • Combina el RTT con la salud en la función de puntuación.

Cortacircuitos y expulsión: umbrales, semiabierto, histéresis

Un cortacircuitos expulsa un endpoint tras un umbral de errores consecutivos, luego permite periódicamente una prueba semiabierta para testear la recuperación. Si la prueba tiene éxito, el endpoint vuelve al pool; si falla, el cortacircuitos permanece abierto. La histéresis de recuperación (requerir varios éxitos antes de la reincorporación completa) evita el parpadeo.

Un cortacircuitos ingenuamente agresivo puede parpadear: expulsa ante un solo error transitorio y luego readmite de inmediato, causando que el tráfico oscile. Usa un umbral de errores consecutivos (por ejemplo, 3–5), un periodo de enfriamiento y una prueba semiabierta con un requisito de éxito. Los números exactos son medidos por el lector; ajústalos según tu propio perfil de errores.

La expulsión también debe considerar el retraso de cabeza: un endpoint que está activo pero desactualizado debe ser expulsado incluso si devuelve HTTP 200. Trata el retraso de cabeza como una señal de salud de primera clase en el cortacircuitos.

  • Umbral de errores consecutivos (p. ej., 3–5) antes de la expulsión.
  • Periodo de enfriamiento antes de la prueba semiabierta.
  • Prueba semiabierta con requisito de éxito antes de la reincorporación completa.
  • Trata el retraso de cabeza como una señal de salud de primera clase.

Trampas de consistencia de estado alrededor de la cabeza de la cadena

Diferentes endpoints, incluso de la misma cadena, pueden discrepar alrededor de la cabeza. Un endpoint puede estar uno o dos bloques por delante de otro, por lo que una lectura en 'latest' puede devolver un estado diferente según qué endpoint responda. Esto no es un error; es la naturaleza de una cabeza de cadena distribuida.

La solución es fijar un nivel de compromiso o finalidad para las lecturas críticas para la corrección. Por ejemplo, usa 'finalized' o una etiqueta de bloque específica, y no mezcles niveles entre el pool. Si mezclas 'latest' y 'finalized' entre endpoints, puedes obtener respuestas inconsistentes para la misma consulta lógica.

Para lecturas no críticas (paneles, estimaciones), 'latest' está bien, pero ten en cuenta que un endpoint ligeramente desactualizado puede devolver una cabeza un poco más antigua. La comprobación de retraso de cabeza en la sonda de salud es lo que mantiene acotada esa obsolescencia.

  • Los endpoints pueden discrepar alrededor de la cabeza; esto es normal.
  • Fija el compromiso/finalidad para lecturas críticas para la corrección.
  • No mezcles niveles entre el pool.
  • La comprobación de retraso de cabeza acota la obsolescencia en lecturas no críticas.

Transición y división de tráfico: introducir o reemplazar un endpoint de forma segura

Introducir un nuevo endpoint directamente en el pool es arriesgado. El patrón seguro es una transición con sombra/división de tráfico: escribe doble al endpoint sombra, compara las respuestas con el primario actual y luego desplaza un porcentaje del tráfico. Empieza con un porcentaje pequeño, vigila la tasa de errores y la latencia, y aumenta gradualmente.

El paso de comparación es importante: para la misma solicitud, ¿el sombra y el primario devuelven el mismo resultado en el mismo nivel de compromiso? Si no, investiga antes de desplazar el tráfico. Esto detecta diferencias de configuración, desajustes de cadena y nodos desactualizados.

Una vez que el endpoint sombra pasa la comparación y un pequeño porcentaje de tráfico está saludable, promuévelo a tráfico completo y degrada el endpoint antiguo a standby. Este es el mismo patrón canary que se usa en despliegues de API genéricos, aplicado a endpoints de RPC.

  • Escribe doble al endpoint sombra, compara respuestas.
  • Desplaza un pequeño porcentaje de tráfico, vigila la tasa de errores y la latencia.
  • Promueve a tráfico completo solo después de que la comparación pase.
  • Degrada el endpoint antiguo a standby, no lo elimines de inmediato.

Enrutador TypeScript ejecutable: sondas de salud, puntuación, cortacircuitos, despacho

El siguiente ejemplo de Node.js/TypeScript implementa un enrutador de RPC multirregión mínimo. Ejecuta un bucle de sondas de salud que mide el RTT y el retraso de cabeza por endpoint, puntúa los endpoints, mantiene el estado del cortacircuitos y despacha las solicitudes al endpoint más saludable mientras expulsa los que fallan. Usa solo fetch integrado y sin dependencias externas.

Ejecútalo con Node 18+ (que tiene fetch global). Reemplaza las URL de los endpoints por tus propios endpoints independientes. La salida esperada muestra los resultados de las sondas y el endpoint elegido para una solicitud de ejemplo.

// rpc-router.ts — run with: npx tsx rpc-router.ts (Node 18+)
type Endpoint = {
  url: string;
  name: string;
  rttMs: number;
  head: number;
  healthy: boolean;
  consecutiveErrors: number;
  breakerOpen: boolean;
  lastProbe: number;
};

const endpoints: Endpoint[] = [
  { url: 'https://rpc-a.example.com', name: 'region-a', rttMs: 0, head: 0, healthy: true, consecutiveErrors: 0, breakerOpen: false, lastProbe: 0 },
  { url: 'https://rpc-b.example.com', name: 'region-b', rttMs: 0, head: 0, healthy: true, consecutiveErrors: 0, breakerOpen: false, lastProbe: 0 },
  { url: 'https://rpc-c.example.com', name: 'region-c', rttMs: 0, head: 0, healthy: true, consecutiveErrors: 0, breakerOpen: false, lastProbe: 0 },
];

const ERROR_THRESHOLD = 3;
const HEAD_LAG_THRESHOLD = 5;
const PROBE_INTERVAL_MS = 5000;

async function rpc(url: string, method: string, params: any[] = []): Promise<any> {
  const start = Date.now();
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
  });
  const rtt = Date.now() - start;
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const json = await res.json();
  if (json.error) throw new Error(json.error.message);
  return { result: json.result, rtt };
}

async function probe(e: Endpoint): Promise<void> {
  try {
    const { result, rtt } = await rpc(e.url, 'eth_blockNumber');
    e.rttMs = rtt;
    e.head = parseInt(result, 16);
    e.healthy = true;
    e.consecutiveErrors = 0;
    e.breakerOpen = false;
  } catch (err) {
    e.consecutiveErrors += 1;
    e.healthy = false;
    if (e.consecutiveErrors >= ERROR_THRESHOLD) e.breakerOpen = true;
  }
  e.lastProbe = Date.now();
}

function score(e: Endpoint, maxHead: number): number {
  if (e.breakerOpen || !e.healthy) return -1;
  const lag = maxHead - e.head;
  if (lag > HEAD_LAG_THRESHOLD) return -1;
  return 1000 - e.rttMs - lag * 10;
}

function pick(): Endpoint | null {
  const maxHead = Math.max(...endpoints.map((e) => e.head));
  const ranked = endpoints
    .map((e) => ({ e, s: score(e, maxHead) }))
    .filter((x) => x.s >= 0)
    .sort((a, b) => b.s - a.s);
  return ranked.length ? ranked[0].e : null;
}

async function dispatch(method: string, params: any[] = []): Promise<any> {
  const maxHead = Math.max(...endpoints.map((e) => e.head));
  const ranked = endpoints
    .map((e) => ({ e, s: score(e, maxHead) }))
    .filter((x) => x.s >= 0)
    .sort((a, b) => b.s - a.s);
  for (const { e } of ranked) {
    try {
      const { result } = await rpc(e.url, method, params);
      return { endpoint: e.name, result };
    } catch {
      e.consecutiveErrors += 1;
      if (e.consecutiveErrors >= ERROR_THRESHOLD) e.breakerOpen = true;
    }
  }
  throw new Error('all endpoints failed');
}

async function main() {
  setInterval(() => endpoints.forEach(probe), PROBE_INTERVAL_MS);
  await Promise.all(endpoints.map(probe));
  console.log('probe results:', endpoints.map((e) => ({ name: e.name, rttMs: e.rttMs, head: e.head, healthy: e.healthy, breakerOpen: e.breakerOpen })));
  const chosen = pick();
  console.log('chosen endpoint:', chosen?.name ?? 'none');
  const out = await dispatch('eth_blockNumber');
  console.log('dispatch result:', out);
}

main().catch(console.error);

Tabla de resultados: mide tus propios endpoints

Usa la tabla de abajo para registrar tus propias mediciones. No confíes en cifras publicadas por proveedores; mide desde la región de tu aplicación. Ejecuta el bucle de sondas durante al menos 10 minutos y registra el RTT mediano y el p95, el retraso de cabeza y la tasa de errores por endpoint.

La nota 'documentado / varía según el proveedor' se aplica a cualquier cifra de latencia o tiempo de actividad publicada por un proveedor. Tus valores medidos son los que importan para las decisiones de enrutamiento.

  • Nombre del endpoint | Proveedor | Región | RTT mediano (ms) | RTT p95 (ms) | Retraso de cabeza (bloques) | Tasa de errores (%) | Notas
  • region-a | provider-1 | us-east | (medido) | (medido) | (medido) | (medido) | primario
  • region-b | provider-2 | eu-west | (medido) | (medido) | (medido) | (medido) | standby
  • region-c | provider-3 | ap-south | (medido) | (medido) | (medido) | (medido) | standby

Plan de verificación: inyecta un fallo y comprueba que el tráfico se mueve

La verificación no es opcional. Inyecta un endpoint defectuoso y comprueba que el tráfico se mueve a uno saludable, luego comprueba que se recupera cuando el endpoint vuelve. Usa un fallo controlado: apunta la URL de un endpoint a una dirección no enrutable o devuelve HTTP 500 desde un proxy local.

Pasos: (1) ejecuta el enrutador con tres endpoints; (2) confirma que el endpoint elegido es el saludable con menor RTT; (3) rompe el endpoint elegido; (4) confirma que el enrutador lo expulsa tras el umbral de errores consecutivos y despacha al siguiente endpoint saludable; (5) restaura el endpoint; (6) confirma que la prueba semiabierta lo readmite tras el enfriamiento.

Registra el comportamiento observado en la tabla de resultados. Si el enrutador no mueve el tráfico, revisa el intervalo de la sonda de salud, el umbral de errores y si el cortacircuitos está atascado abierto.

  • Inyecta fallo: URL no enrutable o proxy HTTP 500.
  • Comprueba que el tráfico se mueve tras el umbral de errores consecutivos.
  • Comprueba la recuperación tras el enfriamiento y la prueba semiabierta.
  • Registra el comportamiento observado en la tabla de resultados.

Fallos comunes y solución de problemas

El fallo más común es la falsa redundancia: dos endpoints en el mismo proveedor o región. Revisa el proveedor, la región y el ASN de cada endpoint. El segundo más común es una comprobación de liveness ingenua que no detecta el retraso de cabeza; añade la comparación de retraso de cabeza.

Otros fallos: un cortacircuitos que parpadea porque el umbral es demasiado bajo; mezclar niveles de compromiso entre el pool, causando lecturas inconsistentes; y un bucle de sondas que mide desde la región equivocada. Para cada uno, la solución está en el diseño de la señal de salud y la función de puntuación.

Si ves lecturas obsoletas intermitentes, comprueba si el endpoint está por detrás de la punta de la cadena y si tus lecturas fijan un nivel de finalidad. Si ves tráfico oscilando entre endpoints, aumenta el umbral de errores consecutivos y añade histéresis de recuperación.

  • Falsa redundancia: mismo proveedor/región/ASN.
  • Liveness ingenua: no detecta el retraso de cabeza, devuelve estado obsoleto.
  • Parpadeo del cortacircuitos: umbral demasiado bajo, sin histéresis.
  • Niveles de compromiso mezclados: lecturas inconsistentes.
  • Sonda desde la región equivocada: RTT engañoso.

Limitaciones, compensaciones y cuándo actualizar este diseño

Un enrutador del lado del cliente añade complejidad: debes mantener el bucle de sondas, la función de puntuación y el estado del cortacircuitos. También añade una pequeña cantidad de latencia por el tráfico de sondas. Para aplicaciones muy pequeñas, un balanceador de carga gestionado o un único proveedor con un standby puede ser más simple.

El enrutamiento basado en latencia optimiza la mediana pero puede perjudicar la cola si el endpoint con menor RTT es también el más cargado. El hedging puede ayudar a la cola pero duplica el volumen de solicitudes. No hay comida gratis; mide y ajusta.

Actualiza este diseño cuando cambie tu conjunto de endpoints, cuando cambien las regiones de tu proveedor o cuando cambie tu patrón de tráfico. Vuelve a ejecutar el plan de verificación después de cualquier cambio. Para implicaciones de precios y planes, consulta Precios de RPC.

  • El enrutador del lado del cliente añade complejidad y tráfico de sondas.
  • El enrutamiento por latencia optimiza la mediana, no siempre la cola.
  • El hedging ayuda a la cola pero duplica el volumen de solicitudes.
  • Vuelve a ejecutar la verificación tras cualquier cambio de endpoint o proveedor.

Próximos pasos: del enrutador al despliegue en producción

Empieza midiendo tus endpoints actuales con la tabla de resultados. Luego implementa el enrutador en un entorno de staging, ejecuta el plan de verificación y solo entonces despliégalo a producción. Mantén el endpoint antiguo como standby durante la transición.

Para un contexto más amplio, revisa Monitoreo de nodos RPC, métricas y conmutación por error y Endpoints de RPC públicos vs dedicados para producción. Para trabajo de latencia específico de una cadena, consulta Latencia de RPC de Base: medición y optimización y Base.

Si estás evaluando proveedores, usa Elegir un proveedor de RPC (RPC Assistant) y Precios de RPC. El centro de aprendizaje de OnFinality reúne el conjunto completo de guías de rendimiento y optimización.

  • Mide los endpoints actuales con la tabla de resultados.
  • Implementa y verifica el enrutador primero en staging.
  • Mantén el endpoint antiguo como standby durante la transición.
  • Revisa las guías de monitoreo y selección de proveedores para contexto.

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar