perf(auth): JWT 权限缓存 RwLock 替换为 DashMap

USER_SCOPE_CACHE 从 LazyLock<RwLock<HashMap>> 改为 LazyLock<DashMap>,
消除读写锁竞争,提升高并发场景下的认证中间件吞吐量。
过期条目淘汰逻辑改用 DashMap::retain,无需手动获取 write lock。
This commit is contained in:
iven
2026-05-17 12:54:34 +08:00
parent c2c7f2d967
commit e8bbc36364
2 changed files with 20 additions and 18 deletions

View File

@@ -26,3 +26,4 @@ cbc.workspace = true
hex.workspace = true hex.workspace = true
base64 = "0.22" base64 = "0.22"
redis.workspace = true redis.workspace = true
dashmap.workspace = true

View File

@@ -2,6 +2,7 @@ use axum::body::Body;
use axum::http::Request; use axum::http::Request;
use axum::middleware::Next; use axum::middleware::Next;
use axum::response::Response; use axum::response::Response;
use dashmap::DashMap;
use erp_core::error::AppError; use erp_core::error::AppError;
use erp_core::request_info::REQUEST_INFO; use erp_core::request_info::REQUEST_INFO;
use erp_core::request_info::RequestInfo; use erp_core::request_info::RequestInfo;
@@ -12,11 +13,11 @@ use crate::service::token_service::TokenService;
type DeptIds = Vec<uuid::Uuid>; type DeptIds = Vec<uuid::Uuid>;
type DataScopes = std::collections::HashMap<String, DataScope>; type DataScopes = std::collections::HashMap<String, DataScope>;
type ScopeCacheEntry = (DeptIds, DataScopes, std::time::Instant); type ScopeCacheEntry = (DeptIds, DataScopes, std::time::Instant);
type ScopeCacheMap = std::collections::HashMap<uuid::Uuid, ScopeCacheEntry>;
/// 用户权限数据缓存user_id -> (department_ids, data_scopes, cached_at) /// 用户权限数据缓存user_id -> (department_ids, data_scopes, cached_at)
static USER_SCOPE_CACHE: std::sync::LazyLock<std::sync::RwLock<ScopeCacheMap>> = /// DashMap 分片并发,读写无锁竞争
std::sync::LazyLock::new(|| std::sync::RwLock::new(std::collections::HashMap::new())); static USER_SCOPE_CACHE: std::sync::LazyLock<DashMap<uuid::Uuid, ScopeCacheEntry>> =
std::sync::LazyLock::new(DashMap::new);
const SCOPE_CACHE_TTL: std::time::Duration = std::time::Duration::from_secs(60); const SCOPE_CACHE_TTL: std::time::Duration = std::time::Duration::from_secs(60);
@@ -76,16 +77,17 @@ pub async fn jwt_auth_middleware_fn(
} }
// 查询用户所属部门 ID 列表 + 权限数据范围(带 60 秒缓存) // 查询用户所属部门 ID 列表 + 权限数据范围(带 60 秒缓存)
let cached = { let cached = USER_SCOPE_CACHE.get(&claims.sub).and_then(|entry| {
let cache = USER_SCOPE_CACHE.read().unwrap(); let (_, _, at) = entry.value();
cache.get(&claims.sub).and_then(|(depts, scopes, at)| { if at.elapsed() < SCOPE_CACHE_TTL {
if at.elapsed() < SCOPE_CACHE_TTL { let (depts, scopes, _) = entry.value();
Some((depts.clone(), scopes.clone())) Some((depts.clone(), scopes.clone()))
} else { } else {
None drop(entry);
} USER_SCOPE_CACHE.remove(&claims.sub);
}) None
}; }
});
let (department_ids, permission_data_scopes) = match cached { let (department_ids, permission_data_scopes) = match cached {
Some(hit) => hit, Some(hit) => hit,
None => fetch_and_cache_scopes(claims.sub, claims.tid, &db).await, None => fetch_and_cache_scopes(claims.sub, claims.tid, &db).await,
@@ -207,15 +209,14 @@ async fn fetch_and_cache_scopes(
Some(conn) => fetch_permission_data_scopes(user_id, tenant_id, conn).await, Some(conn) => fetch_permission_data_scopes(user_id, tenant_id, conn).await,
None => std::collections::HashMap::new(), None => std::collections::HashMap::new(),
}; };
let mut cache = USER_SCOPE_CACHE.write().unwrap(); USER_SCOPE_CACHE.insert(
cache.insert(
user_id, user_id,
(depts.clone(), scopes.clone(), std::time::Instant::now()), (depts.clone(), scopes.clone(), std::time::Instant::now()),
); );
// 惰性淘汰过期条目,防止 HashMap 无限增长 // 惰性淘汰过期条目,防止 DashMap 无限增长
if cache.len() > 500 { if USER_SCOPE_CACHE.len() > 500 {
let now = std::time::Instant::now(); let now = std::time::Instant::now();
cache.retain(|_, (_, _, at)| now.duration_since(*at) < SCOPE_CACHE_TTL); USER_SCOPE_CACHE.retain(|_, (_, _, at)| now.duration_since(*at) < SCOPE_CACHE_TTL);
} }
(depts, scopes) (depts, scopes)
} }