030-企业级最佳实践
🎯 难度: 专家级 | ⏱️ 预计时间: 150分钟 | 📋 前置: 029-requests源码深度解析
学习目标
完成本章节后,你将能够:
- 掌握企业级requests应用的最佳实践
- 实现高可用、高性能的HTTP客户端
- 建立完善的监控和日志体系
- 处理复杂的企业级场景和需求
- 制定代码规范和架构标准
企业级架构设计
分层架构模式
企业级HTTP客户端设计
# 文件路径: enterprise/http_client.py
"""
企业级HTTP客户端实现
"""
import requests
import time
import logging
import threading
from typing import Dict, Any, Optional, Callable, List
from dataclasses import dataclass, field
from enum import Enum
from urllib.parse import urljoin
import json
from datetime import datetime, timedelta
class ServiceTier(Enum):
"""服务等级"""
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
@dataclass
class ServiceConfig:
"""服务配置"""
base_url: str
timeout: int = 30
max_retries: int = 3
retry_backoff: float = 1.0
tier: ServiceTier = ServiceTier.MEDIUM
circuit_breaker_threshold: int = 5
circuit_breaker_timeout: int = 60
rate_limit: Optional[int] = None
auth_config: Optional[Dict[str, Any]] = None
custom_headers: Dict[str, str] = field(default_factory=dict)
@dataclass
class RequestMetrics:
"""请求指标"""
start_time: datetime
end_time: Optional[datetime] = None
duration: Optional[float] = None
status_code: Optional[int] = None
error: Optional[str] = None
retries: int = 0
cache_hit: bool = False
class CircuitBreaker:
"""熔断器实现"""
def __init__(self, threshold: int = 5, timeout: int = 60):
self.threshold = threshold
self.timeout = timeout
self.failure_count = 0
self.last_failure_time = None
self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
self._lock = threading.Lock()
def call(self, func: Callable, *args, **kwargs):
"""执行函数调用,带熔断保护"""
with self._lock:
if self.state == 'OPEN':
if time.time() - self.last_failure_time > self.timeout:
self.state = 'HALF_OPEN'
else:
raise Exception("Circuit breaker is OPEN")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise e
def _on_success(self):
"""成功回调"""
with self._lock:
self.failure_count = 0
self.state = 'CLOSED'
def _on_failure(self):
"""失败回调"""
with self._lock:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.threshold:
self.state = 'OPEN'
class RateLimiter:
"""速率限制器"""
def __init__(self, max_calls: int, time_window: int = 60):
self.max_calls = max_calls
self.time_window = time_window
self.calls = []
self._lock = threading.Lock()
def acquire(self) -> bool:
"""获取调用许可"""
with self._lock:
now = time.time()
# 清理过期的调用记录
self.calls = [call_time for call_time in self.calls
if now - call_time < self.time_window]
if len(self.calls) < self.max_calls:
self.calls.append(now)
return True
return False
def wait_time(self) -> float:
"""计算需要等待的时间"""
if not self.calls:
return 0
oldest_call = min(self.calls)
return max(0, self.time_window - (time.time() - oldest_call))
class RequestCache:
"""请求缓存"""
def __init__(self, max_size: int = 1000, ttl: int = 300):
self.max_size = max_size
self.ttl = ttl
self.cache = {
}
self._lock = threading.Lock()
def get(self, key: str) -> Optional[Any]:
"""获取缓存"""
with self._lock:
if key in self.cache:
value, timestamp = self.cache[key]
if time.time() - timestamp < self.ttl:
return value
else:
del self.cache[key]
return None
def set(self, key: str, value: Any):
"""设置缓存"""
with self._lock:
# 清理过期缓存
self._cleanup()
# 如果缓存已满,删除最旧的条目
if len(self.cache) >= self.max_size:
oldest_key = min(self.cache.keys(),
key=lambda k: self.cache[k][1])
del self.cache[oldest_key]
self.cache[key] = (value, time.time())
def _cleanup(self):
"""清理过期缓存"""
now = time.time()
expired_keys = [key for key, (_, timestamp) in self.cache.items()
if now - timestamp >= self.ttl]
for key in expired_keys:
del self.cache[key]
class EnterpriseHTTPClient:
"""企业级HTTP客户端"""
def __init__(self, config: ServiceConfig):
self.config = config
self.session = self._create_session()
self.circuit_breaker = CircuitBreaker(
threshold=config.circuit_breaker_threshold,
timeout=config.circuit_breaker_timeout
)
self.rate_limiter = RateLimiter(config.rate_limit) if config.rate_limit else None
self.cache = RequestCache()
self.metrics = []
self.logger = self._setup_logger()
def _create_session(self) -> requests.Session:
"""创建配置好的会话"""
session = requests.Session()
# 设置适配器
adapter = requests.adapters.HTTPAdapter(
pool_connections=20,
pool_maxsize=20,
max_retries=0 # 我们自己处理重试
)
session.mount('http://', adapter)
session.mount('https://', adapter)
# 设置默认头部
session.headers.update({
'User-Agent': 'EnterpriseClient/1.0',
'Accept': 'application/json',
'Content-Type': 'application/json',
**self.config.custom_headers
})
# 设置认证
if self.config.auth_config:
self._setup_auth(session)
return session
def _setup_auth(self, session: requests.Session):
"""设置认证"""
auth_type = self.config.auth_config.get('type')
if auth_type == 'basic':
from requests.auth import HTTPBasicAuth
session.auth = HTTPBasicAuth(
self.config.auth_config['username'],
self.config.auth_config['password']
)
elif auth_type == 'bearer':
session.headers['Authorization'] = f"Bearer {
self.config.auth_config['token']}"
elif auth_type == 'api_key':
key_name = self.config.auth_config.get('key_name', 'X-API-Key')
session.headers[key_name] = self.config.auth_config['api_key']
def _setup_logger(self) -> logging.Logger:
"""设置日志记录器"""
logger = logging.getLogger(f'enterprise_client_{
id(self)}')
logger.setLevel(logging.INFO)
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
"""发送请求"""
url = urljoin(self.config.base_url, endpoint)
# 创建请求指标
metrics = RequestMetrics(start_time=datetime.now())
try:
# 速率限制检查
if self.rate_limiter and not self.rate_limiter.acquire():
wait_time = self.rate_limiter.wait_time()
self.logger.warning(f"Rate limit exceeded, waiting {
wait_time:.2f}s")
time.sleep(wait_time)
if not self.rate_limiter.acquire():
raise Exception("Rate limit still exceeded after waiting")
# 检查缓存(仅对GET请求)
cache_key = None
if method.upper() == 'GET':
cache_key = self._generate_cache_key(url, kwargs)
cached_response = self.cache.get(cache_key)
if cached_response:
metrics.cache_hit = True
metrics.end_time = datetime.now()
metrics.duration = (metrics.end_time - metrics.start_time).total_seconds()
self.metrics.append(metrics)
self.logger.info(f"Cache hit for {
method} {
url}")
return cached_response
# 使用熔断器执行请求
response = self.circuit_breaker.call(
self._execute_request_with_retry,
method, url, metrics, **kwargs
)
# 缓存成功的GET响应
if method.upper() == 'GET' and response.status_code == 200 and cache_key:
self.cache.set(cache_key, response)
return response
except Exception as e:
metrics.error = str(e)
metrics.end_time = datetime.now()
metrics.duration = (metrics.end_time - metrics.start_time).total_seconds()
self.metrics.append(metrics)
self.logger.error(f"Request failed: {
method} {
url} - {
e}")
raise
def _execute_request_with_retry(self, method: str, url: str,
metrics: RequestMetrics, **kwargs) -> requests.Response:
"""执行带重试的请求"""
last_exception = None
for attempt in range(self.config.max_retries + 1):
try:
if attempt > 0:
wait_time = self.config.retry_backoff * (2 ** (attempt - 1))
self.logger.info(f"Retrying request (attempt {
attempt + 1}) after


1万+

被折叠的 条评论
为什么被折叠?



