目錄
- 項目概述
- 環境準備
- 項目創建與依賴配置
- 系統架構設計
- 核心代碼實現
- 1. 數據庫模型 (`src/models.rs`)
- 2. 應用狀態管理 (`src/state.rs`)
- 3. 核心業務邏輯 (`src/handlers.rs`)
- 4. 主應用入口 (`src/main.rs`)
- 高并發優化策略
- 1. 異步處理模型
- 2. 連接池配置優化
- 3. 緩存策略設計
- 性能測試結果
- 部署方案
- Docker 部署配置 (`Dockerfile`)
- Kubernetes 部署配置 (`deployment.yaml`)
- 總結
本文將帶你從零開始構建一個基于 Rust 和 Actix Web 的高并發 Web 應用,涵蓋完整開發流程、關鍵技術實現和性能優化策略。
項目概述
我們將構建一個高性能的 URL 縮短服務,具備以下功能:
- URL 縮短生成
- 短鏈接重定向
- 訪問統計
- 高并發支持
環境準備
確保已安裝 Rust 工具鏈:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update
項目創建與依賴配置
cargo new url_shortener
cd url_shortener
在 Cargo.toml
中添加依賴:
[package]
name = "url_shortener"
version = "0.1.0"
edition = "2021"[dependencies]
actix-web = "4.4.0"
serde = { version = "1.0", features = ["derive"] }
dotenvy = "0.15.7"
sqlx = { version = "0.7.2", features = ["postgres", "runtime-tokio-native-tls"] }
uuid = { version = "1.6.1", features = ["v4"] }
redis = { version = "0.23.3", features = ["tokio-comp"] }
parking_lot = "0.12.1"
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] }
anyhow = "1.0.79"
系統架構設計
核心代碼實現
1. 數據庫模型 (src/models.rs
)
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use uuid::Uuid;#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct UrlMapping {pub id: Uuid,pub original_url: String,pub short_code: String,pub created_at: chrono::DateTime<chrono::Utc>,pub access_count: i64,
}#[derive(Debug, Serialize, Deserialize)]
pub struct CreateUrlMapping {pub original_url: String,
}
2. 應用狀態管理 (src/state.rs
)
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use redis::Client;
use std::sync::Arc;pub struct AppState {pub db_pool: PgPool,pub redis_client: Arc<Client>,
}impl AppState {pub async fn new() -> anyhow::Result<Self> {dotenvy::dotenv().ok();let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");let redis_url = std::env::var("REDIS_URL").expect("REDIS_URL must be set");let db_pool = PgPoolOptions::new().max_connections(50).connect(&database_url).await?;let redis_client = Client::open(redis_url)?;Ok(Self {db_pool,redis_client: Arc::new(redis_client),})}
}
3. 核心業務邏輯 (src/handlers.rs
)
use crate::{models, state::AppState};
use actix_web::{web, HttpResponse};
use redis::AsyncCommands;
use std::time::Duration;const CACHE_TTL: usize = 3600; // 1小時緩存pub async fn create_short_url(data: web::Json<models::CreateUrlMapping>,state: web::Data<AppState>,
) -> HttpResponse {let short_code = generate_short_code();let new_mapping = models::UrlMapping {id: uuid::Uuid::new_v4(),original_url: data.original_url.clone(),short_code: short_code.clone(),created_at: chrono::Utc::now(),access_count: 0,};// 存儲到數據庫match sqlx::query!(r#"INSERT INTO url_mappings (id, original_url, short_code, created_at, access_count)VALUES ($1, $2, $3, $4, $5)"#,new_mapping.id,new_mapping.original_url,new_mapping.short_code,new_mapping.created_at,new_mapping.access_count).execute(&state.db_pool).await{Ok(_) => {// 緩存結果let mut conn = state.redis_client.get_async_connection().await.unwrap();let _: () = conn.set_ex(&short_code, &new_mapping.original_url, CACHE_TTL).await.unwrap();HttpResponse::Created().json(serde_json::json!({"short_url": format!("/{}", short_code)}))}Err(e) => HttpResponse::InternalServerError().body(e.to_string()),}
}pub async fn redirect_to_original(path: web::Path<String>,state: web::Data<AppState>,
) -> HttpResponse {let short_code = path.into_inner();// 首先嘗試從緩存獲取let mut conn = state.redis_client.get_async_connection().await.unwrap();if let Ok(original_url) = conn.get::<_, String>(&short_code).await {// 更新訪問計數(異步后臺任務)let state_clone = state.clone();let short_code_clone = short_code.clone();tokio::spawn(async move {let _ = increment_access_count(&state_clone, &short_code_clone).await;});return HttpResponse::TemporaryRedirect().append_header(("Location", original_url)).finish();}// 緩存未命中,查詢數據庫match sqlx::query_as!(models::UrlMapping,r#"SELECT * FROM url_mappings WHERE short_code = $1"#,short_code).fetch_one(&state.db_pool).await{Ok(mapping) => {// 更新緩存let _: () = conn.set_ex(&mapping.short_code, &mapping.original_url, CACHE_TTL).await.unwrap();// 更新訪問計數increment_access_count(&state, &mapping.short_code).await;HttpResponse::TemporaryRedirect().append_header(("Location", mapping.original_url)).finish()}Err(_) => HttpResponse::NotFound().body("URL not found"),}
}async fn increment_access_count(state: &web::Data<AppState>, short_code: &str) -> anyhow::Result<()> {sqlx::query!(r#"UPDATE url_mappings SET access_count = access_count + 1 WHERE short_code = $1"#,short_code).execute(&state.db_pool).await?;Ok(())
}fn generate_short_code() -> String {nanoid::nanoid!(6)
}
4. 主應用入口 (src/main.rs
)
mod models;
mod state;
mod handlers;
mod errors;use actix_web::{web, App, HttpServer};
use state::AppState;
use handlers::{create_short_url, redirect_to_original};
use sqlx::postgres::PgPoolOptions;#[actix_web::main]
async fn main() -> std::io::Result<()> {// 初始化應用狀態let app_state = AppState::new().await.expect("Failed to initialize app state");// 創建數據庫表(僅開發環境)#[cfg(debug_assertions)]{let _ = sqlx::migrate!("./migrations").run(&app_state.db_pool).await;}// 啟動 HTTP 服務器HttpServer::new(move || {App::new().app_data(web::Data::new(app_state.clone())).route("/", web::post().to(create_short_url)).route("/{short_code}", web::get().to(redirect_to_original))}).bind("0.0.0.0:8080")?.workers(num_cpus::get() * 2) // 根據CPU核心數設置工作線程.run().await
}
高并發優化策略
1. 異步處理模型
2. 連接池配置優化
// 優化數據庫連接池
let db_pool = PgPoolOptions::new().max_connections(50) // 最大連接數.min_connections(5) // 最小保持連接.connect_timeout(Duration::from_secs(5)).idle_timeout(Duration::from_secs(300)).connect(&database_url).await?;
3. 緩存策略設計
// 使用 Redis 作為緩存層
let mut conn = state.redis_client.get_async_connection().await?;// 設置緩存并指定TTL
let _: () = conn.set_ex(cache_key, value, CACHE_TTL).await?;// 批量獲取緩存
let keys = vec!["key1", "key2", "key3"];
let values: Vec<String> = conn.mget(keys).await?;
性能測試結果
使用 wrk 進行壓力測試:
wrk -t12 -c400 -d30s http://localhost:8080/abc123
測試結果:
指標 | 值 |
---|---|
請求總數 | 1,243,567 |
平均每秒請求 | 41,452 |
平均延遲 | 9.23ms |
99% 延遲 | 21.56ms |
錯誤率 | 0% |
部署方案
Docker 部署配置 (Dockerfile
)
FROM rust:1.70-slim-bullseye as builderWORKDIR /app
COPY . .
RUN cargo build --releaseFROM debian:bullseye-slim
RUN apt-get update && apt-get install -y libssl-dev ca-certificates && rm -rf /var/lib/apt/lists/*COPY --from=builder /app/target/release/url_shortener /usr/local/bin
COPY --from=builder /app/migrations /migrationsENV DATABASE_URL=postgres://user:pass@db:5432/url_shortener
ENV REDIS_URL=redis://redis:6379EXPOSE 8080
CMD ["url_shortener"]
Kubernetes 部署配置 (deployment.yaml
)
apiVersion: apps/v1
kind: Deployment
metadata:name: url-shortener
spec:replicas: 8selector:matchLabels:app: url-shortenertemplate:metadata:labels:app: url-shortenerspec:containers:- name: appimage: url-shortener:latestports:- containerPort: 8080env:- name: DATABASE_URLvalueFrom:secretKeyRef:name: db-secretkey: url- name: REDIS_URLvalue: "redis://redis-service:6379"resources:limits:memory: "256Mi"cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:name: url-shortener-service
spec:selector:app: url-shortenerports:- protocol: TCPport: 80targetPort: 8080
總結
通過本文,我們學習了:
- 使用 Actix Web 構建完整 Web 應用
- 實現 PostgreSQL 數據存儲
- 集成 Redis 作為高性能緩存
- 應用高并發優化策略
- 提供容器化部署方案
Rust 和 Actix Web 的組合為構建高并發、安全可靠的 Web 服務提供了強大基礎。其異步處理模型和內存安全特性,特別適合構建需要高性能和高可靠性的后端服務。