Reintentos y tiempo de espera
Retry y Timeout son mecanismos fundamentales para mejorar la resiliencia de los microservicios. Con Istio, puede configurar estas políticas sin cambiar el código de la aplicación.
Tabla de contenidos
- Descripción general
- Configuración de Timeout
- Configuración de Retry
- Combinación de Retry y Timeout
- Ejemplos prácticos
- Advertencias importantes
- Mejores prácticas
- Solución de problemas
Descripción general
¿Por qué Timeout y Retry?
Configuración de Timeout
Timeout básico
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews-timeout
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
timeout: 10s # Timeout after 10 secondsTimeout específico por ruta
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-timeouts
spec:
hosts:
- api.example.com
http:
# Fast response API - short timeout
- match:
- uri:
prefix: "/api/quick"
route:
- destination:
host: api-service
timeout: 1s
# Standard API
- match:
- uri:
prefix: "/api/standard"
route:
- destination:
host: api-service
timeout: 5s
# Heavy operations - long timeout
- match:
- uri:
prefix: "/api/batch"
route:
- destination:
host: api-service
timeout: 30sConfiguración de Retry
Retry básico
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews-retry
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
retries:
attempts: 3 # Maximum 3 retries
perTryTimeout: 2s # 2s timeout per attempt
retryOn: 5xx,reset,connect-failure,refused-stream # Retry conditionsCondiciones de Retry
| Condición | Descripción |
|---|---|
5xx | Errores HTTP 5xx |
gateway-error | Errores 502, 503, 504 |
reset | Restablecimiento de conexión |
connect-failure | Fallo de conexión |
refused-stream | HTTP/2 REFUSED_STREAM |
retriable-4xx | 409 Conflict |
retriable-status-codes | Códigos de estado personalizados |
Configuración avanzada de Retry
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: advanced-retry
spec:
hosts:
- payment-service
http:
- route:
- destination:
host: payment-service
retries:
attempts: 5
perTryTimeout: 3s
retryOn: 5xx,reset,connect-failure
retryRemoteLocalities: true # Retry to other regionsCombinación de Retry y Timeout
Timeouts en capas
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: layered-timeouts
spec:
hosts:
- frontend
http:
- route:
- destination:
host: frontend
timeout: 10s # Total timeout
retries:
attempts: 3
perTryTimeout: 3s # Per-retry timeoutCálculo: Tiempo máximo de espera = mín(timeout total, intentos × perTryTimeout) = mín(10s, 3 × 3s) = 9s
Configuración consciente de la idempotencia
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: idempotent-retry
spec:
hosts:
- order-service
http:
# GET requests - safe to retry
- match:
- method:
exact: GET
route:
- destination:
host: order-service
timeout: 5s
retries:
attempts: 3
perTryTimeout: 1s
retryOn: 5xx,reset
# POST requests - limited retry
- match:
- method:
exact: POST
route:
- destination:
host: order-service
timeout: 10s
retries:
attempts: 1 # Minimal retry if idempotency not guaranteed
perTryTimeout: 5s
retryOn: connect-failure,reset # Network issues onlyEjemplos prácticos
Ejemplo 1: Cadena de microservicios
# Frontend → Backend → Database
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: frontend
spec:
hosts:
- frontend
http:
- route:
- destination:
host: frontend
timeout: 15s # Consider entire chain
retries:
attempts: 2
perTryTimeout: 7s
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: backend
spec:
hosts:
- backend
http:
- route:
- destination:
host: backend
timeout: 10s # Consider database call
retries:
attempts: 3
perTryTimeout: 3s
retryOn: 5xx,reset
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: database
spec:
hosts:
- database
http:
- route:
- destination:
host: database
timeout: 5s
retries:
attempts: 2
perTryTimeout: 2s
retryOn: connect-failure,refused-streamEjemplo 2: Llamada a API externa
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: external-api
spec:
hosts:
- api.external.com
http:
- route:
- destination:
host: api.external.com
timeout: 30s # External APIs can be slow
retries:
attempts: 5 # External APIs have frequent transient failures
perTryTimeout: 5s
retryOn: 5xx,reset,connect-failure,gateway-error
---
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-api
spec:
hosts:
- api.external.com
ports:
- number: 443
name: https
protocol: HTTPS
location: MESH_EXTERNAL
resolution: DNSEjemplo 3: Combinado con Circuit Breaker
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: resilient-service
spec:
hosts:
- payment
http:
- route:
- destination:
host: payment
timeout: 10s
retries:
attempts: 3
perTryTimeout: 3s
retryOn: 5xx,reset,connect-failure
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: payment-circuit-breaker
spec:
host: payment
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50Advertencias importantes
Riesgos de Retry para solicitudes no idempotentes
Principio fundamental: Los reintentos automáticos en el nivel de Istio Proxy para solicitudes que cambian el estado, como POST, PUT y DELETE, pueden provocar problemas de consistencia de datos.
Escenario del problema
¿Por qué es peligroso?
- Creación duplicada: La solicitud POST realmente se completó correctamente, pero la respuesta se perdió debido a problemas de red; Proxy vuelve a intentar crear registros duplicados.
- Cambios de estado incorrectos: Las operaciones críticas para el negocio, como pagos y deducciones de inventario, pueden ejecutarse varias veces.
- No verificable: Istio Proxy no tiene forma de confirmar si la solicitud se completó correctamente.
Estrategia segura de Retry
Recomendado: Retry a nivel de aplicación
# Istio: Only retry network issues
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- match:
- method:
exact: POST
route:
- destination:
host: order-service
timeout: 10s
retries:
attempts: 1 # Disable retry
perTryTimeout: 5s
retryOn: connect-failure,refused-stream # Connection failures only# Application: Use Idempotency Key
import uuid
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def create_order_with_idempotency(order_data):
# Generate unique Idempotency Key
idempotency_key = str(uuid.uuid4())
session = requests.Session()
retry_strategy = Retry(
total=3,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["POST"], # Allow POST retry
backoff_factor=1
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
headers = {
"X-Idempotency-Key": idempotency_key # Prevent duplicates
}
response = session.post(
"http://order-service/orders",
json=order_data,
headers=headers
)
return response
# Server side: Validate Idempotency Key
@app.route('/orders', methods=['POST'])
def create_order():
idempotency_key = request.headers.get('X-Idempotency-Key')
# Check if already processed in Redis/DB
if redis.exists(f"order:idempotency:{idempotency_key}"):
# Already processed - return cached result
cached_result = redis.get(f"order:result:{idempotency_key}")
return jsonify(json.loads(cached_result)), 200
# Create new order
order = create_order_in_db(request.json)
# Cache Idempotency Key and result (24h TTL)
redis.setex(f"order:idempotency:{idempotency_key}", 86400, "1")
redis.setex(f"order:result:{idempotency_key}", 86400, json.dumps(order))
return jsonify(order), 201Seguridad de Retry según el método HTTP
| Método | Idempotente | Seguridad de Retry de Istio | Configuración recomendada |
|---|---|---|---|
| GET | Sí | Seguro | attempts: 3, retryOn: 5xx,reset |
| HEAD | Sí | Seguro | attempts: 3, retryOn: 5xx,reset |
| OPTIONS | Sí | Seguro | attempts: 3, retryOn: 5xx,reset |
| PUT | Condicional | Precaución | Necesita Idempotency Key |
| DELETE | Condicional | Precaución | Necesita Idempotency Key |
| POST | No | Peligroso | attempts: 1, application retry |
| PATCH | No | Peligroso | attempts: 1, application retry |
Casos seguros de Retry
# Read-only requests - safe
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service-reads
spec:
hosts:
- api-service
http:
- match:
- method:
regex: "GET|HEAD|OPTIONS"
route:
- destination:
host: api-service
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx,reset,connect-failure# Write requests with idempotency guaranteed
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: idempotent-writes
spec:
hosts:
- api-service
http:
- match:
- method:
exact: PUT
- headers:
x-idempotency-key:
regex: ".+" # Only when Idempotency Key present
route:
- destination:
host: api-service
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx,resetPrecaución al usarlo con Circuit Breaker
Circuit Breaker es eficaz para el aislamiento de fallos, pero no puede evitar la ejecución duplicada de solicitudes no idempotentes.
# Bad example: POST + Circuit Breaker + Retry
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: payment-service
spec:
hosts:
- payment-service
http:
- route:
- destination:
host: payment-service
retries:
attempts: 3 # 3 retries for POST is dangerous
retryOn: 5xx
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: payment-circuit-breaker
spec:
host: payment-service
trafficPolicy:
outlierDetection:
consecutiveErrors: 5
baseEjectionTime: 30s
# Result: Before the Circuit Breaker opens,
# duplicate payments can occur 3 times!# Good example: Use Circuit Breaker only, retry at application level
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: payment-service
spec:
hosts:
- payment-service
http:
- route:
- destination:
host: payment-service
timeout: 10s
retries:
attempts: 0 # Completely disable retry
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: payment-circuit-breaker
spec:
host: payment-service
trafficPolicy:
outlierDetection:
consecutiveErrors: 5
baseEjectionTime: 30sPautas prácticas
- GET/HEAD/OPTIONS: Puede usar Retry de Istio Proxy
- POST/PATCH: Deshabilite Retry de Istio, use Retry a nivel de aplicación + Idempotency Key
- PUT/DELETE: Use Retry de Istio solo cuando se garantice la idempotencia
- Operaciones críticas (pago/inventario/puntos): Deben tener validación a nivel de aplicación + Idempotency Key
Mejores prácticas
1. Guía de configuración de Timeout
# Good example: Appropriate timeout per layer
# Frontend: 15s
# API Gateway: 10s
# Backend Service: 5s
# Database: 3s
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-gateway
spec:
hosts:
- api-gateway
http:
- route:
- destination:
host: api-gateway
timeout: 10s
retries:
attempts: 2
perTryTimeout: 4s# Bad example: Timeout too long
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-gateway
spec:
hosts:
- api-gateway
http:
- route:
- destination:
host: api-gateway
timeout: 300s # 5 minutes is too long2. Estrategia de Retry
# Good example: Consider idempotency
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service
spec:
hosts:
- api-service
http:
# GET - safe to retry
- match:
- method:
exact: GET
route:
- destination:
host: api-service
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx,reset,connect-failure
# POST - retry carefully
- match:
- method:
exact: POST
route:
- destination:
host: api-service
retries:
attempts: 1
perTryTimeout: 5s
retryOn: connect-failure # Network issues only3. Retroceso exponencial
Istio realiza Retry con un intervalo predeterminado de 25ms, pero si se necesita un retroceso personalizado:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: backoff-retry
spec:
hosts:
- payment
http:
- route:
- destination:
host: payment
retries:
attempts: 5
perTryTimeout: 2s
retryOn: 5xx,reset
# Istio automatically increases retry interval
# 25ms, 50ms, 100ms, 200ms, 400ms4. Cálculo del Timeout total del sistema
# Frontend → API Gateway → Backend → Database
# Frontend: 20s
# API Gateway: 15s (must be less than Frontend)
# Backend: 10s (must be less than API Gateway)
# Database: 5s (must be less than Backend)
# Each layer should consider downstream timeout + overheadSolución de problemas
Timeout no funciona
# 1. Check VirtualService
kubectl get virtualservice -n <namespace>
kubectl describe virtualservice <name> -n <namespace>
# 2. Check Envoy configuration
istioctl proxy-config routes <pod-name> -n <namespace> -o json | grep timeout
# 3. Test actual timeout
kubectl exec -it <pod-name> -n <namespace> -c istio-proxy -- \
curl -v --max-time 5 http://backend-serviceDemasiados Retry
# Check retry metrics
kubectl exec -n <namespace> <pod-name> -c istio-proxy -- \
curl -s localhost:15000/stats/prometheus | grep retry
# Check retries for specific service
istio_requests_total{destination_service="backend.default.svc.cluster.local",response_flags="UR"}Prevención de una tormenta de Retry
# Use with Circuit Breaker
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: prevent-retry-storm
spec:
host: backend
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 10 # Limit pending requests
http2MaxRequests: 100
maxRequestsPerConnection: 1
outlierDetection:
consecutiveErrors: 3 # Fast circuit break
interval: 10s
baseEjectionTime: 30s