[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:

  1. 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 containing 0-9a-zA-Z.
  2. 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:

    1. How many unique identifiers possible? Will you run out of unique URLs?
    2. Should the identifier be increment or not? Which is easier to design? Pros and cons?
    3. Mapping an identifier to an URL and its reversal - Does this problem ring a bell to you?
    4. How do you store the URLs? Does a simple flat file database work?
    5. What is the bottleneck of the system? Is it read-heavy or write-heavy?
    6. Estimate the maximum number of URLs a single machine can store.
    7. Estimate the maximum number of queries per second (QPS) for decoding a shortened URL in a single machine.
    8. 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.
    9. How could you handle redundancy? i,e, if a server is down, how could you ensure the service is still operational?
    10. Keep URLs forever or prune, pros/cons? How we do pruning? (Contributed by @alex_svetkin)
    11. What API would you provide to a third-party developer? (Contributed by @alex_svetkin)
    12. 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 = 1;
        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 = 0;
        for (int i = 0; i < s.size(); ++i) {
            n = n * 62 + convert(s[i]);
        }
        return n;
    }

    int convert(char c) {
        if (c >= '0' && c <= '9') {
            return c - '0';
        } else if (c >= 'a' && c <= 'z') {
            return c - 'a' + 10;
        } else if (c >= 'A' && c <= 'Z') {
            return c - 'A' + 36;
        }
        return -1;
    }

    string base10ToBase62(int n) {
        string str = "";
        while (n != 0) {
            str.insert(str.begin(), elements[n % 62]);
            n /= 62;
        }
        while (str.size() != 6) {
            str.insert(str.begin(), '0');
        }
        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 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM