[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 数据结构中,将首先处理添加到队列中的第一个元素 ...
随机推荐
- java设计模式------建造者模式
建造者模式(Builder),将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 类图 描述 Builder:定义一个建造者抽象类,以规范产品对象的各个组成部分的建造.这个接口 ...
- 将 Shiro 作为应用的权限基础 五:SpringMVC+Apache Shiro+JPA(hibernate)整合配置
配置web.xml,applicationContext.xml, spring-mvc.xml,applicationContext-shiro.xml,而且都有详细的说明. Web.xml是web ...
- System V IPC 之共享内存
IPC 是进程间通信(Interprocess Communication)的缩写,通常指允许用户态进程执行系列操作的一组机制: 通过信号量与其他进程进行同步 向其他进程发送消息或者从其他进程接收消息 ...
- python内置函数 divmod()
先来看一下builtins.py中的代码: def divmod(x, y): # known case of builtins.divmod """ Return th ...
- Leetcode 17.——Letter Combinations of a Phone Number
Given a digit string, return all possible letter combinations that the number could represent. A map ...
- JavaScript(第二十一天)【DOM元素尺寸和位置】
学习要点: 1.获取元素CSS大小 2.获取元素实际大小 3.获取元素周边大小 本章,我们主要讨论一下页面中的某一个元素它的各种大小和各种位置的计算方式,以便更好的理解. 一.获取元素CSS大小 ...
- xcode7,ios9 部分兼容设置
神奇的苹果公司,再一次让程序员中枪. 一.xcode7 新建的项目,Foundation下默认所有http请求都被改为https请求. HTTP+SSL/TLS+TCP = HTTPS 也就是说,服务 ...
- 使用PostMan进行API自动化测试
最近在进行一个老项目的升级,第一步是先将node版本从4.x升级到8.x,担心升级会出现问题,所以需要将服务的接口进行验证:如果手动输入各种URL,人肉check,一个两个还行,整个服务..大几十个接 ...
- RAID6三块硬盘离线导致的数据丢失恢复过程
小编我最近参与了一例非常成功的数据恢复的案例,在这里分享给大家.用户是一组6块750G磁盘的 RAID6,先后有两块磁盘离线,但维护人员在此情况下依然没有更换磁盘,所以在第三块硬盘离线后raid直接崩 ...
- DES加密实现的思想及代码
感谢: http://blog.csdn.net/yxstars/article/details/38424021 上面的日志非常清晰的写出了这个DES加密的过程,主要存在初始IP置换,然后中间存在8 ...