feat: 性能监控基础设施、解密缓存及计费简化

- 新增 PerfRecorder 性能记录工具,支持采样率与慢请求日志
- 在请求管道中埋点:auth、body_read、json_parse、context_build、authorize、handle
- 流处理器增加 parse/conversion 耗时追踪与 perf_metrics 落库
- 解密服务添加 LRU 缓存,降低高频解密 CPU 开销
- 格式转换分层开关设计:全局 OFF 时回退到端点配置,而非一刀切拒绝
- 移除 shadow billing 模块,统一使用新计费引擎
- 新增 Codex 网关请求适配器(store=false、role 映射、include 补齐)
- endpoint 创建接口支持 body_rules 参数
This commit is contained in:
fawney19
2026-02-05 14:22:11 +08:00
parent e72e5370c4
commit ed2ff5c1d7
25 changed files with 836 additions and 963 deletions

View File

@@ -78,10 +78,18 @@ def is_format_compatible(
if provider_key == client_key:
return True, False, None
# 2. 格式不同 -> 需要检查格式转换开关
# 如果总开关为 False直接拒绝禁用任何跨格式转换
if not effective_conversion_enabled:
return False, False, "格式转换已禁用enable_format_conversion=false"
# 2. 格式不同 -> 需要检查格式转换开关(分层开关)
#
# 设计语义(与模块顶部注释一致):
# - 全局开关 ON -> 强制允许跨格式(通常 caller 会传 skip_endpoint_check=True
# - 全局开关 OFF -> 不再“一刀切”拒绝,而是回退到 provider/endpoint 开关:
# - provider 开关 ON -> 强制允许skip_endpoint_check=True跳过端点检查
# - provider 开关 OFF -> 由端点 format_acceptance_config 决定skip_endpoint_check=False
#
# 说明:
# - effective_conversion_enabled 表示“全局默认允许”,不是“全局总闸/kill switch”
# - 当它为 False 时我们仍然会继续执行后续检查provider/endpoint
# 兼容“按 Provider/Endpoint 精细化开启转换”的场景。
# 3. 如果全局或提供商开关为 ON跳过端点配置检查
if not skip_endpoint_check:

View File

@@ -12,12 +12,16 @@ from __future__ import annotations
import base64
import hashlib
import threading
import time
from collections import OrderedDict
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from src.core.logger import logger
from src.utils.perf import PerfRecorder
from ..config import config
from ..core.exceptions import DecryptionException
@@ -31,8 +35,8 @@ class CryptoService:
使用 FernetAES-128-CBC + HMAC-SHA256确保数据机密性和完整性。
"""
_instance = None
_cipher = None
_instance: CryptoService | None = None
_cipher: Fernet | None = None
_key_source: str = "unknown" # 记录密钥来源,用于调试
# 应用级 salt基于应用名称生成比硬编码更安全
@@ -70,6 +74,21 @@ class CryptoService:
self._cipher = Fernet(key)
logger.info(f"加密服务初始化成功 (key_source={self._key_source})")
# 解密缓存配置(使用实例变量,避免测试场景下缓存跨实例持久化)
self._decrypt_cache: OrderedDict[str, tuple[str, float]] = OrderedDict()
self._decrypt_cache_lock = threading.Lock()
self._decrypt_cache_enabled = bool(getattr(config, "crypto_decrypt_cache_enabled", False))
self._decrypt_cache_size = int(getattr(config, "crypto_decrypt_cache_size", 0) or 0)
self._decrypt_cache_ttl_seconds = float(
getattr(config, "crypto_decrypt_cache_ttl_seconds", 0.0) or 0.0
)
if self._decrypt_cache_enabled and self._decrypt_cache_size > 0:
logger.info(
"解密缓存已启用 (size={}, ttl={}s)",
self._decrypt_cache_size,
self._decrypt_cache_ttl_seconds,
)
def _derive_fernet_key(self, encryption_key: str) -> bytes:
"""
从密码/密钥派生 Fernet 兼容的密钥
@@ -138,11 +157,22 @@ class CryptoService:
if not ciphertext:
return ciphertext
cached = self._get_cached_decrypt(ciphertext)
if cached is not None:
PerfRecorder.record_counter("crypto_decrypt_cache_hits_total", 1)
return cached
PerfRecorder.record_counter("crypto_decrypt_cache_misses_total", 1)
start = PerfRecorder.start()
try:
encrypted = base64.urlsafe_b64decode(ciphertext.encode())
decrypted = self._cipher.decrypt(encrypted)
return decrypted.decode()
plaintext = decrypted.decode()
self._set_cached_decrypt(ciphertext, plaintext)
PerfRecorder.stop(start, "crypto_decrypt")
return plaintext
except Exception as e:
PerfRecorder.stop(start, "crypto_decrypt")
if not silent:
logger.error(f"Decryption failed: {e}")
# 抛出自定义异常,方便在上层通过类型判断是否需要打印堆栈
@@ -163,6 +193,47 @@ class CryptoService:
"""
return hashlib.sha256(api_key.encode()).hexdigest()
def _cache_key(self, ciphertext: str) -> str:
"""生成缓存 key使用密文 hash避免内存中保留完整密文"""
return hashlib.sha256(ciphertext.encode()).hexdigest()[:32]
def _get_cached_decrypt(self, ciphertext: str) -> str | None:
if not self._decrypt_cache_enabled:
return None
if not ciphertext:
return None
if self._decrypt_cache_size <= 0:
return None
cache_key = self._cache_key(ciphertext)
with self._decrypt_cache_lock:
entry = self._decrypt_cache.get(cache_key)
if not entry:
return None
value, expires_at = entry
if expires_at <= time.time():
self._decrypt_cache.pop(cache_key, None)
return None
# 维护 LRU 顺序
self._decrypt_cache.move_to_end(cache_key)
return value
def _set_cached_decrypt(self, ciphertext: str, plaintext: str) -> None:
if not self._decrypt_cache_enabled:
return
if not ciphertext:
return
if self._decrypt_cache_size <= 0:
return
if self._decrypt_cache_ttl_seconds <= 0:
return
cache_key = self._cache_key(ciphertext)
expires_at = time.time() + self._decrypt_cache_ttl_seconds
with self._decrypt_cache_lock:
self._decrypt_cache[cache_key] = (plaintext, expires_at)
self._decrypt_cache.move_to_end(cache_key)
while len(self._decrypt_cache) > self._decrypt_cache_size:
self._decrypt_cache.popitem(last=False)
# 创建全局加密服务实例
crypto_service = CryptoService()

View File

@@ -80,28 +80,3 @@ format_conversion_duration_seconds = Histogram(
["direction", "source_format", "target_format"],
buckets=[0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0],
)
# ==================== Billing migration / shadow billing ====================
billing_requests_total = Counter(
"billing_requests_total",
"Total number of billing calculations",
["engine_mode", "truth_engine"], # low-cardinality labels
)
billing_fallback_total = Counter(
"billing_fallback_total",
"Total number of billing fallbacks to legacy engine",
)
billing_diff_exceeds_threshold_total = Counter(
"billing_diff_exceeds_threshold_total",
"Total number of shadow billing diffs exceeding threshold",
["engine_mode"],
)
billing_invariant_violation_total = Counter(
"billing_invariant_violation_total",
"Total number of billing invariant violations (sum(breakdown)!=total)",
["engine_mode", "truth_engine"],
)