如何设计短链接系统?
短链接系统的设计核心只有三件事:一个能稳定发号的短码生成器、一张短码到长链的映射表、一次 302 重定向。真正的难点不在生成短码,而在于重定向接口要扛住读写比几十万比一的高并发,以及缓存穿透和短码冲突的兜底处理。
短链接系统的最小可用架构由哪几部分组成?
结论:一个够用的短链接系统最少需要 4 个模块——发号器、映射存储、缓存层、重定向服务。
写入链路是:用户提交长链接 → 服务生成唯一短码 → 写入 MySQL 映射表 → 返回 https://短域名/Ab3xK9。读取链路是:用户访问短链 → 服务用短码查缓存 → 缓存没有则查 DB 并回填 → 返回 HTTP 重定向。
写流量通常只有总请求量的万分之一,读流量是绝对瓶颈。所以架构上所有优化都应该压在读路径上,写路径用最简单的同步写 MySQL 就够了,不要一上来就上消息队列。
短码怎么生成?Base62 加自增 ID 是最稳的方案
结论:用全局自增 ID 做输入、Base62 编码做输出,是目前最不容易出错的短码生成方式。
Base62 指用 0-9、a-z、A-Z 共 62 个字符做进制转换。6 位 Base62 能表示 62⁶ ≈ 568 亿个短码,7 位能表示约 3.5 万亿个,绝大多数业务用 6 位足够。
具体做法:Redis 用 INCRBY shortlink:seq 1000 一次取 1000 个号缓存在本地内存,用完再取下一段。这样 Redis 的 QPS 直接降到原来的 1/1000,单机 Redis 就能支撑每秒百万级的短码生成。多实例部署时给每个实例配置不同的初始偏移和相同步长(如实例 A 从 1 开始、实例 B 从 2 开始、步长都取 10),就不会撞号。
不推荐直接对长链做 MD5 取前 8 位再 Base62,因为哈希冲突需要额外查重,而且 8 位 MD5 截断后的冲突概率在亿级数据量下不可忽略。它唯一的优势是「同一长链永远得到同一短码」,如果业务有这个需求可以把它作为二级方案。
重定向应该用 301 还是 302?
结论:默认用 302,只有确定链接永不修改、且不需要点击统计时才用 301。
301 是永久重定向,浏览器会把它缓存下来,用户第二次点击时请求根本不会到达你的服务器——结果是点击统计直接失效,而且你再也无法修改这条短链的目标地址。302(或 307)是临时重定向,每次点击都会回源,才能做统计、风控和失效控制。
企业级短链服务的点击数据、地域分布、渠道效果都依赖这个回源过程,所以 302 是行业默认选择。
高并发读怎么扛住?缓存要分两级
结论:用「本地缓存 + Redis + 布隆过滤器」三层挡在读路径前面,能让 99% 的请求不落到 MySQL。
第一层是进程内的本地缓存(如 Caffeine),缓存热点短码,命中耗时在微秒级;第二层是 Redis,存 short_code -> long_url 的映射,TTL 可以设 7 天并开启 LRU 淘汰;第三层是 MySQL,只在缓存未命中时查询。
缓存穿透必须专门处理:用布隆过滤器拦截明显不存在的短码,命中「不存在」时直接返回 404,不要查 DB;同时对确实查不到的短码缓存一个空值(TTL 设 60 秒左右),防止恶意刷不存在的短码把 DB 打挂。
数据怎么存、怎么分片?
结论:MySQL 单表存 short_code(varchar(8),唯一索引)、long_url(varchar(2048))、create_time、expire_time、user_id 就够;数据量过千万行后按短码做分片,分成 1024 张表,用 hash(short_code) % 1024 路由。
分片键选短码而不是 user_id,因为读取路径永远是用短码查,用短码做分片键能让每次查询只落一张表。短码本身是随机分布的,天然避免了热点分片。
还有哪些容易踩的坑?
三个高频问题:一是短码保留字,api、admin、favicon.ico 这类路径要提前拉黑,否则会路由冲突;二是长链要做协议校验和黑名单过滤,防止被用来做钓鱼跳板;三是同一长链重复提交时,建议按 user_id + 长链 MD5 做一次去重查询,避免用户后台里出现一堆指向同一个地址的短链。
总结:短链系统的设计重点是「写简单、读极致」——自增 ID 加 Base62 发号,映射存 MySQL 并按短码分 1024 张表,读路径用本地缓存加 Redis 加布隆过滤器三层兜底,重定向统一用 302 保证可统计可撤销。
原文链接:https://www.gj0.com/thread-888.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。