短网址设计
Scenario
需求
Apply Short URL
graph LR A(用户) --> |http://www.jiuzhang.com| B(http://bit.ly) B --> |http://bit.ly/1UloQB6| A根据 Short URL 还原 Long URL, 并跳转
graph LR A(用户) --> |"http://bit.ly/1UloQB6"| B(http://bit.ly) B --> |"Please redirect to www.jiuzhang.com"| A A --> |"Request"| C(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 里面新增一个字段, 因为这样会导致空间大量浪费. 可以新增一个表.
随机生成: 完全兼容, 直接插入就好.
标签:shortURL,URL,网址,62,QPS,设计,短链,id From: https://www.cnblogs.com/geraldkohn/p/17091099.html