mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
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:
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
使用 Fernet(AES-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()
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user