[System Design] TinyURL 設計短網址系統

Design TinyURL 設計短網址系統

九章系統設計 TinyURL筆記
SN-提問:qps+容量
AK-畫圖:流程,可行解
E-優化:流量/存儲mysql

S場景:

長到短
短到長程序員

N需求(不肯定的話先往小裏說):

qps

日活用戶:100M
每人天天使用:(寫)長到短0.1,(讀)短到長1
日request:寫10M,讀100M
qps:寫100,讀1K
peak qps: 寫200,讀2K
(千級別的qps能夠單臺SSD MySQL搞定)web

存儲

天天新產生10M個長連接<->短連接映射
每一個映射(長連接+短連接)平均大小100B
天天1GB,1T的硬盤能夠用3年
存儲對於這種系統不是問題,netflix這種纔有存儲上的問題面試

經過SN的分析,能夠知道要作多大致量的系統,能夠感受到這個系統不是很難,單臺電腦配個ssd硬盤,用mysql就能夠搞定
下面開始AK細化流程,畫圖,給出可行解算法

A服務與接口:

就一個service,URLService.sql

Core(Business Logic)層:
類:URLService
接口:數據庫

  • URLService.encode(String long_url)api

  • URLService.decode(String short_url)瀏覽器

Web層:
REST API:緩存

GET: /{short_url}
返回一個http redirect response(301)

POST
goo.gl作法: POST: https://goo.gl/api/shorten
bit.ly作法: POST: https://bitly.com/data/shorten
Request Body: {url=long_url}
返回OK(200),data裏帶着生成的short_url

K數據存取

第一步:select 選存儲結構 -> 內存 or 文件系統 or 數據庫 -> SQL or NoSQL?
第二步:schema 細化數據表

第一步:選存儲結構

SQL vs NoSQL?

  1. 是否須要支持Transaction?
    NoSQL不支持Transaction.

  2. 是否須要豐富的SQL Query?
    NoSQL的SQL Query豐富度不如SQL,不過目前差距正在縮小.

  3. 是否追求效率(想偷懶)?
    大多數Web Framework與SQL數據庫兼容得很好(自帶ORM),意味着能夠少些不少代碼.

  4. 是否須要AUTO_INCREMENT ID?
    NoSQL作不到1,2,3,4,5...NoSQL只能作到一個全局unique的Object_id.

  5. 對QPS要求高不高?
    NoSQL性能高,好比Memcached的qps能夠到million級別,MondoDB能夠到10k級別,MySQL只能在K這個級別.

  6. 對Scalability的要求有多高?
    SQL須要程序員本身寫代碼來scale; NoSQL這些都是自帶的(sharding,replica).

Mark一下,寫到這的時候,收到hr的郵件,因爲uberChina和滴滴出行合併,面試推遲,系統設計的學習也要告一段落了,很少說。。

綜上:
transaction? 不須要 -> nosql
sql query? 不須要 -> nosql
是否追求效率? 原本也沒多少代碼 -> nosql
對qps要求高? 讀2k,寫200,真心不高 -> sql
對scalability要求高? 存儲和QPS要求都不高,單機就能夠了 -> sql
要auto_increment_id? 咱們的算法要! -> sql

ok,那麼來聊一聊系統的算法是什麼,有以下方案:
1.hash function:
把long_url用md5/sha1哈希
md5把一個string轉化成128位二進制數,通常用32位十六進制數(16byte)表示:
http://site.douban.com/chuan -> c93a360dc7f3eb093ab6e304db516653
sha1把一個string轉化成160位二進制數,通常用40位十六進制數(20byte)表示:
http://site.douban.com/chuan -> dff85871a72c73c3eae09e39ffe97aea63047094
這兩個算法能夠保證哈希值分佈很隨機,可是衝突是不可避免的,任何一個哈希算法都不可避免有衝突。
優勢:簡單,能夠根據long_url直接生成;假設一個url中一個char佔兩個字節,平均長度爲30的話,原url佔大小60byte,hash以後要16byte。咱們能夠取md5的前6位,這樣就更節省。
缺點:難以保證哈希算法沒有衝突
解決衝突方案:1.拿(long_url + timestamp)來哈希;2.衝突的話,重試(timestamp會變,會生成新的hash)
綜上,流量很少時,可行;可是,當url超過了假設1 billion的時候,衝突會很是多,效率很是低。

2.base62:
將六位的short_url看作是一個62進制數(0-9,a-z,A-Z),能夠表示62^6=570億個數。整個互聯網的網頁數在trillion級別,即一萬億這個級別。6位足夠。
每一個short_url對應一個十進制整數,這個整數就能夠是sql數據庫中的自增id,即auto_increment_id。

