跳到主要内容

短链接系统设计

短链接系统要满足哪些需求?​

  • 核心功能: 为长 URL 生成唯一短别名,访问短链接时重定向,并支持约定的到期时间;
  • 用户价值: 节省分享空间、减少输入错误,并为点击统计和设备适配提供入口;
  • 质量目标: 读多写少,优先保障重定向的可用性与低延迟,再考虑规模增长;
  • 扩展范围: 防止接口滥用,记录访问统计,可按业务增加私有链接;
  • 推导标记: 下文保留原文假设,容量修正及幂等、授权等失败处理是为使教学方案闭环所补充的条件;

如何计算短链接的读写与存储基线?​

  • 假设: 每月创建 1 亿条链接,读取与写入之比为 100:1,每月按 30 天,每条记录按 500 B,保存 10 年;
项目计算结果
每月读取1 亿 × 100100 亿次
平均创建1 亿 / 259200038.58 次/s,约 40
平均重定向写入 × 1003858 次/s,约 4000
写入负载带宽40 × 500 B20 KB/s
读取负载带宽4000 × 500 B2 MB/s
十年记录数1 亿 × 12 × 10120 亿
逻辑存储120 亿 × 500 B6 TB
  • 估算边界: 带宽是简化的请求或响应负载估计,不包括 TLS、HTTP 头部、复制、索引和峰值;
  • 缓存修正: 原文的约 35 GB 来自“日请求数 × 20% × 500 B”,只有另外假设这些请求对应不同缓存项才有容量意义;
  • 实际缓存: 应测量活跃唯一短码、键值开销和 TTL,80/20 是可能的访问分布,不是无条件的容量公式;

数据模型如何表达短码归属和过期?​

实体关键字段约束或访问方式
usersid、name、email、createdAtid 唯一
urlshash、userID、originalURL、expirationhash 唯一,按短码点查
私有链接授权hash、userID约束谁可访问指定链接
  • 关系: 一个用户可以创建多个链接,原文模型将短码 hash 作为链接主键及索引;
  • 存储选择: 键值或文档模型可匹配短码点查,关系数据库也可实现,不能仅凭链接间关系较少就排除 SQL;
  • 到期语义: 明确空到期时间是否表示永久,以及删除后是否允许复用短码;复用可能让旧外部链接指向新目标;

创建接口如何定义一次可重试的操作?​

createURL(apiKey, originalURL, expiration?) → shortURL
  • 输入: 验证调用方身份、目标 URL 及可选到期时间,再检查配额;
  • API Key: 用于识别调用应用并计量额度,不自动代表其有权修改所有用户的链接;
  • 幂等补充: 为同一创建请求附带请求 ID,重试时返回已创建结果,避免超时后重复生成;
  • 成功语义: 持久化链接映射后返回 201 与短链接,不能只分配到短码就宣称创建完成;

为什么短码长度不能单独保证唯一性?​

  • Base62: 用大小写字母和数字共 62 个符号表示整数或编码值,长度 N 的空间是 62^N;
  • 编码边界: Base62 编码长 URL 不保证变短,实际应编码唯一编号或独立产生的短键;
长度可用编码数
5916132832,约 9.16 亿
656800235584,约 568 亿
73521614606208,约 3.52 万亿
  • 碰撞风险: 随机选择或截断哈希都可能撞码,必须用唯一约束及冲突处理保证结果;
  • 原文 MD5 方案: 对 URL 求摘要后截断再编码仍可能碰撞,同一个输入重复求同一个摘要也无法消除冲突;
  • 修正路径: 若采用摘要方案,需要额外盐值或其他候选生成方式并检查占用;MD5 也不应承担安全保护用途;

分段计数器如何生成不重复的短码?​

