Merge pull request #94 from openqj/feat-regperf

perf: 注册开通复用进程内管理员会话
This commit is contained in:
openqj authored and GitHub committed 2026-10-27 22:46:50 +08:00
commit 494c3847d4
2 files changed
+154 -3

No files matched your search

+65 -3
View File
@@ -16,6 +16,7 @@
import json import json
import os import os
import secrets import secrets
import threading
import time import time
import urllib.error import urllib.error
import urllib.request import urllib.request
@@ -112,8 +113,63 @@ def get_user_gateway_key(conn, user_id):
return None return None
# 进程内共享管理员会话:(origin, email) -> (token, refresh, expires_at)。
# 只复用 token 元组,不共用实例(实例不跨线程);注册与首任务开通
# 每次新建实例都要登录一次(实测约 111ms,占开通链路约 1/3),
# 共享后同一进程内第二次起免登录。401/刷新失败会回落到重登录,
# 密码轮换后下一次登录即用新密码刷新缓存,不会卡死。
_SHARED_ADMIN = {}
_SHARED_ADMIN_LOCK = threading.RLock()
def _store_shared_session(admin):
key = getattr(admin, "_shared_key", None)
if key is None or not admin._token:
return
with _SHARED_ADMIN_LOCK:
_SHARED_ADMIN[key] = (admin._token, admin._refresh,
admin._expires_at)
def _evict_shared_session(admin):
key = getattr(admin, "_shared_key", None)
if key is None:
return
with _SHARED_ADMIN_LOCK:
_SHARED_ADMIN.pop(key, None)
def shared_admin_session(request=None):
"""带进程内 token 复用的管理员会话(生产开通路径默认用它)。
缓存命中且未过期时直接播种 token,免一次管理员登录;
未命中/已过期时在锁内登录一次再返回:突发冷缓存下
N 个并发开通也只登录一次(其余线程等锁后直接播种),
不会各登各的把网关刷出 N 次 bcrypt。调用方显式传
admin 的单测不受影响(不走这条工厂)。
"""
admin = Sub2apiAdmin.from_env(request=request)
key = (admin.origin, admin.email)
admin._shared_key = key
with _SHARED_ADMIN_LOCK:
cached = _SHARED_ADMIN.get(key)
if cached and cached[2] > time.time():
admin._token, admin._refresh, admin._expires_at = cached
return admin
# 缓存未命中:在锁内登录(_login 成功后经
# _store_shared_session 写回同一缓存,RLock 可重入)。
admin._login()
return admin
class Sub2apiAdmin: class Sub2apiAdmin:
"""管理员会话:登录、建/删用户、查组。token 自动刷新一次。""" """管理员会话:登录、建/删用户、查组。token 自动刷新一次。
每次实例只在进程内复用 token 元组(见 shared_admin_session):
实例本身不跨线程共用,只复用 (token, refresh, expires_at),
401 时走已有刷新->重登录链路自愈,密码轮换后下一次登录
即用新密码并刷新缓存,不会卡在旧 token 上。
"""
def __init__(self, origin, email, password, request=None): def __init__(self, origin, email, password, request=None):
if not origin: if not origin:
@@ -127,6 +183,7 @@ class Sub2apiAdmin:
self._token = None self._token = None
self._refresh = None self._refresh = None
self._expires_at = 0 self._expires_at = 0
self._shared_key = None
@classmethod @classmethod
def from_env(cls, request=None): def from_env(cls, request=None):
@@ -141,6 +198,7 @@ class Sub2apiAdmin:
{"email": self.email, "password": self.password}) {"email": self.email, "password": self.password})
payload = (data or {}).get("data") or {} payload = (data or {}).get("data") or {}
if st != 200 or not payload.get("access_token"): if st != 200 or not payload.get("access_token"):
_evict_shared_session(self)
raise Sub2apiError( raise Sub2apiError(
"sub2api 管理员登录失败: %s" "sub2api 管理员登录失败: %s"
% ((data or {}).get("message") or st), status=st) % ((data or {}).get("message") or st), status=st)
@@ -151,6 +209,7 @@ class Sub2apiAdmin:
except (TypeError, ValueError): except (TypeError, ValueError):
ttl = 3600 ttl = 3600
self._expires_at = time.time() + max(60, ttl - 60) self._expires_at = time.time() + max(60, ttl - 60)
_store_shared_session(self)
def _refresh_token(self): def _refresh_token(self):
if not self._refresh: if not self._refresh:
@@ -169,6 +228,7 @@ class Sub2apiAdmin:
except (TypeError, ValueError): except (TypeError, ValueError):
ttl = 3600 ttl = 3600
self._expires_at = time.time() + max(60, ttl - 60) self._expires_at = time.time() + max(60, ttl - 60)
_store_shared_session(self)
return return
self._login() self._login()
@@ -310,7 +370,9 @@ def ensure_user_gateway_key(conn, user_id, admin=None, request=None):
入库失败时尽力清理刚建的 key 与账号(避免孤儿 key),再抛原错; 入库失败时尽力清理刚建的 key 与账号(避免孤儿 key),再抛原错;
授余额失败同样走这条清理(余额为 0 的 key 在网关侧直接 403, 授余额失败同样走这条清理(余额为 0 的 key 在网关侧直接 403,
留着没有意义)。同一用户并发开通时建号撞 email 已存在, 留着没有意义)。同一用户并发开通时建号撞 email 已存在,
则轮询复用胜出者入库的行。调用方负责 conn 的提交语义:本函数在入库后 则轮询复用胜出者入库的行。管理员工单默认走进程内共享会话
(同一进程内第二次起免管理员登录,见 shared_admin_session)。
调用方负责 conn 的提交语义:本函数在入库后
commit(开通是独立事务,不跟调用方的业务事务绑在一起)。 commit(开通是独立事务,不跟调用方的业务事务绑在一起)。
""" """
ensure_gateway_keys_table(conn) ensure_gateway_keys_table(conn)
@@ -320,7 +382,7 @@ def ensure_user_gateway_key(conn, user_id, admin=None, request=None):
origin = gateway_origin() origin = gateway_origin()
if not origin: if not origin:
raise Sub2apiError("SUB2API_BASE_URL 未配置,无法开通网关账号") raise Sub2apiError("SUB2API_BASE_URL 未配置,无法开通网关账号")
admin = admin or Sub2apiAdmin.from_env(request=request) admin = admin or shared_admin_session(request=request)
req = request or _default_request req = request or _default_request
email = "codex-%s@codex.local" % user_id email = "codex-%s@codex.local" % user_id
username = "codex-" + str(user_id)[:12] username = "codex-" + str(user_id)[:12]
+89
View File
@@ -7,6 +7,7 @@
import os import os
import sqlite3 import sqlite3
import sys import sys
import threading
import unittest import unittest
from pathlib import Path from pathlib import Path
from unittest import mock from unittest import mock
@@ -31,6 +32,7 @@ class FakeApi:
def __init__(self): def __init__(self):
self.calls = [] self.calls = []
self._lock = threading.Lock()
self.admin_token = "adm-tok" self.admin_token = "adm-tok"
self.user_token = "usr-tok" self.user_token = "usr-tok"
self.groups = [{"id": 2, "name": "codex-system"}] self.groups = [{"id": 2, "name": "codex-system"}]
@@ -43,6 +45,10 @@ class FakeApi:
self.create_user_hook = None self.create_user_hook = None
def __call__(self, method, url, body=None, token=None): def __call__(self, method, url, body=None, token=None):
with self._lock:
return self._handle(method, url, body, token)
def _handle(self, method, url, body=None, token=None):
path = url.split("28084", 1)[-1] if "28084" in url else url path = url.split("28084", 1)[-1] if "28084" in url else url
self.calls.append((method, path, body, token)) self.calls.append((method, path, body, token))
if path == "/api/v1/auth/login" and body.get("email") == "adm@x": if path == "/api/v1/auth/login" and body.get("email") == "adm@x":
@@ -216,6 +222,89 @@ class TestEnsureGatewayKey(unittest.TestCase):
" WHERE user_id = ?", ("user-race2",)).fetchone() " WHERE user_id = ?", ("user-race2",)).fetchone()
self.assertEqual(row[0], 0) self.assertEqual(row[0], 0)
def _admin_env(self):
old = {k: os.environ.get(k) for k in
("SUB2API_ADMIN_EMAIL", "SUB2API_ADMIN_PASSWORD")}
os.environ["SUB2API_ADMIN_EMAIL"] = "adm@x"
os.environ["SUB2API_ADMIN_PASSWORD"] = "pw"
sub2api._SHARED_ADMIN.clear()
return old
def _restore_admin_env(self, old):
for k, v in old.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
sub2api._SHARED_ADMIN.clear()
def _admin_login_count(self):
return len([
c for c in self.api.calls
if c[0] == "POST" and c[1] == "/api/v1/auth/login"
and (c[2] or {}).get("email") == "adm@x"])
def test_shared_admin_session_reuses_login(self):
old = self._admin_env()
try:
conn1 = _conn()
key1 = ensure_user_gateway_key(conn1, "user-s1",
request=self.api)
self.assertTrue(key1.startswith("sk-probe-"))
conn2 = _conn()
key2 = ensure_user_gateway_key(conn2, "user-s2",
request=self.api)
self.assertTrue(key2.startswith("sk-probe-"))
# 同一进程内第二次开通不再管理员登录(只剩首次那一次)。
self.assertEqual(self._admin_login_count(), 1)
finally:
self._restore_admin_env(old)
def test_shared_admin_session_expired_refetches(self):
old = self._admin_env()
sub2api._SHARED_ADMIN[("http://x:28084", "adm@x")] = (
"old-tok", "old-ref", 0)
try:
conn = _conn()
key = ensure_user_gateway_key(conn, "user-s3",
request=self.api)
self.assertTrue(key.startswith("sk-probe-"))
self.assertEqual(self._admin_login_count(), 1)
finally:
self._restore_admin_env(old)
def test_shared_admin_session_concurrent_burst_logs_in_once(self):
old = self._admin_env()
barrier = threading.Barrier(10)
results = []
errors = []
def run(i):
try:
barrier.wait(timeout=10)
conn = _conn()
key = ensure_user_gateway_key(
conn, "user-c%d" % i, request=self.api)
results.append(key)
except Exception as exc: # noqa: BLE001 - 汇总后断言
errors.append(exc)
try:
threads = [threading.Thread(target=run, args=(i,))
for i in range(10)]
for t in threads:
t.start()
for t in threads:
t.join(timeout=30)
self.assertEqual(errors, [])
self.assertEqual(len(results), 10)
self.assertTrue(all(k.startswith("sk-probe-")
for k in results))
# 突发冷缓存:10 路并发也只登录一次。
self.assertEqual(self._admin_login_count(), 1)
finally:
self._restore_admin_env(old)
class TestProviderCommand(unittest.TestCase): class TestProviderCommand(unittest.TestCase):
def test_server_command_sub2api(self): def test_server_command_sub2api(self):