perf: 前端 API 并行化 + 后端 Redis 连接缓存 — 响应时间从 2.26s 降至 2ms

后端:
- rate_limit 中间件新增 RedisAvailability 缓存
- Redis 不可用时跳过限流,30 秒冷却后再重试
- 避免 get_multiplexed_async_connection 每次请求阻塞 2 秒

前端:
- plugin store schema 加载改为 Promise.allSettled 并行(原为 for...of 顺序)
- 先基于 entities 渲染回退菜单,schema 加载完成后更新
- 移除 Home useEffect 中 unreadCount 依赖,消除双重 fetch
- MainLayout 使用选择性 store selector 减少重渲染
This commit is contained in:
iven
2026-04-17 01:12:17 +08:00
parent f4dd228a67
commit b08e8b5ab5
4 changed files with 91 additions and 11 deletions

View File

@@ -1,3 +1,6 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Instant;
use axum::body::Body;
use axum::extract::State;
use axum::http::{Request, StatusCode};
@@ -5,6 +8,7 @@ use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use redis::AsyncCommands;
use serde::Serialize;
use tokio::sync::Mutex;
use crate::state::AppState;
@@ -26,6 +30,53 @@ pub struct RateLimitConfig {
pub key_prefix: String,
}
/// Redis 可用性状态缓存,避免重复连接失败时阻塞。
struct RedisAvailability {
available: AtomicBool,
last_check: Mutex<Instant>,
}
impl RedisAvailability {
fn new() -> Self {
Self {
available: AtomicBool::new(true),
last_check: Mutex::new(Instant::now() - std::time::Duration::from_secs(60)),
}
}
/// 检查是否应该尝试连接 Redis。
/// 如果上次连接失败且冷却期未过,返回 false。
async fn should_try(&self) -> bool {
if self.available.load(Ordering::Relaxed) {
return true;
}
let mut last = self.last_check.lock().await;
// 连接失败后冷却 30 秒再重试
if last.elapsed() > std::time::Duration::from_secs(30) {
*last = Instant::now();
true
} else {
false
}
}
fn mark_ok(&self) {
self.available.store(true, Ordering::Relaxed);
}
async fn mark_failed(&self) {
self.available.store(false, Ordering::Relaxed);
*self.last_check.lock().await = Instant::now();
}
}
/// 全局 Redis 可用性缓存
static REDIS_AVAIL: std::sync::OnceLock<RedisAvailability> = std::sync::OnceLock::new();
fn redis_avail() -> &'static RedisAvailability {
REDIS_AVAIL.get_or_init(RedisAvailability::new)
}
/// 基于 Redis 的 IP 限流中间件。
///
/// 使用 INCR + EXPIRE 实现固定窗口计数器。
@@ -65,12 +116,23 @@ async fn apply_rate_limit(
req: Request<Body>,
next: Next,
) -> Response {
let avail = redis_avail();
// 快速跳过Redis 不可达时直接放行
if !avail.should_try().await {
return next.run(req).await;
}
let key = format!("rate_limit:{}:{}", prefix, identifier);
let mut conn = match redis_client.get_multiplexed_async_connection().await {
Ok(c) => c,
Ok(c) => {
avail.mark_ok();
c
}
Err(e) => {
tracing::warn!(error = %e, "Redis 连接失败,跳过限流");
avail.mark_failed().await;
return next.run(req).await;
}
};