You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

高读写比场景下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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:44:51