算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
backend-resilience-patterns【设计与创意】Use this skill when the user says 'resilience', 'circuit breaker', 'retry', 'bulkhead', 'timeout', 'fallback', 'resilience4j', 'fault tolerance', 'rate limiter', 'backoff', 'retry strategy', 'bulkhead pattern'. This skill applies production fault-tolerance patterns: circuit breaker, retry with backoff, bulkhead isolation, timeouts, and fallback handl…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Protect backend services from cascading failures by applying circuit breakers, retries with backoff, bulkheads, timeouts, and fallbacks. Resilience is not optional — every external call must be wrapped in fault-tolerance patterns.
Exact user phrases: "resilience", "circuit breaker", "retry", "bulkhead", "timeout", "fallback", "resilience4j", "fault tolerance", "retry strategy", "backoff", "rate limiter".
Configuration snippets or code. No file unless requested.
Pattern: {circuit-breaker|retry|bulkhead|timeout|fallback}
Config: {key parameters and values}
4 lines per pattern. 20 lines for full configuration.
What type of failure are you protecting against?
├── Slow responses (server busy, GC pause, overloaded)
│ └── Timeout + Circuit Breaker
├── Transient failures (network blip, connection reset, DNS failure)
│ └── Retry with backoff + Circuit Breaker
├── Resource exhaustion (thread pool, connection pool, memory)
│ └── Bulkhead + Circuit Breaker
├── Downstream service completely down
│ └── Circuit Breaker + Fallback
└── Client sending too many requests
└── Rate Limiter + Bulkhead
How should patterns be composed?
┌─────────────────────────────────┐
│ 1. Timeout (outermost) │ ← Fail fast
│ 2. Bulkhead (semaphore limit) │ ← Isolate resources
│ 3. Circuit Breaker │ ← Stop cascading failures
│ 4. Retry (innermost) │ ← Retry transient failures
│ 5. Fallback (catch-all) │ ← Degrade gracefully
└─────────────────────────────────┘
What is the nature of the downstream call?
├── Internal service (same datacenter) → 2-5s
├── External API (third-party) → 5-10s
├── Database query → 5-30s (depending on query)
├── Batch/analytics query → 30-60s
└── Must be LOWER than the caller's timeout of YOU
Every external call needs a timeout. No exceptions.
timeouts:
http_internal: { connect: 2s, read: 5s, write: 5s }
http_external: { connect: 5s, read: 10s, write: 10s }
database: { query: 10s, transaction: 30s }
message_queue: { publish: 5s, consume: 30s }
retry:
max_attempts: 3
backoff: exponential
initial_delay: 100ms
multiplier: 2
jitter: 50ms
max_delay: 10s
retry_on:
- 5xx (except 500 if idempotent)
- network_error
- timeout
do_not_retry:
- 4xx (client errors)
- 400 (bad request — will always fail)
- 401/403 (auth — will always fail)
- 404 (not found — will always fail)
- 409 (conflict — needs human resolution)
async function retryWithBackoff(fn, options = {}) {
const { maxAttempts = 3, initialDelay = 100, maxDelay = 10000 } = options;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) throw error;
if (!isRetryable(error)) throw error;
const delay = Math.min(initialDelay * Math.pow(2, attempt - 1), maxDelay);
const jitter = delay * 0.5 * Math.random();
await sleep(delay + jitter);
}
}
}
circuit_breaker:
per_dependency: true # One CB per downstream service
sliding_window_size: 10 # Number of calls to evaluate
minimum_calls: 5 # Minimum before evaluating
failure_rate_threshold: 50 # % failures to open circuit
wait_duration_open: 30s # Time before half-open
half_open_max_calls: 3 # Probe requests in half-open
record_slow_calls: true # Treat slow calls as failures
slow_call_duration_threshold: 5s # What counts as "slow"
// Node.js implementation using opossum
import CircuitBreaker from 'opossum';
const circuitBreaker = new CircuitBreaker(callExternalService, {
timeout: 5000,
errorThresholdPercentage: 50,
resetTimeout: 30000,
volumeThreshold: 5,
});
circuitBreaker.fallback(() => {
return { status: 'degraded', cachedData: getStaleData() };
});
circuitBreaker.on('open', () => logger.warn('Circuit breaker opened for payment-service'));
circuitBreaker.on('halfOpen', () => logger.info('Circuit breaker half-open for payment-service'));
circuitBreaker.on('close', () => logger.info('Circuit breaker closed for payment-service'));
bulkhead:
type: semaphore # semaphore (lightweight) or thread_pool
http_calls: # All external HTTP calls share this pool
max_concurrent: 20
max_wait_duration: 500ms
database_calls: # All database calls share this pool
max_concurrent: 10
max_wait_duration: 1s
// Java (Resilience4j) — Bulkhead with thread pool
ThreadPoolBulkheadConfig bulkheadConfig = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(10)
.coreThreadPoolSize(5)
.queueCapacity(20)
.build();
ThreadPoolBulkhead bulkhead = ThreadPoolBulkhead.of("payment-service", bulkheadConfig);
// Decorate supplier
Supplier<Payment> decorated = Decorators.ofSupplier(() -> paymentService.charge(orderId))
.withThreadPoolBulkhead(bulkhead)
.withCircuitBreaker(circuitBreaker)
.decorate();
interface FallbackStrategy<T> {
// Degraded response
static: () => T;
// Stale/cached data
cached: (key: string) => Promise<T | null>;
// Default value
default: T;
// Graceful degradation
graceful: (partial: Partial<T>) => T;
}
// Example: fallback from cache
async function getUserWithFallback(userId: string): Promise<User> {
try {
return await userService.getUser(userId);
} catch {
logger.warn('User service unavailable, serving cached data', { userId });
const cached = await cache.get(`user:${userId}`);
if (cached) return cached;
return { id: userId, name: 'Unknown', status: 'degraded' };
}
}
// Decorated chain
CircuitBreaker cb = CircuitBreaker.ofDefaults("paymentService");
Retry retry = Retry.ofDefaults("paymentService");
Bulkhead bh = Bulkhead.ofDefaults("paymentService");
TimeLimiter tl = TimeLimiter.of(Duration.ofSeconds(5));
Supplier<Payment> supplier = () -> client.charge(orderId);
Supplier<Payment> decorated = Decorators.ofSupplier(supplier)
.withCircuitBreaker(cb)
.withRetry(retry)
.withBulkhead(bh)
.withTimeLimiter(tl)
.withFallback(asList(cb, retry, bh, tl), e -> Payment.degraded())
.decorate();
CompletableFuture<Payment> future = CompletableFuture.supplyAsync(decorated);
var pipeline = new ResiliencePipelineBuilder<Payment>()
.AddRetry(new RetryStrategyOptions<Payment> {
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
Delay = TimeSpan.FromMilliseconds(100),
})
.AddCircuitBreaker(new CircuitBreakerStrategyOptions<Payment> {
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(30),
})
.AddTimeout(TimeSpan.FromSeconds(5))
.Build();
var payment = await pipeline.ExecuteAsync(ct => client.charge(orderId, ct));
import backoff
import circuitbreaker
@backoff.on_exception(backoff.expo, requests.exceptions.RequestException, max_tries=3)
@circuitbreaker.circuit(failure_threshold=5, recovery_timeout=30)
def charge_payment(order_id: str, amount: float) -> Payment:
response = requests.post(f"{PAYMENT_API}/charge", json={"order_id": order_id, "amount": amount})
response.raise_for_status()
return Payment(**response.json())
| Metric | Description | Alert Threshold |
|---|---|---|
| Circuit breaker state | open/closed/half-open | State=OPEN > 5 min |
| Call count | Total calls in window | N/A (baseline) |
| Failure rate | % of failed calls | > 30% |
| Slow call rate | % of slow calls | > 20% |
| Rejection count | Calls rejected due to open CB | > 0 (investigate) |
Client timeout (browser/mobile): 30s
→ API Gateway timeout: 25s
→ Service A timeout: 20s
→ Service A → Service B call: 10s
→ Service B internal timeout: 8s
→ Service B → Database call: 5s
Each layer must timeout before the layer above it. Otherwise, useless waiting occurs.
| Pattern | Latency Impact | Memory Impact |
|---|---|---|
| Timeout | None (unless triggered) | Minimal |
| Retry | Adds delay between attempts | Minimal |
| Circuit Breaker | <0.1ms per call (state check) | Per-circuit state |
| Bulkhead (semaphore) | <0.01ms | Minimal |
| Bulkhead (thread pool) | ~0.1ms queue | Per-pool threads |
| Fallback | None (unless triggered) | Per-fallback data |
| Pattern | Default | Notes |
|---|---|---|
| Timeout | 5s | Adjust per dependency |
| Retry | 3 attempts, 100ms base | Exponential, +jitter |
| Circuit Breaker | 50% threshold, 30s open | 10-slot window |
| Bulkhead | 20 concurrent | Per dependency group |
| Fallback | Degraded response | Never crash |
No artifact produced unless requested. Next skill: openapi-documentation — document the resilient API endpoints. Carry forward: timeout values, retry configuration, circuit breaker thresholds.
from typing import Callable, Any, Optional, Dict
from enum import Enum
from datetime import datetime, timedelta
import asyncio
import logging
logger = logging.getLogger("circuit-breaker")
class CircuitState(Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"
class CircuitBreaker:
def __init__(self, name: str, failure_threshold: int = 5, recovery_timeout: float = 30.0):
self.name = name
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.state = CircuitState.CLOSED
self.failure_count = 0
self.success_count = 0
self.last_failure_time: Optional[datetime] = None
self.last_state_change: datetime = datetime.now()
async def call(self, func: Callable, fallback: Optional[Callable] = None) -> Any:
if self.state == CircuitState.OPEN:
if self._should_attempt_recovery():
self.state = CircuitState.HALF_OPEN
logger.info(f"Circuit {self.name} → HALF_OPEN")
else:
logger.warning(f"Circuit {self.name} is OPEN, using fallback")
return await self._call_fallback(fallback) if fallback else None
try:
result = await func()
self._on_success()
return result
except Exception as e:
self._on_failure()
if self.state == CircuitState.HALF_OPEN:
self.state = CircuitState.OPEN
logger.error(f"Circuit {self.name} → OPEN (half-open test failed)")
logger.error(f"Circuit {self.name} failure: {e}")
return await self._call_fallback(fallback) if fallback else None
def _on_success(self):
if self.state == CircuitState.HALF_OPEN:
self.success_count += 1
if self.success_count >= 2:
self.state = CircuitState.CLOSED
self.failure_count = 0
self.success_count = 0
logger.info(f"Circuit {self.name} → CLOSED (recovered)")
else:
self.failure_count = 0
def _on_failure(self):
self.failure_count += 1
self.last_failure_time = datetime.now()
if self.state == CircuitState.CLOSED and self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
self.last_state_change = datetime.now()
logger.warning(f"Circuit {self.name} → OPEN ({self.failure_count} failures)")
def _should_attempt_recovery(self) -> bool:
if not self.last_failure_time:
return True
elapsed = (datetime.now() - self.last_failure_time).total_seconds()
return elapsed >= self.recovery_timeout
async def _call_fallback(self, fallback: Callable) -> Any:
try:
return await fallback()
except Exception as e:
logger.error(f"Fallback for {self.name} failed: {e}")
return None
def get_state(self) -> Dict:
return {
"name": self.name,
"state": self.state.value,
"failure_count": self.failure_count,
"failure_threshold": self.failure_threshold,
"recovery_timeout": self.recovery_timeout,
"last_failure": self.last_failure_time.isoformat() if self.last_failure_time else None,
}
import asyncio
import random
from typing import Callable, Any, List, Optional
class RetryHandler:
def __init__(self, max_retries: int = 3, base_delay: float = 1.0, max_delay: float = 30.0, jitter: bool = True):
self.max_retries = max_retries
self.base_delay = base_delay
self.max_delay = max_delay
self.jitter = jitter
async def execute(self, func: Callable, retryable_exceptions: Optional[List[type]] = None) -> Any:
last_exception = None
for attempt in range(self.max_retries + 1):
try:
return await func()
except Exception as e:
last_exception = e
if retryable_exceptions and not any(isinstance(e, exc) for exc in retryable_exceptions):
raise
if attempt < self.max_retries:
delay = self._calculate_delay(attempt)
logger.warning(f"Retry {attempt + 1}/{self.max_retries} after {delay:.2f}s: {e}")
await asyncio.sleep(delay)
raise last_exception
def _calculate_delay(self, attempt: int) -> float:
delay = min(self.base_delay * (2 ** attempt), self.max_delay)
if self.jitter:
delay *= 0.5 + random.random() * 0.5
return delay
What's the failure mode?
├── Upstream is slow (not failing)
│ └── Timeout + Circuit Breaker
│ ├── Set timeout < upstream's timeout
│ └── Open circuit when timeouts exceed threshold
│
├── Upstream returns errors
│ ├── Transient (network, 503, timeout)
│ │ └── Retry with exponential backoff + jitter
│ └── Permanent (400, 404, 422)
│ └── Don't retry — fail fast
│
├── Upstream is unavailable
│ └── Circuit Breaker + Fallback
│ ├── Open circuit after N failures
│ └── Return cached/stale/default response
│
└── Resource exhaustion
├── Connection pool starvation → Bulkhead
└── Memory/cpu saturation → Bulkhead + shedding
| Anti-Pattern | Why It Fails | Correct Approach |
|---|---|---|
| Infinite retries with no backoff | Overwhelms failing system | Fixed max retries + exponential backoff + jitter |
| Circuit breaker without fallback | User sees error despite CB open | Provide cached/stale/default response |
| Same timeout for all operations | Slow ops time out, fast ops wait too long | Per-operation timeout based on historical p99 |
| Bulkhead without monitoring | Can't tell when bulkhead is saturated | Monitor pool utilization, queue depth, rejections |
| Retry without idempotency safety | Duplicate writes on retry | Idempotency keys for all retried operations |
min(base * 2^attempt + jitter, max_delay) to prevent thundering herd. jitter = random(0, delay) spreads retries across time.` interface EventObserver { onEvent(event: T): Promise; }
class EventBus { private observers: Set<EventObserver> = new Set(); subscribe(observer: EventObserver): void { this.observers.add(observer); } unsubscribe(observer: EventObserver): void { this.observers.delete(observer); } async emit(event: T): Promise { const results = Array.from(this.observers).map(o => o.onEvent(event)); await Promise.allSettled(results); } } `
config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% over 5min | Critical | Page on-call |
| p99 latency | > 2s over 5min | Warning | Investigate |
| Throughput drop | > 50% over 1min | Critical | Check upstream |
| Queue depth | > 1000 over 1min | Warning | Scale consumers |
| Disk usage | > 85% | Warning | Clean or expand |
| Memory usage | > 90% heap | Critical | Restart or scale |
| Anti-Pattern | Symptom | Root Cause | Solution |
|---|---|---|---|
| Premature optimization | Complex code for no measured benefit | Guessing instead of profiling | Measure first, optimize based on data |
| Copy-paste reuse | Duplicate code across codebase | Lack of abstraction | Extract shared logic into libraries |
| Gold-plating | Features with no current requirement | Over-engineering | YAGNI — build what's needed now |
| Magical thinking | Assumptions without validation | Skipping error handling | Handle all failure modes explicitly |
Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).