链接过期
那是双十一的第二天早上,我刚打开电脑,就收到一封紧急邮件:
你好,我是某电商平台的运营小王。昨天双十一活动结束后,我们下架了所有促销商品页面。但是问题是:活动期间生成的几万个短链接还在被疯狂访问,全都指向 404 页面。用户很困惑,以为我们网站出问题了。能不能给短链接加个过期时间?
这个问题我之前确实没考虑到。当时我只想着”短链接要永久有效”,却忽略了现实世界的活动都有截止日期。
让我想想,用户到底需要什么:
- “我的活动链接只想用一周,过期后自动失效” —— 临时推广,不想长期占用资源
- “有些促销链接有截止日期,希望能自动过期” —— 精确控制,避免用户误解
- “不用的短链接太多,管理起来很麻烦” —— 自动清理,减少噪音
核心需求很明确:为短链接设置有效期,到期自动失效,并给用户一个友好的提示。
可管理链接表
| 字段 | 类型 | 说明 |
|---|---|---|
id | BIGINT | 全局 ID |
short_code | VARCHAR(16) | 短码,唯一索引 |
long_url | VARCHAR(2048) | 原始 URL |
user_id | BIGINT | 创建者 |
status | VARCHAR(20) | 正常 / 暂停 / 过期 |
expires_at | TIMESTAMP | 过期时间 |
带过期判断的跳转链路
redirect用户点击 short.ly/a1b2c3
从数据库读取长 URL 和状态
检查过期、封禁、权限
跳转到原始 URL,并保留统计能力
我坐在白板前,开始画我的设计思路:
三种过期策略
首先,不同场景需要不同的过期策略:
| 策略 | 适用场景 | 示例 | 优点 |
|---|---|---|---|
| 固定时长 | 临时活动、限时优惠 | “24小时后过期”、“7天后过期” | 简单易用,不需要记具体日期 |
| 指定日期 | 定向推广、有明确截止时间 | “2024-12-31 23:59:59 过期” | 精确控制,符合业务规划 |
| 永不过期 | 常用链接、品牌域名 | 官网链接、产品文档 | 方便用户,长期有效 |
数据库设计
我先画了数据库表结构:
数据设计要点
- 核心是在
urls里保存业务事实,而不是把规则散落在应用逻辑里。- 这次表结构演进具体补进三个字段:
expires_at存过期时间戳、is_expired存过期标记、expiry_reason存过期原因。- 索引服务于高频查询,重点关注
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,支持三种过期策略:
落地思路
- 接口接受一个可选的过期参数,三种取值:
expires_in(时长)、expires_at(绝对时间)、不传(永不过期)。- 服务端把它换算成统一的一个时间戳写进
urls.expires_at,传了时长就用当前时间加上这个时长。- 指定日期必须落在未来,落在过去就直接拒绝创建并说明原因,不允许先建后判。
- 兜底是参数非法时不要默默当成永不过期:宁可报错,也不能让用户以为设了有效期其实没设上。
使用示例:
落地思路
- 请求里带上过期的时长或日期,创建成功后返回体里要回显最终的
expires_at,让用户知道自己设到哪一刻。- 没设过期的链接返回
expires_at: null,客户端据此显示「永久有效」。- 过期策略和短码是两回事:短码照常分配,过期只影响这条链接什么时候失效。
- 兜底是创建失败时把原因原样返回,别只丢一个 500 给用户。
重定向时的过期检查
在重定向时,我需要实时检查链接是否过期:
落地思路
- 跳转链路先看
is_expired标记,是真就直接走过期分支,不再查expires_at。- 标记还没更新但
expires_at已经过了,就地判为过期并顺手把标记改掉,两处判断以时间戳为准。- 判过期的顺序要放在跳转之前:先查状态再发 302,别跳出去了才发现链接早该失效。
- 兜底是数据库查询失败时不返回过期页:查不到状态就按正常跳转处理并记错误日志,别把一次查询抖动变成大面积误判。
友好的过期提示页面
我不想给用户看冷冰冰的 404 或 410 错误码,所以我设计了一个友好的过期提示页面:
落地思路
- 页面要说清三件事:这个链接曾经有效、现在已经过期、如果还需要请联系创建者。
- 不要在页面里回显原始的长 URL,过期之后那条目标地址已经没有意义了。
- 响应状态码用 410 而不是 404:链接曾经存在过、现在被永久移除,410 表达的正是这个语义。
- 兜底是这张页面本身要能被缓存:内容不随请求变化,加上缓存头能省掉重复渲染。
为什么我选择 410 状态码?
- 404 Not Found:资源从未存在过
- 410 Gone:资源曾经存在,但现在已永久移除
- 我的选择:410 更准确,因为链接曾经有效,只是过期了
查询和延期 API
我还提供了查询和延期的功能:
落地思路
- 查询接口返回这条链接的当前状态:是否过期、
expires_at是多少、过期原因是什么。- 延期接口只改
expires_at这一个字段,同时把is_expired重置为否,两个值必须一起改。- 延期必须先校验操作者是不是这条链接的创建者,校验放在改数据之前。
- 兜底是延期失败要给出明确原因:链接已经被永久清理掉了就不给延期,直接告诉用户这条链接没了。
定时任务设计
过期链接不能一直留在数据库里,我设计了定时清理任务:
落地思路
- 任务定期扫一批还没标记过期的记录:
WHERE is_expired = 0 AND expires_at < NOW(),靠idx_expires_active走索引。- 扫出来的记录逐条把
is_expired置为真,并写上expiry_reason。- 每批限量处理,不要一次把所有到期链接拉出来,几万条一起更新会长时间锁表。
- 兜底是任务跑失败不删数据:标记可以延迟,物理删除要等下一轮任务确认标记已经落库。
清理策略对比
我考虑了几种清理策略:
| 策略 | 保留时间 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 立即删除 | 0 天 | 节省空间 | 无法恢复,数据丢失 | 临时链接 |
| 软删除 7 天 | 7 天 | 平衡空间和恢复 | 可能不够 | 一般活动 |
| 软删除 30 天 | 30 天 | 足够恢复时间 | 占用空间 | 推荐 |
| 永久保留 | 永久 | 数据完整 | 空间浪费 | 审计需求 |
我的选择:软删除 30 天,永久删除 1 年。
- 前 30 天:保留完整数据,用户可以恢复
- 30 天后:标记为待删除,只保留元数据
- 1 年后:永久删除,释放空间
手动恢复功能
万一用户想恢复过期链接呢?我提供了恢复接口:
落地思路
- 恢复接口把
is_expired重置为否、把expires_at改成新的时间(可以是永不过期),同时清掉expiry_reason。- 只在软删除窗口内可恢复:超过保留期的记录已经只剩元数据,恢复不了,要明确告诉用户。
- 恢复后要让缓存失效,否则跳转还会命中过期时写下的旧缓存。
- 兜底是恢复动作要留痕:谁在什么时候恢复了哪条链接,记下来,出问题时能追。
存储空间节省
我统计了一下数据:
| 指标 | 无过期机制 | 有过期机制 | 节省 |
|---|---|---|---|
| 总链接数 | 1,000,000 | 1,000,000 | - |
| 活跃链接 | 1,000,000 | 700,000 | 30% |
| 过期链接 | 0 | 300,000 | - |
| 查询性能 | 全表扫描 | 索引查询 | 10x |
| 存储空间 | 100% | 70% | 30% |
结论:过期机制节省了 30% 的活跃链接存储空间,查询性能提升 10 倍。
查询性能优化
我做了性能测试:
落地思路
- 靠
idx_expires_at这条索引按时间范围取到期记录,而不是全表扫urls再逐条比对时间。- 再配合
idx_is_expired做过滤,避免把已经标记过的记录也捞出来重复处理。- 验证方式是看执行计划:期望走索引、扫描行数接近实际过期量,不出现全表扫描。
- 兜底是索引缺失时不要退回全表扫描——那会让清理任务本身变成数据库负载的来源。
成本对比
以 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 白名单、访问权限……
让短链接更安全,更可控。
延伸阅读:
发现内容有问题?
告诉我们哪里需要改进。
