高读写比场景下Axum应用状态存储的替代方案咨询
Axum高读写比场景下的状态存储优化方案
针对你这种100万:1的极高读写比场景,Arc<Mutex<MyState>>确实不是最优选择——Mutex的排他锁会让所有读操作排队,严重拖慢读取性能。下面是几种更适合的方案,包括你提到的“常量式存储+定时替换”的实现方式:
方案1:读写锁(Arc<RwLock<MyState>>)
这是最容易上手的替代方案,RwLock允许多个读请求同时获取锁,只有写操作会独占锁,完美匹配读多写少的场景。
代码示例
use axum::{extract::State, routing::get, Router}; use std::sync::{Arc, RwLock}; use tokio::time::{sleep, Duration}; use chrono::Local; #[derive(Clone)] struct MyState { data: String, } impl MyState { fn new() -> Self { Self { data: "初始数据".to_string() } } // 模拟后台计算新状态的逻辑 fn recalculate(&self) -> Self { Self { data: format!("更新于: {}", Local::now().format("%Y-%m-%d %H:%M:%S")) } } } #[tokio::main] async fn main() { let state = Arc::new(RwLock::new(MyState::new())); // 后台定时更新任务(每小时执行一次) let state_clone = state.clone(); tokio::spawn(async move { loop { sleep(Duration::from_secs(3600)).await; let new_state = state_clone.read().unwrap().recalculate(); *state_clone.write().unwrap() = new_state; } }); // API端点:读取状态 let app = Router::new() .route("/get-data", get(get_data)) .with_state(state); axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_svc()) .await .unwrap(); } async fn get_data(State(state): State<Arc<RwLock<MyState>>>) -> String { state.read().unwrap().data.clone() }
优缺点
- 优点:实现简单,完全内存安全,无需unsafe代码,读操作并发度高。
- 缺点:写操作会阻塞所有读写请求(不过你的写频率极低,几乎可以忽略)。
方案2:原子指针替换(Arc<AtomicPtr<MyState>>)
这个方案完全符合你“常量式存储+定时替换”的需求:读取时直接访问内存中的状态实例(无锁),写入时原子替换整个实例指针,性能拉满。
代码示例
use axum::{extract::State, routing::get, Router}; use std::sync::{Arc, atomic::{AtomicPtr, Ordering}}; use tokio::time::{sleep, Duration}; use std::ptr; use chrono::Local; #[derive(Clone)] struct MyState { data: String, } impl MyState { fn new() -> Self { Self { data: "初始数据".to_string() } } fn recalculate(&self) -> Self { Self { data: format!("更新于: {}", Local::now().format("%Y-%m-%d %H:%M:%S")) } } } // 实现Drop确保旧状态内存被正确释放 impl Drop for MyState { fn drop(&mut self) { println!("释放旧状态: {}", self.data); } } #[tokio::main] async fn main() { // 初始化状态,将Box转为裸指针 let initial_state = Box::new(MyState::new()); let state = Arc::new(AtomicPtr::new(Box::into_raw(initial_state))); // 后台更新任务 let state_clone = state.clone(); tokio::spawn(async move { loop { sleep(Duration::from_secs(3600)).await; // 读取当前状态并计算新状态 let current_state = unsafe { &*state_clone.load(Ordering::Acquire) }; let new_state = Box::new(current_state.recalculate()); // 原子替换指针,并释放旧状态内存 let old_ptr = state_clone.swap(Box::into_raw(new_state), Ordering::Release); unsafe { Box::from_raw(old_ptr); } } }); let app = Router::new() .route("/get-data", get(get_data)) .with_state(state); axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_svc()) .await .unwrap(); } async fn get_data(State(state): State<Arc<AtomicPtr<MyState>>>) -> String { let ptr = state.load(Ordering::Acquire); let current_state = unsafe { &*ptr }; current_state.data.clone() }
优缺点
- 优点:读操作完全无锁,性能最优,完全符合“常量式存储”的需求。
- 缺点:需要使用unsafe代码处理裸指针,必须手动管理内存(避免内存泄漏),对开发者的内存安全意识要求较高。
方案3:异步监听通道(tokio::sync::watch)
Tokio提供的watch通道天生适合“单生产者多消费者”的场景,后台任务作为生产者更新状态,API端点作为消费者读取最新状态,全程异步无锁。
代码示例
use axum::{extract::State, routing::get, Router}; use tokio::sync::watch; use tokio::time::{sleep, Duration}; use chrono::Local; #[derive(Clone)] struct MyState { data: String, } impl MyState { fn new() -> Self { Self { data: "初始数据".to_string() } } fn recalculate(&self) -> Self { Self { data: format!("更新于: {}", Local::now().format("%Y-%m-%d %H:%M:%S")) } } } #[tokio::main] async fn main() { // 创建watch通道,初始状态为MyState::new() let (tx, rx) = watch::channel(MyState::new()); // 后台更新任务 tokio::spawn(async move { loop { sleep(Duration::from_secs(3600)).await; let current_state = tx.borrow().clone(); let new_state = current_state.recalculate(); // 发送新状态,所有监听者都会收到更新 tx.send(new_state).unwrap(); } }); // 将Receiver包装成Arc,供所有请求共享 let state = Arc::new(rx); let app = Router::new() .route("/get-data", get(get_data)) .with_state(state); axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_svc()) .await .unwrap(); } async fn get_data(State(state): State<Arc<watch::Receiver<MyState>>>) -> String { // 获取最新状态快照 state.borrow().data.clone() }
优缺点
- 优点:完全异步,内存安全,API简洁友好,无需手动处理锁或指针,适合Axum的异步生态。
- 缺点:读操作需要调用
borrow()获取快照,性能略低于AtomicPtr,但远优于Mutex。
内容的提问来源于stack exchange,提问作者Blankman
相关产品推荐
相关产品推荐

