Files
sproutlink/shortlink-design-summary.md

8.5 KiB
Raw Permalink Blame History

短链系统设计方案对比与融合总结

一、核心目标

短链系统本质只做两件事:

  1. 压缩:把长网址映射成短字符串。
  2. 跳转:用户访问短链时,准确、快速地返回长链。

围绕这两件事,不同场景会衍生出容量、并发、成本、安全、业务规则等差异。


二、方案 ACloudflare Sproutlink当前项目

2.1 架构组成

组件 说明
边缘运行时 Cloudflare Worker
数据库 Cloudflare D1SQLite
静态前端 Vite React SPA通过 ASSETS 绑定由 Worker 直接服务
短码生成 D1 单表原子自增 + 线性同余混洗 + Base62 编码
部署 Serverless静态资源不计入 Worker Bundle

2.2 短码生成原理

  1. 原子自增序列link_seq 表保存 next_id,每次创建时 UPDATE ... SET next_id = next_id + 1 RETURNING next_id
  2. 线性同余混洗:把顺序的 seq 映射到 [0, 62^4) 空间,公式为 index = (A × seq + B) mod 62^4
    • A:与 62^4 互质的乘数(LINK_MULTIPLIER secret默认内置大质数
    • B:偏移量(LINK_OFFSET secret
    • 双射特性保证唯一,同时让短码看起来“不连续”。
  3. Base62 编码:字符集 0-9A-Za-z,固定 4 位,容量 62^4 ≈ 1477 万
  4. 自定义短码:管理员可指定 132 位 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 短码生成原理

  1. 分布式 ID 发号器:为每个长链接分配一个全局唯一、单调递增的数字 ID。
  2. Base62 编码:把十进制 ID 转换成 62 进制,固定 6 位
    • 字符集 0-9a-zA-Z
    • 容量 62^6 ≈ 568 亿,足够公开服务长期运行。
  3. 唯一索引兜底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、设备等访问控制能力。

四、两套方案核心差异对比

维度 SproutlinkCloudflare 大规模分布式方案
短码长度 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,保证点击量可统计。
规则 实现位置
有效期 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

六、选型建议

场景 推荐方案
个人博客、小团队、私域运营、预算敏感 SproutlinkCloudflare
公开互联网、大厂、营销短信、微博 大规模分布式方案6 位 + 发号器 + Redis
希望初期低成本、后期可平滑扩展 融合方案Worker/D1 起步,用户量增长后迁移到 MySQL + Redis

七、一句话总结

Sproutlink 是“小而美、功能全、低成本”的私域短链方案;大规模分布式方案是“高容量、高并发、抗攻击”的公开短链标准架构。最优实践是把后者的容量与并发能力,与前者的访问规则引擎结合起来,按需分层演进。