public class URLService {
    HashMap<String, Integer> ltos;
    HashMap<Integer, String> stol;
    static int COUNTER;
    String elements;
    URLService() {
        ltos = new HashMap<String, Integer>();
        stol = new HashMap<Integer, String>();
        COUNTER = 1;
        elements = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    }
    public String longToShort(String url) {
        String shorturl = base10ToBase62(COUNTER);
        ltos.put(url, COUNTER);
        stol.put(COUNTER, url);
        COUNTER++;
        return "http://tiny.url/" + shorturl;
    }
    public String shortToLong(String url) {
        url = url.substring("http://tiny.url/".length());
        int n = base62ToBase10(url);
        return stol.get(n);
    }
    
    public int base62ToBase10(String s) {
        int n = 0;
        for (int i = 0; i < s.length(); i++) {
            n = n * 62 + convert(s.charAt(i));
        }
        return n;
        
    }
    public int convert(char c) {
        if (c >= '0' && c <= '9')
            return c - '0';
        if (c >= 'a' && c <= 'z') {
            return c - 'a' + 10;
        }
        if (c >= 'A' && c <= 'Z') {
            return c - 'A' + 36;
        }
        return -1;
    }
    public String base10ToBase62(int n) {
        StringBuilder sb = new StringBuilder();
        while (n != 0) {
            sb.insert(0, elements.charAt(n % 62));
            n /= 62;
        }
        while (sb.length() != 6) {
            sb.insert(0, '0');
        }
        return sb.toString();
    }
}

第二步:schema 細化數據表

一個表:兩列(id,long_url),其中id爲主鍵(自帶index),long_url將其index,這樣一張表能夠雙向查!

到如今有了work solution!能夠達到weak hire。
基本的系統架構是:
瀏覽器 <-> Web <-> Core <-> DB

E優化

如何提升響應速度?

提升web server和數據庫之間的響應速度

讀:利用Memcached提升響應速度,get的時候先去cache找,沒有就從數據庫裏找;能夠把90%的讀請求都引流到cache上

提升web server和用戶瀏覽器之間的響應速度(利用地理位置信息提速)

不一樣地區,使用不一樣的Web服務器和緩存服務器,全部地區share一個db,用於緩存沒hit的狀況
經過動態DNS解析能夠把不一樣地區的用戶match到最近的Web服務器

假如一臺MySQL 存不下/忙不過 了怎麼辦?

面臨問題:
Cache資源不夠
寫操做愈來愈多
愈來愈多的cache miss率
怎麼作:
拆數據庫。
拆數據庫有兩種,一種是把不一樣的表放到不一樣的機器(vertical sharding),另外一種是把數據散列到不一樣的機器(horizontal)。
最好用的是horizontal sharding。
當前的表結構是:(id, long_url),既須要用id查long_url,也須要用long_url查id,如何分,把哪列做爲sharding key呢?
一個簡單可行的辦法是,按id取模sharding,由於讀(短到長)的需求是主要的;寫的時候就廣播給全部機器,因爲機器不會太多,也是可行的。
此時一個新的問題來了,n臺機器如何共享一個全局自增id?
兩個辦法:開一臺新的機器專門維護這個全局自增id,或者用zookeeper。都很差。
因此咱們不用全局自增id。
業內的作法是,把sharding key做爲第一位直接放到short_url裏。這樣就不須要全局自增id,每臺機器自增就行了。
用consistent hashing將環分爲62份(這個無所謂,由於預估機器不會超過這個數目,也能夠設成360或者別的數,每次新加一個機器能夠把區間最大的分一半)每一個機器在環上負責一段區間。
具體作法:
新來一個long_url -> hash(long_url)%62 -> 把long_url放到hash value對應的機器裏 -> 在這臺機器上生成short_url -> 返回short_url
來一個short_url請求 -> 提取short_url的第一位獲得sharding key -> 到sharding key對應的機器裏找 -> 返回long_url
新增一臺機器 -> 找原來機器裏負責range(0-61)最大的機器 -> 將其range減半 -> 把一半放到新增機器上

繼續優化?

中國的db放到中國,美國的db放到美國。
用地域信息做爲sharding key,好比中國網站都放到0開頭的,美國網站都放在1開頭的。

加一個新功能custom url怎麼作?

單獨建一張表,存custom_url <--> long_url當查詢時,先從custom表裏查,再從url表裏查。注意,千萬不要在url表裏插一列custom,這樣這列大部分的值爲空。

相關文章
相關標籤/搜索