短链接服务的核心是避坑:base64编码不安全、int64自增ID暴露总量、Redis计数器缺并发回滚、重定向状态码误用均致故障;短码须不可预测、定长、高熵,推荐base62随机生成并查库去重;路由需保留完整query参数;缓存应写DB后删Redis保障最终一致性。

短链接服务的核心不是“怎么写”,而是“怎么避免踩坑”——base64 编码不安全、int64 自增ID暴露总量、Redis 原子计数器没考虑并发回滚、HTTP 重定向状态码用错都会直接导致线上故障。
为什么不能直接用 base64 或 hex 编码原始 URL
这是最常见也最危险的偷懒做法。短链本质是映射关系,不是压缩或加密;对原始 URL 做 base64 会生成过长字符串(比如 "aHR0cHM6Ly9leGFtcGxlLmNvbS8xMjM="),且可逆、无意义、易被批量探测。
- 真正可用的短码必须是「不可预测 + 定长 + 高熵」,推荐用
rand.Read生成随机字节后转base62(含 0-9a-zA-Z,不含易混淆字符如0/O/I/l) - 生成后必须查库去重:即使碰撞概率极低,也要
INSERT ... ON CONFLICT DO NOTHING(PostgreSQL)或INSERT IGNORE(MySQL)兜底 - 别在 HTTP 参数里传原始 URL 再编码——攻击者可构造超长 URL 触发 OOM,必须提前校验长度(建议上限
2048字节)
gorilla/mux 和 net/http 路由在重定向场景下的关键区别
短链跳转本质是 301/302 重定向,路由层必须支持路径通配且不解析 query 参数——否则 /abc?ref=weibo 会被误拆成 path="/abc" + query="ref=weibo",丢失原始参数。
- 用
net/http最简方案:http.HandleFunc("/<code>{short}", handler) 不行,它不支持路径变量;必须用http.ServeMux的默认行为 + 手动截取路径:strings.TrimPrefix(r.URL.Path, "/") -
gorilla/mux正确写法:r.HandleFunc("/{short:[a-zA-Z0-9]{4,8}}", redirectHandler).Methods("GET"),正则限定了短码格式,且自动忽略 query string - 务必设置
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate"):防止 CDN 或浏览器缓存错误的 301,导致改链后用户仍跳转旧地址
Redis + PostgreSQL 双写时如何保证最终一致性
短链读多写少,通常用 Redis 缓存热 key("short:abc123" → "https://..."),但写入时若只更新 Redis 或只更新 DB,就会出现脏数据。
立即学习“go语言免费学习笔记(深入)”;
- 不要用「先写 DB 再写 Redis」:DB 写成功但 Redis 写失败,缓存缺失后穿透到 DB,扛不住流量
- 推荐「写 DB 后删 Redis」:
DELETE short:abc123,下次读 miss 时再回源加载——删除失败影响小(最多缓存旧值几秒),且可用redis.Del的返回值判断是否成功 - 如果必须强一致,用 PostgreSQL 的
LISTEN/NOTIFY:插入成功后发通知,Go 启一个 goroutine 异步刷新 Redis,避免阻塞主流程 - 短码生成和存储必须在同一事务内完成,否则可能生成重复短码(例如两个并发请求同时生成
abc123,都查库发现不存在,然后都插入)
短链服务最难的从来不是生成算法,而是当 QPS 突增到 5000+ 时,你能否在不加机器的前提下,靠连接池配置、Redis pipeline、SQL 查询索引、HTTP 头精简把延迟压在 20ms 内——这些细节,往往在压测前没人碰。


















