短 URL 系统是怎么设计的?
1. 什么是短链系统
短链系统是一种将长的、复杂的网页链接转换为更短、更简洁的链接的服务。例如,一个原始的长链接:
https://www.example.com/articles/2023/detailed-report-on-technology-trends-in-artificial-intelligence
可以通过短链系统转换为:
https://short.link/abc123
- go-zero 开发的短链系统:https://github.com/colinrs/goshorturl
2. 为什么需要短链系统
用户体验优化
- 缩短链接长度,方便分享
- 减少复杂链接的阅读和记忆负担
- 适用于社交媒体、短信等字符受限的场景,在对内容长度有限制的平台发文,可编辑的文字就变多了
短链跳转的基本原理
短网址是如何形成的
短链好处多多,那么它是如何工作的呢。我们用curl -v看看
~ curl -v GET 'http://127.0.0.1:8888/api/v1/shorturl/access?url=geRW0i1Jt5F'
* Could not resolve host: GET
* Closing connection
curl: (6) Could not resolve host: GET
* Trying 127.0.0.1:8888...
* Connected to 127.0.0.1 (127.0.0.1) port 8888
> GET /api/v1/shorturl/access?url=geRW0i1Jt5F HTTP/1.1
> Host: 127.0.0.1:8888
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Location: http://www.baidu.com
< Traceparent: 00-96a3c00fbe0455de9814a229b93c7c8d-adb0403c96337c9a-00
< Date: Tue, 17 Dec 2024 14:55:19 GMT
< Content-Length: 0
<
* Connection #1 to host 127.0.0.1 left intact
可以看到请求后,返回了状态码 302(重定向)与 location 值为长链的响应,然后浏览器会再请求这个长链以得到最终的响应,整个交互流程图如下
sequenceDiagram
participant Client as curl
participant Server as Short URL Service
participant Target as Target Website (baidu.com)
Client->>Server: GET /api/v1/shorturl/access/?url=geRW0i1Jt5F
Note over Client,Server: HTTP Request
Server-->>Client: 302 Found
Note over Server,Client: Redirect Response
Server->>Client: Location: http://www.baidu.com
Note right of Server: Set Redirect URL in Location Header
alt Implicit Client Behavior
Client->>Target: Follows 302 Redirect (not shown in diagram)
end
主要步骤就是访问短网址后重定向访问 B,那么问题来了,301 和 302 都是重定向,到底该用哪个,这里需要注意一下 301 和 302 区别
- 301代表 永久重定向,也就是说第一次请求拿到长链接后,下次浏览器再去请求短链的话,不会向短网址服务器请求了,而是直接从浏览器的缓存里拿,这样在 server 层面就无法获取到短网址的点击数了,如果这个链接刚好是某个活动的链接,也就无法分析此活动的效果。所以我们一般不采用 301。
- 302临时重定向,也就是说每次去请求短链都会去请求短网址服务器(除非响应中用 Cache-Control 或 Expired 暗示浏览器缓存),这样就便于 server 统计点击数,所以虽然用 302 会给 server 增加一点压力,但在数据异常重要的今天,这点代码是值得的,所以推荐使用 302!
系统内部短链生成流程
flowchart LR
Start[开始] --> GetShortCode["根据长码从缓存获取短码,检查短码是否已存在"]
GetShortCode --> IsShortCodeExists{短码是否已存在?}
IsShortCodeExists -->|否| GenerateShortCode["使用算法生成新的短码"]
GenerateShortCode --> StoreShortCode["将新生成的短码保存在存储中"]
IsShortCodeExists -->|是| CheckShortCodeLegality{保存的短码关系是否合法?}
CheckShortCodeLegality -->|否| ReturnShortCode[返回短码]
CheckShortCodeLegality -->|是| ReturnShortCode
短链生成的几种方法
哈希算法生成
MD5哈希
func generateByMD5(longURL string) string {
hash := md5.Sum([]byte(longURL))
// 取前6-8位作为短链接
shortCode := hex.EncodeToString(hash[:])[:8]
return shortCode
}
优点:
- 实现简单
- 冲突概率较低
缺点:
- 可能存在哈希碰撞
- 生成的短码不可预测
MurmurHash算法
func murmur64() uint64 {
return murmur3.Sum64([]byte(str))
}
优点:
- 冲突率更低
- 计算速度快
自增ID生成
数据库自增
func generateByAutoIncrement(longURL string) string {
// 数据库中保存长链接,并获取自增ID
id := saveURLAndGetID(longURL)
// 将ID转换为62进制
return base62Encode(id)
}
// base62编码 , 62进制编码缩短域名
func base62Encode(id int64) string {
const base = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
if id == 0 {
return "0"
}
var encoding string
for id > 0 {
encoding = string(base[id%62]) + encoding
id /= 62
}
return encoding
}
优点:
- 保证唯一性
- 可以直接通过短码还原ID
缺点:
- 需要数据库支持
- 性能相对较低
雪花算法(Snowflake)生成
type Snowflake struct {
sequence int64
lastTimestamp int64
machineID int64
}
func (s *Snowflake) NextID() int64 {
timestamp := time.Now().UnixNano() / 1e6
if timestamp < s.lastTimestamp {
panic("Clock moved backwards")
}
if timestamp == s.lastTimestamp {
s.sequence = (s.sequence + 1) & 4095
if s.sequence == 0 {
timestamp = s.waitNextMillis(timestamp)
}
} else {
s.sequence = 0
}
s.lastTimestamp = timestamp
return ((timestamp - 1609459200000) << 22) |
(s.machineID << 17) |
(s.sequence)
}
func generateBySnowflake(longURL string) string {
sf := &Snowflake{machineID: 1}
id := sf.NextID()
// 62进制编码缩短域名
return base62Encode(id)
}
优点:
- 高性能
- 分布式环境下唯一
- 可以包含时间戳信息
随机token生成
func generateByRandomToken(longURL string, length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
优点:
- 生成简单
- 短码更随机
缺点:
- 冲突风险较高
- 需要额外的唯一性检查
综合评估
方法 | 唯一性 | 性能 | 可读性 | 适用场景 |
---|---|---|---|---|
哈希算法 | 中 | 高 | 低 | 小规模系统 |
自增ID | 高 | 中 | 中 | 中小型系统 |
雪花算法 | 高 | 高 | 低 | 大规模分布式 |
随机Token | 低 | 高 | 高 | 短期临时链接 |
最佳实践建议
- 选择合适的算法: murmur3 转换为 63进制
url = base62Encode(murmur3.Sum64([]byte(url)))
func base62Encode(id uint64) string {
const base = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
if id == 0 {
return "0"
}
var encoding string
for id > 0 {
encoding = string(base[id%62]) + encoding
id /= 62
}
return encoding
}
- 增加防重复机制: 已存在则重复生成
func (s *urlManager) UrlToShortUrl(url string) string {
url = base62Encode(murmur3.Sum64([]byte(url)))
count, err := s.shortUrlRepo.CountShortUrl(s.db, url)
if err != nil || count > 0 {
var sid string
for i := 0; i < 3; i++ {
sid, err = shortid.Generate()
if err == nil && sid != "" {
break
}
}
url = url + sid
}
return url
}
- 使用缓存提高性能
func newLocalCache() (cache.Cache, error) {
memCache, err := cache.NewRistrettoCache(cache.RistrettoCacheConfig{
Capacity: 2147483648, // bytes, max mem:2G
NumCounters: 200000000, // max keys
CostFunc: cache.CostMemoryUsage,
}, codec.NewSonicCodec())
if err != nil {
return nil, err
}
return memCache, nil
}
- 定期清理过期短链
惰性删除缓存
if shortUrl.ExpireAt.UTC().Unix() < time.Now().UTC().Unix() {
gosafe.GoSafe(context.WithoutCancel(l.ctx), func() {
_ = l.svcCtx.LocalCache.Delete(context.WithoutCancel(l.ctx), req.Url)
_ = l.shortUrlManager.DelShortUrl(&model.ShortUrl{ShortUrl: shortUrl.ShortUrl})
})
return nil, code.UrlNotExist
}
数据追踪与分析
- 可以统计链接点击量
- 追踪用户来源和行为
- 提供详细的链接访问数据
func AccessShortUrlHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AccessShortUrlRequest
if err := httpy.Parse(r, &req); err != nil {
httpy.ResultCtx(r, w, nil, err)
return
}
l := shorturl.NewAccessShortUrlLogic(r.Context(), svcCtx)
resp, err := l.AccessShortUrl(&req)
if err != nil {
httpy.ResultCtx(r, w, nil, err)
return
}
// 设置 302 重定向和 Location 头
w.Header().Set("Location", resp.Localtion)
w.WriteHeader(http.StatusFound) // 302 状态码
accessLog := &model.UrlAccessLog{
ShortUrl: req.Url,
Ip: sql.NullString{String: r.RemoteAddr, Valid: true},
UserAgent: sql.NullString{String: r.UserAgent(), Valid: true},
Referrer: sql.NullString{String: r.Referer(), Valid: true},
}
_ = l.SaveAccessLog(accessLog)
}
}
安全性
- 可以隐藏原始链接,防止暴露复杂的 URL 结构
- 可以增加链接跳转前的安全检查
总结
短链系统是一个集数据追踪、用户体验优化于一体的实用性服务,通过巧妙的算法和高效的存储机制,为用户提供便捷的链接服务。
标签:return,string,err,URL,系统,url,func,设计,短链 From: https://blog.csdn.net/baidu_32452525/article/details/144621212