[LeetCode] Design TinyURL 设计精简URL地址
Note: For the coding companion problem, please see: Encode and Decode TinyURL.
How would you design a URL shortening service that is similar to TinyURL?
Background:
TinyURL is a URL shortening service where you enter a URL such as https://leetcode.com/problems/design-tinyurl and it returns a short URL such as http://tinyurl.com/4e9iAk.
Requirements:
- For instance, "http://tinyurl.com/4e9iAk" is the tiny url for the page
"https://leetcode.com/problems/design-tinyurl". The identifier (the highlighted part) can be any string with 6 alphanumeric characters containing0-9,a-z,A-Z. - Each shortened URL must be unique; that is, no two different URLs can be shortened to the same URL.
Note about Questions:
Below are just a small subset of questions to get you started. In real world, there could be many follow ups and questions possible and the discussion is open-ended (No one true or correct way to solve a problem). If you have more ideas or questions, please ask in Discuss and we may compile it here!
Questions:
- How many unique identifiers possible? Will you run out of unique URLs?
- Should the identifier be increment or not? Which is easier to design? Pros and cons?
- Mapping an identifier to an URL and its reversal - Does this problem ring a bell to you?
- How do you store the URLs? Does a simple flat file database work?
- What is the bottleneck of the system? Is it read-heavy or write-heavy?
- Estimate the maximum number of URLs a single machine can store.
- Estimate the maximum number of queries per second (QPS) for decoding a shortened URL in a single machine.
- How would you scale the service? For example, a viral link which is shared in social media could result in a peak QPS at a moment's notice.
- How could you handle redundancy? i,e, if a server is down, how could you ensure the service is still operational?
- Keep URLs forever or prune, pros/cons? How we do pruning? (Contributed by @alex_svetkin)
- What API would you provide to a third-party developer? (Contributed by @alex_svetkin)
- If you can enable caching, what would you cache and what's the expiry time? (Contributed by @Humandroid)
这道系统设计的题跟之前的算法还是不一样的,代码只是其中的一部分,估计大部分还是要跟面试官侃大山,博主也不太熟悉这类题目,还是照着ztlevi大神的帖子来写吧。
S: Scenario 场景
长URL和短URL的相互转换
N: Need 需求
- QPS (Queires Per Second) 每秒查询数
- 日活用户:100M
- 每日人均使用量:(写)long2short 0.1,(读) short2long 1
- 每日请求量:写 10M,读 100M
- QPS:一天共有86400秒,约100K。写 100, 读 1K
- 峰值QPS:写 200, 读 2K
(千级的量可以用一个单SSD的MySQL机器来处理)
- Storage 存储
- 每天10M个新映射(长URL到短URL)
- 一个映射大约占100B的大小
- 每天1GB,1TB大约能扛三年
对于这种系统来说,存储不是问题。只有像Netflix那样的系统可能会有存储问题。通过SN分析,我们对系统有了一个大框架印象,这个系统可以使用单SSD机器来实现。
A: API 接口
只有一种类型的服务:URLService
- Core (Business Logic) Layer
- Class: URLService
- Interface:
- URLService.encode(string long_url)
- URLService.decode(stirng short_url)
- Web Layer
- REST API:
- GET: /{short_url}, return a http redirect response (301)
- POST: goo.gl method - google shorten URL
Request Body: {url=longUrl} e.g. {"longUrl": "http://www.google.com/"}
Return OK(200), short_url is included in the data
K: Data Access 数据访问
Step 1: Pick a storage structure 选择一个存储结构
- SQL VS NoSQL?
- 需要支持事务Transactions吗?NoSQL不支持事务Transactions。
- 需要Rich SQL Query吗? NoSQL不支持SQL那么多的Query。
- 需要高效开发吗?大多数的网络框架对SQL的支持性非常好,意味着系统不需要太多的代码。
- 需要AUTO_INCREMENT ID吗? NoSQL不支持这个,仅有一个全局卫衣的Object_id。
- 需要高QPS吗?NoSQL有高性能。比如Memcached的QPS可达到百万级,MondoDB可达万级,MySQL只有千级。
- 系统的可伸缩性Scalability有多高?SQL需要开发者写代码去伸缩Scale,而NoSQL自带该功能(Sharding,replica)。
- Answer 回答:
- 不需要 -> NoSQL
- 不需要 -> NoSQL
- 无所谓,因为只有很少的代码 -> NoSQL
- 算法需要AUTO_INCREMENT ID -> SQL
- 写 200,读 2K,不高 -> SQL
- 不高 -> SQL
- System Alogrithm 系统算法
- Hash 函数
long_url => md5/sha1
- md5将一个字符串转为128位,通常用16个字节的十六进制来表示:
http://site.douban.com/chuan -> c93a360dc7f3eb093ab6e304db516653
- sha1将字符串转为160位,通常用20个字节的十六进制来表示:
http://site.douban.com/chuan -> dff85871a72c73c3eae09e39ffe97aea63047094
这两个算法使得哈希值是随机分布的,但是冲突Conflicts无法避免。任何哈希算法都无法避免冲突问题。
- 优点:简单。我们用转换字符串的前6个字符
- 缺点:冲突
解决方法 1. 使用(long_url + timestamp)作为哈希函数的关键字Key。2. 当冲突时,重新生成哈希值(生成的值不同因为时间戳改变了)。
总之,当urls的个数超过十亿个,可能会有大量的冲突使得系统不高效。
- base62
将short_url用62 base标记。6位可以表示62^6 57 billion。
每个short_url表示一个十进制数,可以当作SQL数据库中的AUTO_INCREMENT ID。
class URLService {
public:
URLService() {
COUNTER = ;
elements = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
}
string longToShort(string url) {
string short_url = base10ToBase62(COUNTER);
long2short[url] = COUNTER;
short2long[COUNTER] = url;
++COUNTER;
return "http://tiny.url/" + short_url;
}
string shortToLong(string url) {
string prefix = "http://tiny.url/";
url = url.substr(prefix.size());
int n = base62ToBase10(url);
return short2long[n];
}
int base62ToBase10(string s) {
int n = ;
for (int i = ; i < s.size(); ++i) {
n = n * + convert(s[i]);
}
return n;
}
int convert(char c) {
if (c >= '' && c <= '') {
return c - '';
} else if (c >= 'a' && c <= 'z') {
return c - 'a' + ;
} else if (c >= 'A' && c <= 'Z') {
return c - 'A' + ;
}
return -;
}
string base10ToBase62(int n) {
string str = "";
while (n != ) {
str.insert(str.begin(), elements[n % ]);
n /= ;
}
while (str.size() != ) {
str.insert(str.begin(), '');
}
return str;
}
private:
unordered_map<string, int> long2short;
unordered_map<int, string> short2long;
int COUNTER;
string elements;
};
Step 2: Database Schema 数据库概要
一个表(id, long_url)。id是主键,通过long_url排序。基本的系统架构为:
Browser <-> Web <-> Core <-> DB
O: Optimize 优化
如何提高响应速度?
- 在网络服务器和数据库之间提高响应速度
使用Memcached来提高响应速度。当获得long_url时,先在缓存中搜索。我们可以把90%的读请求放在缓存当中。
- 在网络服务器和用户浏览器之间提高响应速度
不同的地区使用不同的网络服务器和缓存服务器。所有的地区共享一个数据库用来匹配用户到最近的网络服务器(通过DNS),当他们不在缓存中的时候。
如果我们需要多于一台的MySQL机器?
- 问题:
- 缓存用完了
- 越来越多的请求
- 越来越多的缓存丢失
- 解决方案:
- 垂直切分 Vertical Sharding
- 水平切分 Horizontal Sharding
最好的方式是水平切分。当前的表结构是(id, long_url),哪列可以当作切分关键字。
一个简单的方法是id模块切分。
现在有另一个问题:如何能使多个机器共享一个全局的AUTO_INCREMENT ID?
两种方法:1. 多使用一个机器去维护id。2. 使用zookeeper。都很操蛋。
所以,我们不适用AUTO_INCREMENT ID
好处是将切分关键字当作short_url的第一个字节。
另一种方法是用统一的哈希将循环断成62份。有多少份并没有啥关系,因为可能并没有62台机器(可能有360或其他的)。每台机器都是为循环的一部分的服务负责。
write long_url -> hash(long_url)%62 -> put long_url to the specific machine according to hash value -> generate short_url on this machine -> return short_url
short_url request -> get the sharding key (first byte of the short_url) -> search in the corresponding machine based on sharding key -> return long_url
每当我们增加一台新机器,将最多使用的机器的一半范围放到心的机器中。
更多优化
将中文服务器放在中国,美国的服务器放在美国。使用地理信息当作切分关键字,例如,0是中国的网站,1是美国的网站。
参考资料:
https://discuss.leetcode.com/topic/95853/a-complete-solution-for-tinyurl-leetcode-system-design
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] Design TinyURL 设计精简URL地址的更多相关文章
- [LeetCode] Encode and Decode TinyURL 编码和解码精简URL地址
Note: This is a companion problem to the System Design problem: Design TinyURL. TinyURL is a URL sho ...
- [LeetCode] 534. Design TinyURL 设计短网址
Note: For the coding companion problem, please see: Encode and Decode TinyURL. How would you design ...
- 535 Encode and Decode TinyURL 编码和解码精简URL地址
详见:https://leetcode.com/problems/encode-and-decode-tinyurl/description/ C++: class Solution { public ...
- LeetCode Design TinyURL
原题链接在这里:https://leetcode.com/problems/design-tinyurl/description/ 题目: How would you design a URL sho ...
- [LeetCode] Design HashMap 设计HashMap
Design a HashMap without using any built-in hash table libraries. To be specific, your design should ...
- [LeetCode] Design Twitter 设计推特
Design a simplified version of Twitter where users can post tweets, follow/unfollow another user and ...
- [LeetCode] Design HashSet 设计HashSet
Design a HashSet without using any built-in hash table libraries. To be specific, your design should ...
- [LeetCode] Design Tic-Tac-Toe 设计井字棋游戏
Design a Tic-tac-toe game that is played between two players on a n x n grid. You may assume the fol ...
- LeetCode 622:设计循环队列 Design Circular Queue
LeetCode 622:设计循环队列 Design Circular Queue 首先来看看队列这种数据结构: 队列:先入先出的数据结构 在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素 ...
随机推荐
- 每天学习点jquery
一.jquery选择器 1.根据给定的ID匹配一个元素(如果选择器中包含特殊字符,可以用两个斜杠转义)id选择器 举例:html代码 <div id="notMe">& ...
- nodejs安装和卸载
Ubuntu 上安装 Node.js 方式一:直接安装 一.安装 1.$ sudo apt-get install nodejs2.$ sudo apt-get install npm二.升级 ...
- Maven+SSM框架搭建【spring+springmvc+mybatis】
本案例用到:ssm[spring+springmvc+mybatis]框架 数据库:mysql (推荐使用mysql 或者 sqlserver .oracle太大,一般大型项目才会用到) 开发工具: ...
- 20162320刘先润第三周Bag类测试
前言 以下内容是本周Bag代码的课后作业,要求是完成伪代码.产品代码和测试代码,为了书写方便我将伪代码以注释的形式写在了产品代码的后面 测试步骤 1.首先对Bag类引用BagInterface的代码进 ...
- 201621123062《java程序设计》第11周作业总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 思维导图: 2. 书面作业 本次PTA作业题集多线程 2.1. 源代码阅读:多线程程序BounceThread 2 ...
- python 堆排序
堆排序就是把堆顶的最大数取出, 将剩余的堆继续调整为最大堆,具体过程在第二块有介绍,以递归实现 剩余部分调整为最大堆后,再次将堆顶的最大数取出,再将剩余部分调整为最大堆,这个过程持续到剩余数只有一个时 ...
- 【iOS】Swift LAZY 修饰符和 LAZY 方法
延时加载或者说延时初始化是很常用的优化方法,在构建和生成新的对象的时候,内存分配会在运行时耗费不少时间,如果有一些对象的属性和内容非常复杂的话,这个时间更是不可忽略.另外,有些情况下我们并不会立即用到 ...
- Beta冲刺Day1
项目进展 李明皇 今天解决的进度 点击首页list相应条目将信息传到详情页 明天安排 优化信息详情页布局 林翔 今天解决的进度 前后端连接成功 明天安排 开始微信前端+数据库写入 孙敏铭 今天解决的进 ...
- 顺企网 爬取16W数据保存到Mongodb
import requests from bs4 import BeautifulSoup import pymongo from multiprocessing.dummy import Pool ...
- Hibernate实体类注解解释
Hibernate注解1.@Entity(name="EntityName")必须,name为可选,对应数据库中一的个表2.@Table(name="",cat ...