题目重点:从shorten/resolve合同出发,区分哈希碰撞、数据库唯一约束和不可猜短码,推导存储与读扩展。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练接口shorten(longUrl,requestId)返回短码,resolve(code)返回长URL;仅接受HTTP(S),不主动抓取目标内容。
- 默认每次新请求可创建新短码,同一requestId重复必须返回同结果;是否同长URL自动去重由业务另定。
- 不假设固定QPS、保留年限、TTL或自定义alias;容量算式用变量及明确示例。
- 本篇先给类职责与生产存储合同,无唯一的公司指定短码策略;防猜与过期需求需面试时确认。
解题路线
- 类可把生成器、存储库和校验器注入,shorten负责校验、查幂等、分配code、事务插入,resolve只按code读状态和longUrl。不要把hash(longUrl)直接作为无需存储的可逆映射:有限短码无法恢复任意原URL。
- 表结构可为links(code PK,long_url,created_at,expires_at,status,owner_id),另有requests(owner_id,request_id,payload_hash,code)唯一键。后者将幂等范围与账号绑定;同键不同URL应报冲突,不能静默返回另一条链接。
- 单机递增ID经base62编码是确定唯一映射,code容量为62^L;无需哈希碰撞重试,但号码可被枚举,且扩容要解决ID分配。可以按业务接受枚举,或选安全随机码与数据库唯一索引,不能把编码当加密。
- 随机码必须以唯一索引作为最终仲裁。先查不存在再insert有并发竞态;正确做法生成后直接尝试插入,冲突重试并设有界失败策略。近似碰撞概率随累计创建数平方增长,不因每个字符随机便为零。
- 分布式唯一ID可由数据库序列、分段租赁或带节点/时间字段生成。序列简单却可能成写瓶颈;分段减少协调但允许空洞;时钟型方案必须处理节点冲突和时钟回退。选型要对齐创建速率、可猜性与故障合同。
- 长URL去重不能只拿截断hash当身份,碰撞时仍应比较规范化后原文或保留冲突桶;查询参数顺序、尾斜杠、fragment能否归一化由业务定义。不同用户所有权和过期策略也可能意味着同URL应保留不同记录。
- redirect读路径按code查缓存/数据库,成功返回Location;选择临时或永久重定向要考虑后续是否能改目标、过期或撤销,因为客户端/中间缓存可能绕过后来服务端校验。不存在、过期和禁用状态分开定义。
- 热点短链可缓存、CDN或读副本扩展,但禁用与过期需控制缓存TTL和失效传播;最终一致副本可能让刚创建的短链短暂404,可用主库回退或read-your-writes处理。恶意URL、枚举和扫描的防护不能用短码唯一性替代。
- 容量设创建速率w、保留秒数T、每条含索引字节b,则活跃存储约wTb,读流量按resolve QPS估计。示例100次/秒保留一年约31.5亿条,不是100次/秒乘一天;副本、索引、幂等记录另计。
手动走一遍
两个并发请求生成同code:唯一索引只允许一个成功,另一方重新生成,而不是互相覆盖long_url。请求A创建成功但响应丢失,重试同requestId应返回原code。若改用顺序ID编码,ID 1与2天然不同,但访问者可能猜出邻近code,这与碰撞是两个独立问题。
规模与取舍
code点查在哈希型内存索引平均O(1),数据库B树通常O(log N)且受IO影响;URL验证/哈希至少O(U),U为URL长度。总存储O(N·(U+元数据+索引)),不能只计几位短码;随机重试的期望成本依赖码空间占用率。
易错点与追问
容易失分的边界
- hash冲突后覆盖原URL。
- 先查再写却没有唯一约束。
- 把base62说成加密或防枚举。
- 永久缓存策略与可撤销短链冲突。
继续深挖
- 自定义alias怎样抢占并保证所有权?
- 要求不可猜code选多大空间?
- 链接撤销怎样同步边缘缓存?
- 多区域写怎样确保唯一与幂等?
