跳到主要内容

设计短链服务

问题

如何用 Go 设计一个短链接生成服务?

答案

核心设计

短码生成方案

方案一:自增 ID + Base62

const base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

func Encode(id uint64) string {
if id == 0 {
return string(base62Chars[0])
}
var result []byte
for id > 0 {
result = append(result, base62Chars[id%62])
id /= 62
}
// 反转
slices.Reverse(result)
return string(result)
}

// Encode(1000000) = "4c92" (4个字符 → 6位可表示 62^6 = 568亿)

方案二:MD5 取前 N 位

func GenerateShortCode(url string) string {
hash := md5.Sum([]byte(url))
// 取前 8 字节转 Base62
id := binary.BigEndian.Uint64(hash[:8])
return Encode(id)[:7] // 取 7 位
}

API 设计

// 创建短链
r.POST("/api/shorten", func(c *gin.Context) {
var req struct {
URL string `json:"url" binding:"required,url"`
}
c.ShouldBindJSON(&req)

code := generateCode()
// 存储: code → URL
db.Create(&ShortURL{Code: code, OriginalURL: req.URL})

c.JSON(200, gin.H{"short_url": "https://short.io/" + code})
})

// 访问短链 → 重定向
r.GET("/:code", func(c *gin.Context) {
code := c.Param("code")

// 先查缓存
if url, ok := cache.Get(code); ok {
c.Redirect(301, url)
return
}

// 查数据库
var shortURL ShortURL
if err := db.Where("code = ?", code).First(&shortURL).Error; err != nil {
c.JSON(404, gin.H{"error": "not found"})
return
}

cache.Set(code, shortURL.OriginalURL, time.Hour*24)
c.Redirect(301, shortURL.OriginalURL)
})

关键决策

决策点方案
301 vs 302301 永久重定向(浏览器缓存),302 临时(可统计点击)
ID 生成分布式 ID(Snowflake)避免单点
防冲突布隆过滤器检查短码是否存在
缓存Redis 缓存热点短链

常见面试问题

Q1: 短码冲突怎么处理?

答案

  • 自增 ID:不会冲突(推荐)
  • 哈希:冲突时在原 URL 后加盐重新哈希
  • 布隆过滤器:写入前检查短码是否已存在

Q2: 同一个长 URL 生成相同的短码吗?

答案:取决于业务需求。如果用自增 ID,同一 URL 每次生成不同短码。如果需要去重,可以先查数据库或用 URL → code 的反向索引。

相关链接