為搜索引擎設(shè)計一個 key-value 儲存

為搜索引擎設(shè)計一個 key-value 儲存 原文鏈接

1.描述使用場景和約束

使用場景:

  • 用戶請求可以命中緩存或者找不到

假設(shè)和約束:

  • 流量不均衡,存在熱點數(shù)據(jù)
  • 查找速度盡量快
  • 設(shè)計緩存淘汰策略
  • 1億用戶量
  • 平均每月100億次查詢

容量估算:
數(shù)據(jù)結(jié)構(gòu)keyqueryvalueresults:

  • query 50字節(jié)

  • title 20字節(jié)

  • snippet 200字節(jié)
    共計270字節(jié)

  • 如果每次查詢都不重復(fù)欲侮,每月有2.7TB的數(shù)據(jù)

  • 每秒4000次讀請求

2.創(chuàng)建系統(tǒng)設(shè)計圖

系統(tǒng)總體設(shè)計圖

3.設(shè)計關(guān)鍵組件

使用場景:用戶請求已在緩存中
場景緩存可以使用Memcache或者Redis,減少倒掛索引服務(wù)和文檔服務(wù)的讀壓力,在緩存淘汰策略上形帮,可以使用LRU(least recently used)。

  • 查詢請求執(zhí)行以下操作:
    • 解析請求
    • 分詞周叮,詞拼寫糾錯
  • 去cache查詢是否存在符合條件的結(jié)果
    • 如果存在辩撑,更新LRU中的cache位置
      • 返回cache里的結(jié)果
    • 如果不存在:
      • 調(diào)用倒掛索引服務(wù)去獲取符合條件的結(jié)果
      • 去文檔服務(wù)中查詢結(jié)果,獲取標題和摘要
      • 將結(jié)果更新到LRU中

LRU的實現(xiàn)可以借助hash表和雙向鏈表來實現(xiàn):

class Node(object):

    def __init__(self, query, results):
        self.query = query
        self.results = results
class LinkedList(object):

    def __init__(self):
        self.head = None
        self.tail = None

    def move_to_front(self, node):
        ...

    def append_to_front(self, node):
        ...

    def remove_from_tail(self):
        ...

class Cache(object):

    def __init__(self, MAX_SIZE):
        self.MAX_SIZE = MAX_SIZE
        self.size = 0
        self.lookup = {}  # key: query, value: node
        self.linked_list = LinkedList()

    def get(self, query)
        """Get the stored query result from the cache.

        Accessing a node updates its position to the front of the LRU list.
        """
        node = self.lookup[query]
        if node is None:
            return None
        self.linked_list.move_to_front(node)
        return node.results

    def set(self, results, query):
        """Set the result for the given query key in the cache.

        When updating an entry, updates its position to the front of the LRU list.
        If the entry is new and the cache is at capacity, removes the oldest entry
        before the new entry is added.
        """
        node = self.lookup[query]
        if node is not None:
            # Key exists in cache, update the value
            node.results = results
            self.linked_list.move_to_front(node)
        else:
            # Key does not exist in cache
            if self.size == self.MAX_SIZE:
                # Remove the oldest entry from the linked list and lookup
                self.lookup.pop(self.linked_list.tail.query, None)
                self.linked_list.remove_from_tail()
            else:
                self.size += 1
            # Add the new key and value
            new_node = Node(query, results)
            self.linked_list.append_to_front(new_node)
            self.lookup[query] = new_node

查詢服務(wù):

class QueryApi(object):

    def __init__(self, memory_cache, reverse_index_service):
        self.memory_cache = memory_cache
        self.reverse_index_service = reverse_index_service

    def parse_query(self, query):
        """Remove markup, break text into terms, deal with typos,
        normalize capitalization, convert to use boolean operations.
        """
        ...

    def process_query(self, query):
        query = self.parse_query(query)
        results = self.memory_cache.get(query)
        if results is None:
            results = self.reverse_index_service.process_search(query)
            self.memory_cache.set(query, results)
        return results

緩存需要在下面情況下更新:

  • 頁面內(nèi)容發(fā)生變化時
  • 頁面有新增或者刪除時
  • 頁面排序變化時

4.完善設(shè)計

最終設(shè)計圖

關(guān)于分布式緩存仿耽,可以參考Redis Cluster

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末合冀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子项贺,更是在濱河造成了極大的恐慌君躺,老刑警劉巖峭判,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異棕叫,居然都是意外死亡林螃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門俺泣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疗认,“玉大人,你說我怎么就攤上這事伏钠『崧” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵贝润,是天一觀的道長绊茧。 經(jīng)常有香客問我铝宵,道長打掘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任鹏秋,我火速辦了婚禮尊蚁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侣夷。我一直安慰自己横朋,他們只是感情好,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布百拓。 她就那樣靜靜地躺著琴锭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衙传。 梳的紋絲不亂的頭發(fā)上决帖,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音蓖捶,去河邊找鬼地回。 笑死,一個胖子當著我的面吹牛俊鱼,可吹牛的內(nèi)容都是我干的刻像。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼并闲,長吁一口氣:“原來是場噩夢啊……” “哼细睡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帝火,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤溜徙,失蹤者是張志新(化名)和其女友劉穎洒宝,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萌京,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡雁歌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了知残。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靠瞎。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖求妹,靈堂內(nèi)的尸體忽然破棺而出乏盐,到底是詐尸還是另有隱情,我是刑警寧澤制恍,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布父能,位于F島的核電站,受9級特大地震影響净神,放射性物質(zhì)發(fā)生泄漏何吝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一鹃唯、第九天 我趴在偏房一處隱蔽的房頂上張望爱榕。 院中可真熱鬧,春花似錦坡慌、人聲如沸黔酥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跪者。三九已至,卻和暖如春熄求,著一層夾襖步出監(jiān)牢的瞬間渣玲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工抡四, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留柜蜈,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓指巡,卻偏偏與公主長得像淑履,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子藻雪,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容