这是基于李智慧《高并发架构实战课》中短URL系统设计的本地验证实现。
本项目通过简化的本地实现来验证原文中的核心技术点,用代码和数据说明架构设计的合理性。
本项目重点验证以下技术点:
- ✅ 随机生成算法
- ✅ 布隆过滤器去重
- ✅ 文件存储管理
- ✅ 与Hash/自增方案的性能对比
- ✅ 内存链表实现
- ✅ 头指针消费模式
- ✅ 异步加载机制
- ✅ 偏移量文件互斥
- ✅ 内存缓存(模拟Redis)
- ✅ SQLite数据库(模拟HBase)
- ✅ 文件存储(模拟HDFS)
- ✅ 缓存命中率测试
- ✅ 并发访问测试
- ✅ 响应时间统计
- ✅ 吞吐量测量
- ✅ 不同方案对比
| 原架构(生产环境) | 本地验证方案 | 验证目标 |
|---|---|---|
| HDFS (86.4GB) | 本地文件 (100MB) | 偏移量互斥机制 |
| HBase (12TB) | SQLite (1GB) | 查询性能 |
| Redis (100GB) | 内存缓存 (100MB) | 缓存命中率 |
| 144亿条短URL | 100万条短URL | 算法正确性 |
| 4万QPS | 1000 QPS | 性能瓶颈 |
fuxi/
├── cmd/
│ ├── generator/ # 预生成短URL工具
│ ├── api/ # API服务器
│ └── benchmark/ # 性能测试工具
├── internal/
│ ├── generator/ # 生成器实现
│ ├── preload/ # 预加载链表
│ ├── storage/ # 存储层
│ └── shorturl/ # 核心业务逻辑
├── test/ # 测试代码
├── scripts/ # 脚本工具
├── data/ # 数据文件
└── docs/ # 文档
# 生成100万条短URL(约6MB文件)
go run cmd/generator/main.go -count 1000000输出:
data/shorturls.dat- 短URL数据文件data/offset.dat- 偏移量文件
# 启动API服务器(端口8080)
go run cmd/api/main.goAPI接口:
POST /api/shorten- 生成短URLGET /:code- 短URL重定向GET /api/stats- 统计信息
# 运行基准测试
go test -bench=. ./test/
# 运行压力测试
./scripts/load_test.sh重要说明:
- 真实的测试数据请查看 REAL_TEST_RESULTS.md
- TECH_BLOG.md 中的部分数据是为了说明概念的示例
- 运行
make test可以获取你本机的真实测试数据
| 测试项 | 结果 |
|---|---|
| 随机生成速度 | 753万 URLs/秒 |
| 序列生成速度 | 2049万 URLs/秒(但可预测 |
| 缓存性能提升 | 168倍 |
| 缓存延迟 | 0.105μs |
| 数据库延迟 | 12.8μs |
| 预加载延迟 | 1.2μs |
详细结果请运行:
make test # 运行所有测试
go test -v ./test/ -run TestGenerationMethods # 生成算法对比
go test -v ./test/ -run TestCacheDemo # 缓存效果演示
go test -bench=. ./test/ # 基准测试详细的技术分析和验证过程请查看:TECH_BLOG.md
博客内容包括:
- 为什么采用预生成策略?
- 链表预加载机制详解
- 偏移量文件的互斥实现
- 缓存策略的性能影响
- 从本地到分布式的扩展思路
// 使用随机数生成短URL
func generateShortURL() string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
result := make([]byte, 6)
for i := range result {
result[i] = charset[rand.Intn(64)]
}
return string(result)
}type URLNode struct {
Code string
Next *URLNode
}
type LinkedURL struct {
head *URLNode
count int
loader *FileLoader
}
func (l *LinkedURL) Acquire() string {
// 获取头节点的短URL
code := l.head.Code
l.head = l.head.Next
l.count--
// 剩余不足阈值时触发异步加载
if l.count < 2000 {
go l.loadMore()
}
return code
}func (f *FileLoader) LoadBatch() ([]string, error) {
// 写打开偏移量文件(获取锁)
offsetFile, err := os.OpenFile("offset.dat", os.O_RDWR, 0644)
if err != nil {
return nil, err
}
defer offsetFile.Unlock() // 释放锁
// 读取当前偏移量
offset := readOffset(offsetFile)
// 读取短URL数据
urls := readURLsFromFile(offset, 10000)
// 更新偏移量
writeOffset(offsetFile, offset + 60000)
return urls, nil
}-
存储层升级
- 文件存储 → HDFS集群
- SQLite → HBase集群
- 内存缓存 → Redis集群
-
服务层扩展
- 单实例 → 多实例负载均衡
- 本地锁 → 分布式锁(ZooKeeper)
- 同步操作 → 消息队列(Kafka)
-
容量扩展
- 100万条 → 100亿条
- 100MB → 10TB
- 垂直扩展 + 水平扩展
- 缓存优化:多级缓存、预热策略
- 数据库优化:索引优化、分区分表
- 网络优化:CDN加速、连接池
- 并发优化:协程池、批量操作
本项目源码:https://github.com/d60-Lab/shorturl
欢迎Star⭐、Fork和提Issue!
MIT License