- gateway/proxy/routes.py:sense 运行时(SenseStore+Grader+晋升表)按配置单例化, 原每请求重建导致:每请求跑全量 DDL、重读工件文件,且 T-X3 60s 决策缓存随 Grader 丢弃、命中率恒为 0(本轮最大收益,激活既有已测组件); T1 审计抽样修复原 NameError×3 被 except 吞掉(load_config/get_review/request_id 均未定义、§9.4 从未入队)——request_id 上提、review 队列经 review_getter 注入 (api.py 反向依赖解除)、sample_rate 走 load_config - gateway/model_pool.py:新增 usable_entries() 未打码内通道;routes 的降级链/ 预算降档/能力位重定向/档位映射四处改走该通道——修复打码 api_key 流入上游 Bearer 头导致带密钥条目重定向必 401 的隐性缺陷(HTTP 管理面仍用打码 list()) - gateway/proxy/ledger.py + billing.py:共享持久连接(原每操作新建,全链路 每请求 3-5 次 connect)+ 日重置 UPDATE 每实例每日一次短路(原每请求全表扫描 抢写锁);check_and_count A/B 0.75→0.31 ms/op(2.4x);新增 usage_stats() SQL 聚合,/admin/stats 从 list_usage(limit=50 万) Python 四遍扫描改为下推聚合 (微基准 ~28x,随流水线性扩大) - gateway/sense/grader.py:决策留痕 insert_decision 移入 asyncio.to_thread (原同步 sqlite 写直接跑在事件循环线程,高并发阻塞网关;与观察写批量队列同等保护) - 全量 474 项两轮复核全绿(基线 474)
230 lines
10 KiB
Python
230 lines
10 KiB
Python
"""计费两阶段(D-P11):预扣-结算-退款,作为 Ledger 的 mixin。
|
||
|
||
拆分为独立模块的工程原因:ledger.py 承载 DDL/CRUD/查询;金额敏感的
|
||
两阶段语义集中在此,便于评审与测试聚焦。
|
||
|
||
并发正确性:全局锁内 check-then-act(单写者模型)+ 余额原子防线
|
||
(语句条件 balance_milli >= ?,受影响行数为 0 即不足)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import threading
|
||
import time
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from gateway.proxy.ledgerutil import _today
|
||
|
||
# 预扣:余额原子防线;日上限判定在锁内 Python 侧(单写者模型下等价安全)
|
||
# 占位顺序:est, spent_total, today, sid, est
|
||
_SQL_HOLD = """UPDATE students SET
|
||
balance_milli = balance_milli - ?,
|
||
spent_today_milli = ?, spent_date = ?
|
||
WHERE id = ? AND balance_milli >= ?"""
|
||
|
||
# 预算四档阈值(T-X1,基点 bp:1% = 100bp;整数比较,杜绝浮点边界漂移)
|
||
BUDGET_OPTIMIZE_BP = 8000 # >= 80% 建议降一档
|
||
BUDGET_CHEAP_BP = 9500 # >= 95% 强制最低档
|
||
|
||
_SQL_HOLD_MARK = """INSERT INTO usage_ledger
|
||
(request_id, ts, key_id, model, bucket, charged_milli, status)
|
||
VALUES (?, ?, ?, ?, ?, ?, 'holding')"""
|
||
|
||
_SQL_INSUFFICIENT = """INSERT OR REPLACE INTO usage_ledger
|
||
(request_id, ts, key_id, model, bucket, charged_milli, status)
|
||
VALUES (?, ?, ?, ?, ?, 0, 'insufficient')"""
|
||
|
||
_SQL_SETTLE_REFUND = """UPDATE students SET
|
||
balance_milli = balance_milli + ?,
|
||
spent_today_milli = MAX(0, spent_today_milli - ?)
|
||
WHERE id = ?"""
|
||
|
||
_SQL_SETTLE_USAGE = """UPDATE usage_ledger SET
|
||
in_miss_tok = ?, in_hit_tok = ?, out_tok = ?, gateway_cached = ?,
|
||
upstream_cost_milli = ?, charged_milli = ?, margin_milli = ? - ?,
|
||
ttfb_ms = ?, total_ms = ?, status = ?
|
||
WHERE request_id = ?"""
|
||
|
||
|
||
class BillingMixin:
|
||
"""try_hold / settle / void(由 Ledger 继承;依赖 _lock/_connect)。"""
|
||
|
||
_lock: threading.Lock
|
||
|
||
def _connect(self): # 由 Ledger 提供
|
||
raise NotImplementedError
|
||
|
||
def try_hold(self, request_id: str, key_id: int, student_id: int,
|
||
model: str, bucket: str, est_milli: int, ts: float) -> bool:
|
||
"""预扣:锁内读现状 -> 日上限判定 -> 余额原子扣减 -> 写 holding 流水。
|
||
|
||
受影响行数为 0 即不足(余额不够)-> False(402);
|
||
request_id 幂等:重复 hold 直接返回 True(不重复扣)。
|
||
"""
|
||
today = _today(ts)
|
||
with self._lock, self._connect() as conn:
|
||
dup = conn.execute(
|
||
"SELECT 1 FROM usage_ledger WHERE request_id = ?",
|
||
(request_id,)).fetchone()
|
||
if dup:
|
||
return True
|
||
stu = conn.execute(
|
||
"SELECT balance_milli, daily_cap_milli, spent_today_milli, spent_date"
|
||
" FROM students WHERE id = ?", (student_id,)).fetchone()
|
||
if stu is None:
|
||
return False
|
||
spent_base = stu["spent_today_milli"] if stu["spent_date"] == today else 0
|
||
if stu["daily_cap_milli"] > 0 and spent_base + est_milli > stu["daily_cap_milli"]:
|
||
conn.execute(_SQL_INSUFFICIENT,
|
||
(request_id, int(ts), key_id, model, bucket))
|
||
return False
|
||
cur = conn.execute(_SQL_HOLD, (
|
||
est_milli, spent_base + est_milli, today, student_id, est_milli))
|
||
if cur.rowcount == 0:
|
||
conn.execute(_SQL_INSUFFICIENT,
|
||
(request_id, int(ts), key_id, model, bucket))
|
||
return False
|
||
conn.execute(_SQL_HOLD_MARK,
|
||
(request_id, int(ts), key_id, model, bucket, est_milli))
|
||
return True
|
||
|
||
def settle(self, request_id: str, actual_milli: int, *,
|
||
in_miss_tok: int = 0, in_hit_tok: int = 0, out_tok: int = 0,
|
||
gateway_cached: int = 0, upstream_cost_milli: int = 0,
|
||
ttfb_ms: Optional[int] = None, total_ms: Optional[int] = None,
|
||
status: str = "ok") -> bool:
|
||
"""结算:按真实值更新流水并回补(预扣额 − 实际额)差额。"""
|
||
with self._lock, self._connect() as conn:
|
||
row = conn.execute(
|
||
"SELECT key_id, charged_milli FROM usage_ledger WHERE request_id = ?",
|
||
(request_id,)).fetchone()
|
||
if row is None:
|
||
return False
|
||
key_id = row["key_id"]
|
||
est = row["charged_milli"]
|
||
student = conn.execute(
|
||
"SELECT student_id FROM proxy_keys WHERE id = ?", (key_id,)).fetchone()
|
||
if student is None:
|
||
return False
|
||
refund = est - actual_milli
|
||
if refund != 0:
|
||
conn.execute(_SQL_SETTLE_REFUND,
|
||
(refund, refund, student["student_id"]))
|
||
conn.execute(_SQL_SETTLE_USAGE, (
|
||
in_miss_tok, in_hit_tok, out_tok, gateway_cached,
|
||
upstream_cost_milli, actual_milli,
|
||
actual_milli, upstream_cost_milli, ttfb_ms, total_ms,
|
||
status, request_id))
|
||
return True
|
||
|
||
def void(self, request_id: str, status: str = "aborted") -> bool:
|
||
"""全额退款(上游失败);流水保留审计。"""
|
||
with self._lock, self._connect() as conn:
|
||
row = conn.execute(
|
||
"SELECT key_id, charged_milli, status FROM usage_ledger"
|
||
" WHERE request_id = ?", (request_id,)).fetchone()
|
||
if row is None or row["status"] not in ("holding", "ok"):
|
||
return False
|
||
est = row["charged_milli"]
|
||
student = conn.execute(
|
||
"SELECT student_id FROM proxy_keys WHERE id = ?",
|
||
(row["key_id"],)).fetchone()
|
||
if student is not None and est > 0:
|
||
conn.execute(_SQL_SETTLE_REFUND,
|
||
(est, est, student["student_id"]))
|
||
conn.execute(
|
||
"UPDATE usage_ledger SET charged_milli = 0, status = ?"
|
||
" WHERE request_id = ?", (status, request_id))
|
||
return True
|
||
|
||
# ---------- 预算四档(T-X1) ----------
|
||
def budget_mode(self, student_id: int, est_milli: int, ts: float) -> str:
|
||
"""预算档位判定:normal / optimize / cheap / block。
|
||
|
||
以「当日已 spent + 本次预估」占日上限的比例判定(基点 bp,全整数运算,
|
||
无浮点边界漂移;黄金用例锁死):
|
||
< 8000bp(80%) -> normal(现行行为)
|
||
>= 8000bp(80%) -> optimize(调用方应降一档模型)
|
||
>= 9500bp(95%) -> cheap(调用方应强制最低档)
|
||
> 10000bp(100%) -> block(try_hold 的硬拒绝语义兜底)
|
||
daily_cap_milli <= 0 视为不限额 -> 恒 normal;学生不存在 -> normal
|
||
(try_hold 才是扣费与拒绝的唯一权威,本方法只做档位建议)。
|
||
"""
|
||
today = _today(ts)
|
||
with self._lock, self._connect() as conn:
|
||
stu = conn.execute(
|
||
"SELECT daily_cap_milli, spent_today_milli, spent_date"
|
||
" FROM students WHERE id = ?", (student_id,)).fetchone()
|
||
if stu is None:
|
||
return "normal"
|
||
cap = int(stu["daily_cap_milli"] or 0)
|
||
if cap <= 0:
|
||
return "normal"
|
||
spent = int(stu["spent_today_milli"] or 0) \
|
||
if stu["spent_date"] == today else 0
|
||
projected = spent + max(0, int(est_milli))
|
||
projected_bp = projected * 10000 // cap
|
||
if projected_bp > 10000:
|
||
return "block"
|
||
if projected_bp >= BUDGET_CHEAP_BP:
|
||
return "cheap"
|
||
if projected_bp >= BUDGET_OPTIMIZE_BP:
|
||
return "optimize"
|
||
return "normal"
|
||
|
||
# ---------- 流水查询 ----------
|
||
def get_usage(self, request_id: str) -> Optional[Dict[str, Any]]:
|
||
with self._lock, self._connect() as conn:
|
||
row = conn.execute(
|
||
"SELECT * FROM usage_ledger WHERE request_id = ?",
|
||
(request_id,)).fetchone()
|
||
return dict(row) if row else None
|
||
|
||
def usage_stats(self) -> Dict[str, Any]:
|
||
"""全局流水聚合(§5.2 看板口径,2026-09 优化:SQL 下推替代全量拉取)。
|
||
|
||
返回 {requests, gateway_cached, in_hit_tok, in_miss_tok, revenue_milli,
|
||
cost_milli, by_bucket};by_bucket 键为 bucket 名。
|
||
"""
|
||
with self._lock, self._connect() as conn:
|
||
row = conn.execute(
|
||
"SELECT COUNT(*) AS requests,"
|
||
" COALESCE(SUM(gateway_cached), 0) AS gateway_cached,"
|
||
" COALESCE(SUM(in_hit_tok), 0) AS in_hit_tok,"
|
||
" COALESCE(SUM(in_miss_tok), 0) AS in_miss_tok,"
|
||
" COALESCE(SUM(charged_milli), 0) AS revenue_milli,"
|
||
" COALESCE(SUM(upstream_cost_milli), 0) AS cost_milli"
|
||
" FROM usage_ledger").fetchone()
|
||
buckets = conn.execute(
|
||
"SELECT bucket, COUNT(*) AS requests,"
|
||
" COALESCE(SUM(charged_milli), 0) AS revenue_milli,"
|
||
" COALESCE(SUM(upstream_cost_milli), 0) AS cost_milli"
|
||
" FROM usage_ledger GROUP BY bucket").fetchall()
|
||
return {
|
||
"requests": row["requests"],
|
||
"gateway_cached": row["gateway_cached"],
|
||
"in_hit_tok": row["in_hit_tok"],
|
||
"in_miss_tok": row["in_miss_tok"],
|
||
"revenue_milli": row["revenue_milli"],
|
||
"cost_milli": row["cost_milli"],
|
||
"by_bucket": {r["bucket"]: {"requests": r["requests"],
|
||
"revenue_milli": r["revenue_milli"],
|
||
"cost_milli": r["cost_milli"]}
|
||
for r in buckets},
|
||
}
|
||
|
||
def list_usage(self, student_id: Optional[int] = None,
|
||
limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]:
|
||
"""流水分页(可按学生过滤,经其名下 key);按学生过滤走联表常量语句。"""
|
||
if student_id is None:
|
||
with self._lock, self._connect() as conn:
|
||
rows = conn.execute(
|
||
"SELECT * FROM usage_ledger ORDER BY ts DESC LIMIT ? OFFSET ?",
|
||
(limit, offset)).fetchall()
|
||
return [dict(r) for r in rows]
|
||
with self._lock, self._connect() as conn:
|
||
rows = conn.execute(
|
||
"SELECT u.* FROM usage_ledger u JOIN proxy_keys k ON k.id = u.key_id"
|
||
" WHERE k.student_id = ? ORDER BY u.ts DESC LIMIT ? OFFSET ?",
|
||
(student_id, limit, offset)).fetchall()
|
||
return [dict(r) for r in rows]
|