8.5 KiB
8.5 KiB
短链系统设计方案对比与融合总结
一、核心目标
短链系统本质只做两件事:
- 压缩:把长网址映射成短字符串。
- 跳转:用户访问短链时,准确、快速地返回长链。
围绕这两件事,不同场景会衍生出容量、并发、成本、安全、业务规则等差异。
二、方案 A:Cloudflare Sproutlink(当前项目)
2.1 架构组成
| 组件 | 说明 |
|---|---|
| 边缘运行时 | Cloudflare Worker |
| 数据库 | Cloudflare D1(SQLite) |
| 静态前端 | Vite React SPA,通过 ASSETS 绑定由 Worker 直接服务 |
| 短码生成 | D1 单表原子自增 + 线性同余混洗 + Base62 编码 |
| 部署 | Serverless,静态资源不计入 Worker Bundle |
2.2 短码生成原理
- 原子自增序列:
link_seq表保存next_id,每次创建时UPDATE ... SET next_id = next_id + 1 RETURNING next_id。 - 线性同余混洗:把顺序的
seq映射到[0, 62^4)空间,公式为index = (A × seq + B) mod 62^4。A:与62^4互质的乘数(LINK_MULTIPLIERsecret,默认内置大质数)。B:偏移量(LINK_OFFSETsecret)。- 双射特性保证唯一,同时让短码看起来“不连续”。
- Base62 编码:字符集
0-9A-Za-z,固定 4 位,容量62^4 ≈ 1477 万。 - 自定义短码:管理员可指定 1–32 位
a-zA-Z0-9_-,但不可为恰好 4 位纯英数字,避免与自动码池冲突。
2.3 跳转与规则校验
- 使用 HTTP 302 临时重定向,确保每次点击都经过 Worker,便于统计。
- 访问流程:查 D1 → 校验过期/点击上限 → 校验 Geo/设备/浏览器/IP/密码 → 302 跳转并原子递增
clicks。
2.4 业务规则能力
| 规则 | 实现 |
|---|---|
| 有效期 | expires_at 字段 |
| 点击上限 | max_clicks 字段 |
| 密码保护 | PBKDF2 哈希,密码页表单验证 |
| Geo 限制 | request.cf.country |
| 设备/浏览器 | User-Agent + Sec-CH-UA* |
| IP 白/黑名单 | CF-Connecting-IP,支持 IPv4 CIDR |
| 软删除 | deleted 字段 |
2.5 优势特点
- 成本极低:Serverless + D1 + 静态托管,免费/低价套餐即可运行。
- 部署极简:单人可维护,无需维护服务器、Redis、MySQL 集群。
- 短码最短:4 位 Base62,对二维码、短信字数敏感场景友好。
- 访问规则丰富:密码、Geo、设备、浏览器、IP 规则开箱即用,适合私域/营销场景。
- 短码防扫号:线性同余混洗让相邻创建的短码不是连续递增,提高被遍历的难度。
2.6 局限
- 容量有限:4 位码仅 1477 万,公开大规模服务很快耗尽。
- 读并发依赖 D1:无独立缓存层,热点短链会直接把压力打到数据库。
- 无缓存穿透防护:缺少布隆过滤器或空值缓存,面对恶意遍历存在一定风险。
- Cloudflare 生态绑定:迁移或私有化部署成本较高。
三、方案 B:大规模分布式短链设计(经典大厂方案)
3.1 架构组成
| 组件 | 说明 |
|---|---|
| 发号器 | 分布式 ID 发号器(MySQL 号段模式 / Redis 自增) |
| 数据库 | MySQL 持久化存储 code → target_url |
| 缓存 | Redis 读缓存 |
| 防护 | 布隆过滤器 + 空值缓存 + 网关限流 |
| 跳转状态码 | HTTP 302 |
3.2 短码生成原理
- 分布式 ID 发号器:为每个长链接分配一个全局唯一、单调递增的数字 ID。
- Base62 编码:把十进制 ID 转换成 62 进制,固定 6 位。
- 字符集
0-9a-zA-Z。 - 容量
62^6 ≈ 568 亿,足够公开服务长期运行。
- 字符集
- 唯一索引兜底:MySQL 对
code加唯一索引,防止并发冲突。
3.3 读链路(高并发重点)
用户点击 /abc123
↓
网关限流 + WAF
↓
Redis 查 sl:abc123
├─ 命中 → 直接返回 302
└─ 未命中 → 布隆过滤器判断
├─ 一定不存在 → 直接 404
└─ 可能存在 → 查 MySQL
├─ 存在 → 写入 Redis → 302
└─ 不存在 → 写短时空值到 Redis → 404
3.4 写链路
用户提交长链
↓
参数校验、黑名单域名过滤
↓
分布式发号器取号
↓
Base62 编码
↓
MySQL 插入(唯一索引防冲突)
↓
写入 Redis + 布隆过滤器
↓
返回短链
3.5 优势特点
- 容量巨大:6 位码 568 亿,远超 4 位方案。
- 读性能强:Redis 缓存扛住读多写少特性,适合热点营销短信/微博场景。
- 生产级健壮:布隆过滤器防缓存穿透、空值缓存防击穿、网关限流防刷。
- 去 Cloudflare 依赖:技术栈通用,易于私有化或多云部署。
3.6 局限
- 成本高:需要维护 MySQL、Redis、发号器、网关等多套组件。
- 短码更长:6 位比 4 位在短信/二维码场景略逊。
- 业务规则薄弱:标准方案通常只讲“跳转”,缺少密码、Geo、设备等访问控制能力。
四、两套方案核心差异对比
| 维度 | Sproutlink(Cloudflare) | 大规模分布式方案 |
|---|---|---|
| 短码长度 | 4 位 | 6 位 |
| 容量 | 62⁴ ≈ 1477 万 | 62⁶ ≈ 568 亿 |
| 发号方式 | D1 原子自增 + 线性同余混洗 | 分布式 ID 发号器 |
| 存储 | Cloudflare D1 | MySQL + Redis |
| 读缓存 | 无 | Redis |
| 防缓存穿透 | 无 | 布隆过滤器 + 空值缓存 |
| 并发能力 | 中等,依赖 D1 | 高,依赖 Redis |
| 访问规则 | 丰富(Geo/设备/IP/密码) | 通常无或简单 |
| 部署成本 | 极低 | 较高 |
| 适用场景 | 私域、小团队、个人、轻量营销 | 公开互联网、大厂、海量用户 |
五、融合后的推荐设计
如果让我从零设计一个可大可小、可进化的短链系统,我会把两套方案取长补短:
5.1 用大规模方案做底座
- 短码长度:默认 6 位 Base62,容量 568 亿。
- 发号器:采用 MySQL 号段模式(Segment),每个实例本地批量领号,减少 DB 压力。
- 存储:MySQL 持久化 + Redis 读缓存。
- 防护:布隆过滤器 + 空值缓存 + 网关限流。
- 状态码:统一使用 302,保证点击量可统计。
5.2 吸收 Sproutlink 的规则引擎
| 规则 | 实现位置 |
|---|---|
| 有效期 | MySQL expires_at + 定时清理任务 |
| 点击上限 | MySQL 原子递增 clicks / max_clicks |
| 密码保护 | Worker / API 网关,密码页验证后 302 |
| Geo 限制 | CDN 边缘(Cloudflare、阿里云 CDN 等)或网关解析 CF-IPCountry |
| 设备/浏览器 | 解析 User-Agent + Client Hints |
| IP 白/黑名单 | 网关层,支持 IPv4 / IPv6 / CIDR |
| 自定义短码 | 管理员接口,与自动 6 位码池隔离 |
5.3 可选:保留短码混洗能力
在 6 位方案中,可以可选地对发号器拿到的顺序 ID 做一次可逆混洗(如线性同余),让短码看起来不连续,提升防扫号能力。注意混洗函数必须保证双射。
5.4 最终数据流
写(生成短链):
提交长链
→ 校验 + 黑名单过滤
→ 号段发号器取唯一 ID
→ 可选混洗
→ Base62 编码
→ MySQL 写入
→ 写入 Redis + 布隆过滤器
→ 返回 6 位短链
读(访问短链):
用户访问 /abc123
→ 网关限流/WAF
→ Redis 查缓存
├─ 命中 → 规则校验 → 302
└─ 未命中 → 布隆过滤器
├─ 不存在 → 404
└─ 可能存在 → MySQL 查询
├─ 存在 → 写 Redis → 规则校验 → 302
└─ 不存在 → 写空值缓存 → 404
六、选型建议
| 场景 | 推荐方案 |
|---|---|
| 个人博客、小团队、私域运营、预算敏感 | Sproutlink(Cloudflare) |
| 公开互联网、大厂、营销短信、微博 | 大规模分布式方案(6 位 + 发号器 + Redis) |
| 希望初期低成本、后期可平滑扩展 | 融合方案:Worker/D1 起步,用户量增长后迁移到 MySQL + Redis |
七、一句话总结
Sproutlink 是“小而美、功能全、低成本”的私域短链方案;大规模分布式方案是“高容量、高并发、抗攻击”的公开短链标准架构。最优实践是把后者的容量与并发能力,与前者的访问规则引擎结合起来,按需分层演进。