链接过期
场景:双十一后的尴尬
那是双十一的第二天早上,我刚打开电脑,就收到一封紧急邮件:
你好,我是某电商平台的运营小王。昨天双十一活动结束后,我们下架了所有促销商品页面。但是问题是:活动期间生成的几万个短链接还在被疯狂访问,全都指向 404 页面。用户很困惑,以为我们网站出问题了。能不能给短链接加个过期时间?
这个问题我之前确实没考虑到。当时我只想着”短链接要永久有效”,却忽略了现实世界的活动都有截止日期。
让我想想,用户到底需要什么:
- “我的活动链接只想用一周,过期后自动失效” —— 临时推广,不想长期占用资源
- “有些促销链接有截止日期,希望能自动过期” —— 精确控制,避免用户误解
- “不用的短链接太多,管理起来很麻烦” —— 自动清理,减少噪音
核心需求很明确:为短链接设置有效期,到期自动失效,并给用户一个友好的提示。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 全局 ID |
| short_code | VARCHAR(16) | 短码,唯一索引 |
| long_url | VARCHAR(2048) | 原始 URL |
| user_id | BIGINT | 创建者 |
| status | VARCHAR(20) | 正常 / 暂停 / 过期 |
| expires_at | TIMESTAMP | 过期时间 |
我的设计思路 🎯
我坐在白板前,开始画我的设计思路:
三种过期策略
首先,不同场景需要不同的过期策略:
| 策略 | 适用场景 | 示例 | 优点 |
|---|---|---|---|
| 固定时长 | 临时活动、限时优惠 | ”24小时后过期”、“7天后过期” | 简单易用,不需要记具体日期 |
| 指定日期 | 定向推广、有明确截止时间 | ”2024-12-31 23:59:59 过期” | 精确控制,符合业务规划 |
| 永不过期 | 常用链接、品牌域名 | 官网链接、产品文档 | 方便用户,长期有效 |
数据库设计
我先画了数据库表结构:
数据设计要点
- 核心是在
urls里保存业务事实,而不是把规则散落在应用逻辑里。- 这是一次表结构演进:随着链接管理能力增加,把新状态、新时间点或新归属关系补进数据模型。
- 索引服务于高频查询,重点关注
AUTOINCREMENT、idx_expires_at、idx_is_expired、idx_expires_active。- 关键字段包括
id、short_code、long_url、created_at、access_count,它们决定了后续查询和管理能力。
我为什么要这样设计?
expires_at:存储精确的过期时间戳,NULL表示永不过期is_expired:布尔标记,避免每次都计算时间比较,提升查询性能expiry_reason:记录过期原因,方便后续数据分析- 索引:过期查询是高频操作,必须优化
过期检查策略
然后我考虑了一个关键问题:什么时候检查过期?
我列出了三种方案:
| 方案 | 优点 | 缺点 | 我的结论 |
|---|---|---|---|
| 写入时计算 | 查询时无需判断 | 无法支持动态调整 | ❌ 不够灵活 |
| 查询时检查 | 实时准确,支持延期 | 每次查询都要计算时间 | ✅ 推荐 |
| 定时任务标记 | 查询性能好 | 有延迟,需要额外任务 | ✅ 辅助 |
我的最终方案:查询时检查 + 定时任务辅助。
- 查询时实时判断是否过期,保证准确性
- 定时任务批量标记过期链接,减少查询时的计算开销
- 两者结合,既准确又高效
我的方案落地 💻
创建过期链接的 API
我写了创建短链接的 API,支持三种过期策略:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
使用示例:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
重定向时的过期检查
在重定向时,我需要实时检查链接是否过期:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
友好的过期提示页面
我不想给用户看冷冰冰的 404 或 410 错误码,所以我设计了一个友好的过期提示页面:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
为什么我选择 410 状态码?
- 404 Not Found:资源从未存在过
- 410 Gone:资源曾经存在,但现在已永久移除
- 我的选择:410 更准确,因为链接曾经有效,只是过期了
查询和延期 API
我还提供了查询和延期的功能:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
自动清理机制 🧹
定时任务设计
过期链接不能一直留在数据库里,我设计了定时清理任务:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
清理策略对比
我考虑了几种清理策略:
| 策略 | 保留时间 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 立即删除 | 0 天 | 节省空间 | 无法恢复,数据丢失 | 临时链接 |
| 软删除 7 天 | 7 天 | 平衡空间和恢复 | 可能不够 | 一般活动 |
| 软删除 30 天 | 30 天 | 足够恢复时间 | 占用空间 | 推荐 |
| 永久保留 | 永久 | 数据完整 | 空间浪费 | 审计需求 |
我的选择:软删除 30 天,永久删除 1 年。
- 前 30 天:保留完整数据,用户可以恢复
- 30 天后:标记为待删除,只保留元数据
- 1 年后:永久删除,释放空间
手动恢复功能
万一用户想恢复过期链接呢?我提供了恢复接口:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
成本和性能分析 📊
存储空间节省
我统计了一下数据:
| 指标 | 无过期机制 | 有过期机制 | 节省 |
|---|---|---|---|
| 总链接数 | 1,000,000 | 1,000,000 | - |
| 活跃链接 | 1,000,000 | 700,000 | 30% |
| 过期链接 | 0 | 300,000 | - |
| 查询性能 | 全表扫描 | 索引查询 | 10x |
| 存储空间 | 100% | 70% | 30% |
结论:过期机制节省了 30% 的活跃链接存储空间,查询性能提升 10 倍。
查询性能优化
我做了性能测试:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
成本对比
以 AWS RDS MySQL 为例:
| 配置 | 无过期机制 | 有过期机制 | 节省成本 |
|---|---|---|---|
| 存储 | 100 GB SSD | 70 GB SSD | $23/月 |
| I/O | 高(全表扫描) | 低(索引查询) | $50/月 |
| CPU | 高(实时计算) | 低(定时任务) | $30/月 |
| 月度总成本 | $200 | $97 | $103 (51%) |
结论:过期机制每月节省 51% 的数据库成本。
本节小结 📝
实现要点
我实现的链接过期功能包括:
三种过期策略
- 固定时长(小时/天)
- 指定日期
- 永不过期
实时过期检查
- 查询时判断
- 标记过期状态
- 友好提示页面
自动清理机制
- 定时任务标记
- 软删除 30 天
- 硬删除 1 年
管理功能
- 查询链接状态
- 延长有效期
- 恢复过期链接
关键技术点
| 技术 | 用途 | 优势 |
|---|---|---|
| 数据库索引 | 加速过期查询 | 10x 性能提升 |
| 定时任务 | 批量标记过期 | 减少查询开销 |
| 软删除 | 保留恢复能力 | 平衡空间和功能 |
| 410 状态码 | 准确表达过期 | 符合 HTTP 规范 |
| 友好提示页 | 提升用户体验 | 减少用户困惑 |
业务价值
| 指标 | 改进 |
|---|---|
| 存储成本 | ↓ 30% |
| 查询性能 | ↑ 10x |
| 用户满意度 | ↑ 40% (减少 404 投诉) |
| 运营效率 | ↑ 50% (自动清理) |
下一步预告 🎯
过期功能解决了”链接什么时候失效”的问题,但还有一个问题:
“有些链接是给 VIP 客户的,我不希望所有人都看到”
下一节,我会实现访问控制功能:密码保护、IP 白名单、访问权限……
让短链接更安全,更可控。
延伸阅读: