Un pool RPC multi-proveedor debe distinguir cuatro clases de fallo: transporte, protocolo, semántico (head obsoleto) y límite de tasa. Un try/catch ingenuo solo captura fallos de transporte, dejando lecturas obsoletas y errores silenciosos sin manejar. La regla de seguridad central es un control de frescura: cada respuesta se marca con el head observado del endpoint, y el pool se niega a satisfacer una lectura 'latest' desde un endpoint por debajo de la marca de agua alta del pool. Los disyuntores con umbrales de eyección específicos de blockchain y sondas semiabiertas que usan lecturas reales baratas evitan fallos repetidos. Este artículo proporciona una capa de despacho Node.js ejecutable, una tabla de resultados para autoevaluación y solución de problemas para heads divididos, respuestas en caché y estampidas de reintentos.
Clasificación de fallos para pools RPC
Un pool RPC multi-proveedor debe clasificar los fallos en cuatro clases distintas, porque cada una requiere una respuesta diferente. Los fallos de transporte (conexión rechazada, fallo de handshake TLS, fallo de resolución DNS) son los más fáciles de detectar y son los únicos que maneja un try/catch ingenuo. Los fallos de protocolo ocurren cuando el endpoint devuelve una respuesta JSON-RPC 2.0 válida que contiene un objeto de error con un código, según lo definido en la Especificación JSON-RPC 2.0. Los fallos semánticos son los más peligrosos: el endpoint devuelve HTTP 200 con un resultado válido, pero el resultado está obsoleto—por ejemplo, eth_blockNumber devuelve una altura de bloque inferior a la marca de agua alta actual del pool. Los fallos por límite de tasa devuelven HTTP 429, a veces con un encabezado Retry-After, y deben manejarse con una espera en lugar de un reintento inmediato.
La especificación JSON-RPC de Ethereum define eth_blockNumber y etiquetas de bloque como latest, safe y finalized. Estas son las señales autoritativas de frescura. Un pool que trata cualquier HTTP 200 como éxito servirá lecturas obsoletas durante fallos parciales, exactamente cuando la corrección es más importante. La página de consistencia multi-endpoint y retraso de head cubre la disciplina de lectura una vez que existe un pool; este artículo define el contrato del pool en sí.
- Fallo de transporte: error de conexión, TLS o DNS—sin respuesta HTTP.
- Fallo de protocolo: objeto de error JSON-RPC con un código (p. ej., -32603 error interno).
- Fallo semántico: HTTP 200 con un resultado obsoleto o inconsistente con el head del pool.
- Fallo por límite de tasa: HTTP 429, opcionalmente con Retry-After; requiere retroceso, no failover inmediato.
Estrategias de despacho: ponderado, round-robin y least-outstanding
El despacho round-robin es activamente dañino para cargas de trabajo de lectura tras escritura en una cadena con retraso de head por nodo. Si una escritura se envía al proveedor A y la lectura posterior se despacha al proveedor B, que está unos bloques por detrás, la lectura puede no ver la escritura. El despacho ponderado puede ayudar si los pesos reflejan la frescura del head observada, pero los pesos estáticos se vuelven obsoletos. El despacho least-outstanding—enviar al endpoint con menos solicitudes en vuelo—equilibra la carga pero ignora la altura del head. El valor predeterminado más seguro para lectura tras escritura es fijar las lecturas al mismo endpoint que sirvió la escritura hasta que la escritura se confirme, y luego permitir failover solo a endpoints en o por encima de la marca de agua alta del pool.
Para cargas de trabajo de solo lectura, least-outstanding combinado con un control de frescura funciona bien. La página de failover RPC multirregión y enrutamiento consciente de latencia cubre la puntuación por región y RTT; este artículo se centra en el control de altura de head que hace segura cualquier estrategia de despacho.
- Round-robin: simple pero inseguro para lectura tras escritura cuando los nodos se retrasan.
- Ponderado: puede incorporar frescura de head pero requiere actualizaciones dinámicas de pesos.
- Least-outstanding: bueno para carga de solo lectura, pero debe combinarse con un control de frescura.
- Fijación: para lectura tras escritura, fijar al endpoint de escritura hasta que se confirme.
El control de frescura: marca de agua alta del head
El control de frescura es la única regla que hace seguro el failover: cada respuesta despachada se marca con el head observado del endpoint (vía eth_blockNumber), y el pool se niega a satisfacer una lectura 'latest' desde un endpoint cuyo head esté por debajo de la marca de agua alta del pool. La marca de agua alta es el head máximo observado entre todos los endpoints saludables, actualizada en cada muestreo exitoso de head. Cuando llega una solicitud de lectura, el despachador selecciona un endpoint cuyo último head observado esté en o por encima de la marca de agua alta. Si no existe tal endpoint, la solicitud espera o falla con un error claro en lugar de servir datos obsoletos.
Este control previene el fallo clásico de lectura obsoleta: un proveedor que está sincronizando o retrasado responde solicitudes pero devuelve datos antiguos. La página de consistencia multi-endpoint y retraso de head detalla monotonicidad y lecturas por quórum; el control aquí es la aplicación a nivel de pool.
// Freshness gate: only dispatch to endpoints at or above high-water mark
function selectEndpoint(pool, request) {
const hwm = pool.highWaterMark;
const eligible = pool.endpoints.filter(ep =>
ep.state === 'closed' &&
ep.lastHead >= hwm &&
ep.inFlight < ep.maxInFlight
);
if (eligible.length === 0) {
throw new Error('No fresh endpoint available; high-water mark=' + hwm);
}
// Least-outstanding among eligible
return eligible.reduce((a, b) => a.inFlight <= b.inFlight ? a : b);
}Estados del disyuntor con eyección específica de blockchain
La máquina de estados del disyuntor—cerrado, abierto, semiabierto—proviene de 'Release It!' de Michael Nygard y está implementada en bibliotecas como Opossum. En un pool RPC de blockchain, los umbrales de eyección deben ser específicos de blockchain: un solo fallo semántico (head obsoleto) debe eyectar un endpoint inmediatamente, mientras que los fallos de transporte pueden tolerar algunos reintentos. La histéresis previene el parpadeo: tras la eyección, el endpoint permanece abierto durante un período de enfriamiento, luego entra en semiabierto y se sondea con una lectura real barata (p. ej., eth_blockNumber) en lugar de un ping TCP. Si la sonda devuelve un head en o por encima de la marca de agua alta, el endpoint vuelve a cerrado; de lo contrario, se reabre.
La sonda semiabierta debe usar una lectura real porque un ping TCP puede tener éxito mientras el nodo aún está sincronizando y decenas de miles de bloques por detrás. La página de monitoreo, métricas y alertas de nodos RPC cubre la observabilidad para estos estados.
- Cerrado: operación normal; los fallos incrementan un contador.
- Abierto: endpoint eyectado; sin tráfico hasta que expire el enfriamiento.
- Semiabierto: una única solicitud de sonda (eth_blockNumber) prueba la frescura; el éxito cierra, el fallo reabre.
- Umbrales de eyección: fallo semántico = eyección inmediata; fallo de transporte = 3 fallos consecutivos; límite de tasa = retroceso, no eyección.
Sondas de salud que no pueden ser engañadas
Una sonda de salud que solo verifica conectividad TCP o HTTP 200 es fácilmente engañada por un nodo que está sincronizando. La sonda correcta compara el head de cada endpoint contra el máximo del pool y contra el tiempo de bloque observado de la cadena. Si el head de un endpoint está más de unos pocos tiempos de bloque por detrás del máximo del pool, se considera obsoleto y se eyecta. El tiempo de bloque de la cadena puede estimarse a partir de la diferencia en las marcas de tiempo de bloques durante una ventana; un endpoint que está más de, digamos, tres tiempos de bloque por detrás probablemente está sincronizando o estancado.
Esta sonda debe ejecutarse periódicamente (p. ej., cada pocos segundos) y actualizar la marca de agua alta. La página de reutilización de conexiones RPC y keep-alive HTTP/2 cubre la eficiencia de transporte para estas sondas.
// Health probe: compare head against pool max and block time
async function probeEndpoint(ep, pool) {
try {
const headHex = await ep.call('eth_blockNumber', []);
const head = parseInt(headHex, 16);
ep.lastHead = head;
ep.lastProbe = Date.now();
const poolMax = Math.max(...pool.endpoints.map(e => e.lastHead || 0));
const blockTimeMs = pool.estimatedBlockTimeMs || 12000;
const lagBlocks = poolMax - head;
const lagMs = lagBlocks * blockTimeMs;
if (lagMs > 3 * blockTimeMs) {
ep.state = 'open';
ep.ejectUntil = Date.now() + pool.cooldownMs;
return false;
}
if (ep.state === 'half-open') ep.state = 'closed';
return true;
} catch (err) {
ep.state = 'open';
ep.ejectUntil = Date.now() + pool.cooldownMs;
return false;
}
}Capa de despacho Node.js ejecutable
La siguiente capa de despacho implementa un registro de endpoints, estado por endpoint, una marca de agua alta de head, un temporizador de eyección, una sonda semiabierta y una espera consciente de 429. Usa fetch para llamadas HTTP y asume que cada endpoint expone una interfaz JSON-RPC 2.0. El despachador selecciona un endpoint elegible mediante el control de frescura, envía la solicitud y clasifica la respuesta. En 429, lee Retry-After y espera antes de reintentar el mismo endpoint; en fallo semántico, eyecta el endpoint y reintenta otro. La marca de agua alta se actualiza en cada muestreo exitoso de head.
Este código es un punto de partida; los despliegues en producción deben agregar métricas, registro y estado persistente. La página de encabezados de límite de tasa y manejo de Retry-After cubre la semántica de 429 en detalle.
class RpcPool {
constructor(endpoints, opts = {}) {
this.endpoints = endpoints.map(url => ({
url, state: 'closed', lastHead: 0, inFlight: 0,
maxInFlight: opts.maxInFlight || 10,
ejectUntil: 0, failures: 0
}));
this.highWaterMark = 0;
this.cooldownMs = opts.cooldownMs || 30000;
this.estimatedBlockTimeMs = opts.blockTimeMs || 12000;
}
async call(method, params) {
const ep = this.selectEndpoint();
ep.inFlight++;
try {
const res = await fetch(ep.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
});
if (res.status === 429) {
const retryAfter = res.headers.get('Retry-After');
const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 1000;
await new Promise(r => setTimeout(r, waitMs));
return this.call(method, params);
}
const json = await res.json();
if (json.error) {
ep.failures++;
if (ep.failures >= 3) this.eject(ep);
throw new Error('RPC error: ' + JSON.stringify(json.error));
}
if (method === 'eth_blockNumber') {
const head = parseInt(json.result, 16);
ep.lastHead = head;
this.highWaterMark = Math.max(this.highWaterMark, head);
}
ep.failures = 0;
return json.result;
} catch (err) {
ep.failures++;
if (ep.failures >= 3) this.eject(ep);
throw err;
} finally {
ep.inFlight--;
}
}
selectEndpoint() {
const now = Date.now();
const eligible = this.endpoints.filter(ep =>
ep.state === 'closed' && ep.lastHead >= this.highWaterMark &&
ep.inFlight < ep.maxInFlight && now > ep.ejectUntil
);
if (eligible.length === 0) throw new Error('No fresh endpoint');
return eligible.reduce((a, b) => a.inFlight <= b.inFlight ? a : b);
}
eject(ep) {
ep.state = 'open';
ep.ejectUntil = Date.now() + this.cooldownMs;
}
}Tabla de resultados: mide contra tus propios proveedores
Usa la siguiente tabla para registrar observaciones de tus propios proveedores. Ejecuta la capa de despacho contra tus endpoints durante al menos 24 horas, muestreando eth_blockNumber cada 10 segundos. Completa cada columna con tus valores medidos. Este es un método verificado por el lector; no se proporcionan números de referencia aquí porque el comportamiento del proveedor varía.
Columnas: URL del proveedor, retraso de head observado (bloques), fallos de transporte (recuento), fallos de protocolo (recuento), fallos semánticos (recuento), respuestas 429 (recuento), latencia promedio (ms), eventos de eyección (recuento).
- URL del proveedor: el endpoint bajo prueba.
- Retraso de head observado: diferencia entre el head del proveedor y la marca de agua alta del pool.
- Fallos de transporte: errores de conexión/TLS/DNS.
- Fallos de protocolo: objetos de error JSON-RPC.
- Fallos semánticos: HTTP 200 con head por debajo de la marca de agua alta.
- Respuestas 429: impactos de límite de tasa.
- Latencia promedio: tiempo medio de ida y vuelta para eth_blockNumber.
- Eventos de eyección: número de veces que se abrió el disyuntor.
Modos de fallo y solución de problemas
Los heads divididos entre proveedores ocurren cuando dos proveedores reportan heads diferentes y la marca de agua alta del pool no se actualiza de manera consistente. Esto puede suceder si el muestreo de head es poco frecuente o si el head de un proveedor se adelanta debido a una reorganización. La solución es muestrear heads con frecuencia y usar el head máximo observado como marca de agua alta, pero también detectar reorganizaciones comparando hashes de bloques. Un proveedor que sirve silenciosamente una respuesta en caché pasará una verificación de salud ingenua; el control de frescura lo detecta porque el head en caché estará por debajo de la marca de agua alta. Una sonda semiabierta que pasa durante una interrupción puede ocurrir si la sonda usa una respuesta en caché u obsoleta; usa siempre una llamada real a eth_blockNumber y compara contra el máximo del pool. La estampida de reintentos producida por un reintento ingenuo en todos los endpoints puede mitigarse agregando jitter a los retrasos de reintento y limitando el número de reintentos concurrentes.
La página de cobertura de solicitudes RPC para latencia de cola cubre la cobertura, que es una técnica diferente del failover. La página de failover RPC multirregión y enrutamiento consciente de latencia cubre el failover a nivel de región.
- Heads divididos: muestrea heads con frecuencia; detecta reorganizaciones vía hashes de bloques.
- Respuestas en caché: el control de frescura rechaza heads por debajo de la marca de agua alta.
- Sonda semiabierta que pasa durante una interrupción: usa eth_blockNumber real, no ping TCP.
- Estampida de reintentos: agrega jitter a los reintentos; limita los reintentos concurrentes.
Limitaciones y compensaciones
Un pool multi-proveedor agrega sobrecarga: solicitudes adicionales gastadas en el muestreo de head, el costo de fijar lecturas a un solo endpoint y la complejidad de mantener el estado por endpoint. El muestreo de head consume cuota y agrega latencia a la ruta de despacho. Fijar lecturas al endpoint de escritura puede reducir la efectividad del balanceo de carga y puede aumentar la latencia si ese endpoint es lento. Un pool no puede arreglar un problema de intención: si la lógica de la aplicación requiere una etiqueta de bloque específica (p. ej., finalized), el pool debe respetar esa etiqueta y no sustituirla por latest. La página de precios de RPC puede ayudar a estimar costos para solicitudes adicionales.
El pool tampoco puede garantizar consistencia entre proveedores si la cadena misma está reorganizándose; solo puede asegurar que la vista del pool sea monotónica. Para aplicaciones que requieren consistencia fuerte, considera usar un solo proveedor con un nodo dedicado, como se describe en la página de servicio API.
- Solicitudes adicionales: el muestreo de head consume cuota.
- Costo de fijación: balanceo de carga reducido, posible aumento de latencia.
- Desajuste de intención: el pool debe respetar las etiquetas de bloque, no anularlas.
- Reorganizaciones: el pool asegura una vista monotónica, no consistencia entre proveedores.
Próximos pasos y guías relacionadas
Para profundizar tu comprensión de las operaciones RPC multi-proveedor, explora el centro de aprendizaje de OnFinality para artículos relacionados. La página de failover RPC multirregión y enrutamiento consciente de latencia cubre la puntuación por región y RTT. La página de cobertura de solicitudes RPC para latencia de cola explica cómo competir con llamadas de solo lectura duplicadas. La página de consistencia multi-endpoint y retraso de head detalla la disciplina de lectura. La página de monitoreo, métricas y alertas de nodos RPC cubre la observabilidad. Para la selección de endpoints en múltiples cadenas, consulta la guía de endpoints RPC multicadena (RPC Assistant).
Para endpoints específicos de Ethereum, visita la página de la red Ethereum. Para estimar costos de solicitudes adicionales de muestreo de head, consulta precios de RPC. Para opciones de nodos dedicados, consulta la página de servicio API.
- Enrutamiento por región: failover RPC multirregión y enrutamiento consciente de latencia
- Cobertura: cobertura de solicitudes RPC para latencia de cola
- Consistencia: consistencia multi-endpoint y retraso de head
- Monitoreo: monitoreo, métricas y alertas de nodos RPC
- Selección de endpoints: guía de endpoints RPC multicadena (RPC Assistant)