4 短网址设计

短网址设计

Scenario#

需求#

Apply Short URL

http://www.jiuzhang.com
http://bit.ly/1UloQB6
用户
http://bit.ly

根据 Short URL 还原 Long URL, 并跳转

http://bit.ly/1UloQB6
Please redirect to www.jiuzhang.com
Request
用户
http://bit.ly
http://www.jiuzhang.com

两个需要和面试官确认的问题:

  • Long URL 和 Short URL 必须是一一对应的关系么

  • Short URL 长时间没人用需要释放么

不同的要求设计出来的系统完全不一样.

QPS#

询问面试官日活跃用户: 比如weibo, 日活 100M, 假设每个用户每天发 0.1 条带分享链接的微博. Avarage Write QPS: 100M x 0.1 / 86400 = 100. Peak Write QPS = 100 x 3 = 300. 推算点击一条 Short URL 的 QPS, 假设每个用户每天平均点击 1 次, Avarage Read QPS = 100M x 1 / 86000 = 1k, Peak Read QPS = 1k x 3 = 3k.

推算每天产生的新的 URL 所占的存储: 100M x 0.1 = 10M 条. 每一条 URL 平均长度为 100 bit, 一共 1G.

Service#

只用一个 URL Service. 301 Moved Permanetly, 302 Moved temporary.

算法1 使用 Hash Function 后截取#

比如说取 Long URL 的 MD5 的最后 6 位, 这样肯定是有问题的, 不同的 Long URL 经过这么操作很有可能得到相同的结果.

算法2 随机生成 short URL + 数据库去重#

伪代码如下:

func LongToShort(longURL string) string {
    for {
        var shortURL = randomURL()
        if !database.filter(shortURL).exists() {
            database.create(shortURL, longURL)
            return shortURL
        }
    }
}

优点: 实现简单.

缺点: 短链网址会生成的越来越多, 所以遇到冲突的情况会越来越多. 这样就可能需要循环很多次才能找到.

算法3 进制转换 Base62#

将 6 位的 Short URL 看作一个 62 进制数 (0-9, a-z, A-Z). 每个 short URL 对应一个整数, 该整数对应数据库表的自增 ID, 可以作为主键也可以不做主键. 这里有一个严重的问题, 如果数据库要分片的话, 在多台服务器上维护一个全局自增 ID 很麻烦.

6 位可以表示的 URL 有多少: 62 ^ 6 = 570 亿

// 将62进制的数转换为10进制的数
func ShortURLToID(shortURL string) int {
    shortURLBytes := []byte(shortURL)
    id := 0
    for i := 0; i < len(shortURLBytes); i++ {
        id = id * 62 + toBase62(shortURLBytes[i])
    }
    return id
}

// 将10进制数转换为6位62进制的数
func IDToShortURL(id int) {
    bytes := []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
    shortURL := ""
    for id > 0 {
        shortURL = bytes[id % 62] + shortURL // 向高位累加
        id = id / 62 // 下一位
    }
    for len(shortURL) < 6 { // 没到6位
        shortURL = "0" + shortURL // 在高位占位
    }
    return shortURL
}

优点: 效率高. 缺点: 依赖于全局的递增ID, 只能依赖于单个表, 无法扩展.

Storage#

最重要的部分!!!

SQL vs NoSQL 到底怎么选呢?

  • 不需要支持事务: NoSQL + 1

  • 不需要丰富的 SQL query: NoSQL + 1

  • NoSQL 实现并不复杂: NoSQL + 1

  • QPS 不高: SQL + 1

Scale#

提高响应速度#

这是一个读多写少的系统, 所以使用 Cache 来进行优化. Cache 中存储着两种数据, 一种是 long to short , 在生成 short url 之前看看之前是否已经生成过了. 一种是 short to long, 查询 short url 对应的 long url.

利用地理位置加速: 使用 DNS 使得不同地区 Web Server 不一样, 将缓存分布在各个地区.

面试官说, 单机 SQL 搞不定了#

什么时候需要多台 MySQL

  • Cache 资源可能不够了

  • 写操作越来越多

增加多台 MySQL 可以优化什么:

  • 解决存不下的问题: Storage

  • 解决忙不过的问题: QPS

短链系统明显是忙不过来的问题.

那如何扩展呢?

  • 垂直拆分: 字段就两个, 不能拆分了

  • 水平拆分:

    • 如果用 ID/ShortURL 做 Sharding Key

      • 长链转换为短链的时候需要广播给 N 台数据库来查询, 因为创建的时候需要避免重复创建.

      • 短链转换为长链只需要用 ShortURL 来 hash 就行. 这样就查找一台 MySQL 就能找到数据.

    • 如果用 Long URL 做 Sharding Key

      • 短链转换为长链的时候需要广播给 N 台数据库来查询

      • 长链转换为短链的时候只需要用 LongURL 来 hash 就行. 这样查找一台 MySQL 就能找到数据.

选 Sharding Key#

如果一个 LongURL 可以对应多个 ShortURL, 那么直接用 ShortURL 即可, LongURL 在创建的过程中不需要保证一一对应.

如果一个 LongURL 只能对应一个 ShortURL:

  • 如果使用随机生成的 Short URL 的算法: 选 ShortURL 吧, 因为短链转换为长链的需求肯定大于长链转换为短链

  • 如果使用 Bash62 进制转换法: 这里有个非常严重的问题, 如何在多台机器上维护一个全局自增 ID?

在多台机器上维护一个全局的自增 ID, 可以使用 Etcd, Zookeeper 等分布式 k-v 数据库.

使用 Hash(LongURL) % 62 作为 Sharding Key. 并将 Hash(LongURL) % 62 直接放入到 shortURL 的最高位上, 将其变为 7 位. 这样我们在查找的时候无论是 shortToLong 还是 longToShort 都可以得到 Sharding Key 从而找到具体的位置.

shortURL 最高位即是 Sharding Key, hash(longURL) % 62 即是 Sharding Key.

这种做法的缺点是机器的数目 <= 62.

还有可以优化的么#

可以根据地域来优化

用户自定义 URL#

Base62: 不能在 URL Table 里面新增一个字段, 因为这样会导致空间大量浪费. 可以新增一个表.

随机生成: 完全兼容, 直接插入就好.

作者:Kohn

出处:https://www.cnblogs.com/geraldkohn/p/17091099.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   kohn  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示