协调服务分配互不重叠区间
实例 A:1 至 1000000
实例 B:1000001 至 2000000
实例内取下一个编号 → Base62 → 唯一短码
  • 唯一性来源: 编号区间不重叠且同一区间内不重复使用,不来自 Base62 本身;
  • 原文方案: 用 ZooKeeper 等协调机制分配区间,实例消耗完后领取新范围;
  • 故障条件: 实例重启不能从已用区间开头重新发号,允许浪费未用编号通常比复用更安全;
  • 可用性: 协调服务需要可靠状态和冗余,已领取区间可以减少每次创建都访问中心的压力;

预生成 KGS 如何安全分配短键?​

  • KGS: Key Generation Service,提前生成唯一短键并存入键库,创建服务只领取现成键;
  • 并发领取: 未用键到已用键的迁移必须原子完成,不能仅依靠两张表就假设并发安全;
  • 内存预取: 实例可以领取一批键放入内存,崩溃时宁可废弃未用键,也不能让两个实例同时使用;
  • 容量修正: 全部六字符键的纯 ASCII 存储为 6 × 62^6 = 340801413504 B,约 340.8 GB,原文约 300/390 GB 的说法不一致;
  • 容量边界: 真实键库还包括占用状态、索引和副本,且没必要一次预生成整个空间;
  • 方案关系: 分段计数器与预生成 KGS 是可以比较或组合的实现选择,不能把二者都当每次请求必经步骤;

创建链路如何协调键库、主库与缓存?​

客户端 → 入口鉴权与限流 → API
API → 领取短键 → 写入 urls → 返回 shortURL
→ 填充或失效缓存
  • 主数据优先: 只有主库映射提交成功才能向用户确认;缓存写失败可异步修复,不应丢失已成功创建的链接;
  • 分配失败: 暂时不能领取键时返回可重试错误,不能复用已被其他请求占用的键;
  • 写库失败: 已领短键可以标记废弃或按严格回收协议处理,避免不确定状态下直接返还;

访问短链接时如何完成一次重定向?​

getURL(apiKey?, shortURL) → 查缓存 → 未命中查 urls
→ 校验存在、到期和访问权限
→ 返回重定向或错误
  • 查找: 缓存保存短码到目标 URL 的映射,未命中时回源并按约定填充,通用策略见 缓存;
  • 不存在: 数据库也没有对应项时返回 404,必要时短暂缓存不存在结果以降低无效流量;
  • 重定向选择: 原文使用 301;永久重定向可能被客户端长期缓存,若链接会到期、改目标或需逐次统计,应评估临时重定向与缓存头;
  • 权限: 未认证与无权访问应区分 401 和 403,公开短链通常不要求访问者提供创建者 API Key;
  • 点击统计: 把国家、平台、浏览次数等事件异步记录,避免每次跳转同步更新同一热点统计行;

到期与删除如何保证链接及时失效?​

deleteURL(apiKey, shortURL) → 校验所有者 → 标记删除 → 失效缓存
  • 访问判断: 无论后台是否已物理清理,读取路径都要拒绝过期或已删除链接;
  • 主动清理: 定时任务分批扫描到期记录并处理缓存,避免一次大删除压垮数据库;
  • 被动清理: 访问到期记录时再移除,节省无人访问数据的前台工作,但不能单独保证存储最终释放;
  • 缓存到期: TTL 不应超过链接有效期,授权变化也不能被旧缓存绕过;

增长和故障时怎样保护重定向路径?​

  • 分区: 可按短码哈希分片,其他列表、范围或复合规则见 分片,需要实际数据分布支持选择;
  • 热点: 一致性哈希减少扩容迁移但不能消除单条爆款链接热点,应由缓存和副本读取分担;
  • 入口与服务: API、KGS 多实例运行,入口只向健康实例分发,键库设置可靠副本与切换策略;
  • 数据库: 读副本可分担读取,但刚创建的链接不能因复制延迟被误报不存在;
  • 缓存: 多实例或副本提高可用性,同时限制失效后的回源并发,避免缓存故障拖垮主库;
  • 验证: 演练领取键后崩溃、创建成功但响应丢失、缓存失效和主库切换,检查唯一性与可访问性;