目录
- 项目概述
- 环境准备
- 项目创建与依赖配置
- 系统架构设计
- 核心代码实现
- 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 服务提供了强大基础。其异步处理模型和内存安全特性,特别适合构建需要高性能和高可靠性的后端服务。