跳到主要内容

设计短链接服务

问题

如何用 Rust 设计一个短链接生成和重定向服务?

答案

架构设计

核心实现

use axum::{Router, routing::{get, post}, extract::{State, Path}, Json, response::Redirect};
use std::collections::HashMap;
use std::sync::{Arc, atomic::{AtomicU64, Ordering}};
use tokio::sync::RwLock;

struct ShortUrlService {
counter: AtomicU64,
url_map: RwLock<HashMap<String, String>>, // short_code → original_url
}

const BASE62: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

impl ShortUrlService {
fn new() -> Self {
Self {
counter: AtomicU64::new(100_000), // 从较大的数开始,保证短码长度
url_map: RwLock::new(HashMap::new()),
}
}

/// 自增 ID → Base62 编码
fn encode_base62(mut num: u64) -> String {
if num == 0 { return "0".to_string(); }
let mut result = Vec::new();
while num > 0 {
result.push(BASE62[(num % 62) as usize]);
num /= 62;
}
result.reverse();
String::from_utf8(result).unwrap()
}

async fn shorten(&self, url: &str) -> String {
let id = self.counter.fetch_add(1, Ordering::Relaxed);
let short_code = Self::encode_base62(id);
self.url_map.write().await.insert(short_code.clone(), url.to_string());
short_code
}

async fn resolve(&self, code: &str) -> Option<String> {
self.url_map.read().await.get(code).cloned()
}
}

// API 路由
async fn create_short_url(
State(svc): State<Arc<ShortUrlService>>,
Json(req): Json<CreateRequest>,
) -> Json<CreateResponse> {
let code = svc.shorten(&req.url).await;
Json(CreateResponse {
short_url: format!("https://short.url/{}", code),
})
}

async fn redirect(
State(svc): State<Arc<ShortUrlService>>,
Path(code): Path<String>,
) -> Result<Redirect, axum::http::StatusCode> {
match svc.resolve(&code).await {
Some(url) => Ok(Redirect::temporary(&url)),
None => Err(axum::http::StatusCode::NOT_FOUND),
}
}

短码生成策略对比

策略冲突率长度可控可预测
自增 ID + Base62无冲突是(可猜测)
MD5/SHA 截取可能冲突
随机生成可能冲突
雪花算法 + Base62无冲突较长

常见面试问题

Q1: 如何避免短码被猜测?

答案

自增 ID 的短码是连续的,可以被遍历。解决方案:

  1. ID 混淆:对自增 ID 做可逆的位运算混淆
  2. 随机 ID:用 uuid 生成随机 ID 再 Base62 编码
  3. 加盐哈希hash(url + salt) 取前 N 位

生产环境通常用方案 1(ID 混淆),兼顾无冲突和不可预测。

相关链接