短链接生成应优先采用自增id转62进制而非哈希;因哈希存在长度过长与截断后冲突风险,而自增id可保证唯一性、定长(5–6位)且无需重试,配合预定义字符集递归取余实现高效转换。

短链接生成的核心逻辑:用哈希还是自增 ID?
直接用 MD5 或 SHA256 哈希原始 URL 生成短码,看似简单,但实际会踩两个坑:一是哈希值太长(MD5 是 32 位十六进制),二是不同 URL 可能哈希出相同前缀(虽然概率低,但短码截取后冲突风险陡增)。更稳妥的做法是用自增 int64 主键转 62 进制(0–9 + a–z + A–Z),比如 1 → "1",62 → "z",63 → "10"。这样能保证唯一性、可预测长度(通常 5–6 位)、且无需查重重试。
实操建议:
- 数据库建表时主键用
BIGINT AUTO_INCREMENT(MySQL)或SERIAL(PostgreSQL),避免用 UUID 字符串做主键,否则丧失顺序性和索引效率 - 62 进制转换函数别手写循环除法,用预定义字符集 + 递归取余更稳:
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"<br>func toBase62(n int64) string {<br> if n == 0 { return "0" }<br> var buf []byte<br> for n > 0 {<br> buf = append(buf, chars[n%62])<br> n /= 62<br> }<br> // reverse in-place<br> for i, j := 0, len(buf)-1; i buf[i], buf[j] = buf[j], buf[i]<br> }<br> return string(buf)<br>} - 如果担心自增 ID 泄露业务量(比如第一天上线就生成了 10 万条),可在入库前对 ID 加密混淆,例如
id ^ 0xdeadbeef再转 62 进制,解码时再异或回来
HTTP 路由与 302 重定向怎么写才不丢 Referer?
用 http.Redirect 默认发的是 302,浏览器会保留原始 Referer,这点没问题;但如果你手动写 w.WriteHeader(302) + w.Header().Set("Location", ...),漏掉 w.Header().Set("Cache-Control", "no-cache"),某些 CDN 或代理可能缓存这个跳转响应,导致后续请求被错误重定向到旧目标。
实操建议:
- 始终用
http.Redirect(w, r, target, http.StatusFound),别自己拼 Header —— 它内部已处理Cache-Control和Vary - 短码路由必须严格匹配,避免
/a匹配到/abc:注册路由用http.HandleFunc("/"+shortCode, handler)不够,得用带尾部/判断或正则,推荐用chi.Router或原生http.ServeMux配合路径检查:if len(r.URL.Path) != 2 || r.URL.Path[1] == '/' {<br> http.NotFound(w, r)<br> return<br>} - 重定向前务必校验短码是否在库中存在,不存在就返回 404,别跳到首页——否则爬虫和用户都会误以为链接有效
并发写入时怎么防止重复插入同一条 URL?
用户连续点两次“生成”,前端没防抖,后端若先查再插(select + insert),在高并发下大概率触发重复插入,报 duplicate key 错误。不能靠捕获错误再查一遍,因为这会放大延迟,还可能因事务隔离级别漏判。
实操建议:
- 把“查找已有短码”和“插入新记录”合并成一条
INSERT ... ON CONFLICT DO NOTHING(PostgreSQL)或INSERT IGNORE(MySQL),然后用sql.Result.RowsAffected()判断是否真插入了新行 - 如果用 MySQL 且版本 INSERT ... ON DUPLICATE KEY UPDATE ... VALUES(LAST_INSERT_ID(), ...) 获取刚插入的 ID,那就得改用
SELECT ... FOR UPDATE加行锁,但性能差,不如升级或换 PostgreSQL - 应用层加一层内存缓存(如
sync.Map)存「URL → shortCode」映射,有效期设 10 秒,能挡住大量重复请求,但注意缓存击穿:需配合单例加载(single-flight)模式,避免多个 goroutine 同时查 DB
为什么不要在短链接服务里做访问统计的实时聚合?
每跳一次就往数据库写一条访问日志并更新计数器,QPS 上千时,MySQL 的 UPDATE counter = counter + 1 会成为瓶颈,还会因行锁导致慢查询堆积。更糟的是,如果统计字段和跳转逻辑共用一张表,一次跳转要持锁读+写,吞吐直接腰斩。
实操建议:
- 跳转主流程只做两件事:查短码对应 URL、302 重定向。所有统计行为异步化——用
channel收集访问事件,后台 goroutine 批量刷入 Redis 或专用日志表 - Redis 用
INCR计数足够快,但注意过期策略:给每个短码的计数 key 设EXPIRE7 天,避免冷数据长期占内存 - 如果必须落盘做长期分析,写入 Kafka 或直接打日志文件(
log.Printf到本地磁盘),再由 Logstash/Flink 消费,别让核心跳转链路等 I/O
短链接系统真正的复杂点不在编码或跳转,而在于如何把「幂等写入」「无锁读取」「异步可观测性」这几件事拆干净。很多团队卡在“先快速上线”,结果三个月后发现日志表爆到 200GB、重定向平均延迟从 8ms 涨到 200ms——这时候再拆,就得停机迁移了。











