首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

FriendFeed 架構揭秘:在 MySQL 上構建無模式存儲系統,實現靈活 Schema 與高性能索引

     編輯:MySQL 綜合教學

FriendFeed 架構揭秘:在 MySQL 上構建無模式存儲系統,實現靈活 Schema 與高性能索引

FaceBook 收購前的 FriendFeed 面對數億條數據、頻繁 Schema 變更、MySQL DDL 鎖表數小時的痛點,創造性地在 MySQL InnoDB 之上構建了一套**無模式存儲層**:實體序列化為 Blob 存儲,索引獨立建表維護,實現「加字段不鎖表、加索引不阻塞寫入、清理工異步回填」。本文深度解析其核心設計、一致性模型、清理機制與性能實測。

一、 背景痛點:傳統 RDBMS 的擴展性困境

  • DDL 鎖表:>1000 萬行表加字段/索引鎖表數小時,阻塞所有讀寫。
  • 索引膨脹:舊索引不刪除佔用內存、拖慢 INSERT;刪除同樣鎖表。
  • 主從切換複雜:從庫加索引再切主風險大、易出錯。
  • 關聯查詢失效:分片後 JOIN 不可用,外鍵約束成累贅。

**決策**:不遷移陌生 NoSQL (CouchDB 等穩定性未驗證),而是**在熟悉、穩定的 MySQL 上實現文檔存儲語義**。

二、 核心數據模型:Entity + 獨立索引表

1. 實體表

CREATE TABLE entities (
    added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,  -- 物理順序寫入優化
    id BINARY(16) NOT NULL,                            -- UUID 16 字節
    updated TIMESTAMP NOT NULL,
    body MEDIUMBLOB,                                   -- zlib 壓縮後的 Python dict/JSON
    UNIQUE KEY (id),
    KEY (updated)
) ENGINE=InnoDB;
  • added_id 自增主鍵保證新實體順序寫入磁盤末尾,熱數據聚集,利於分區讀取。
  • body 存儲完整屬性集 (如 user_id, feed_id, title, link, published...),**對資料庫不透明**,應用層反序列化後解析。

2. 索引表 (按查詢模式按需創建)

-- 按 user_id 查詢用戶所有實體
CREATE TABLE index_user_id (
    user_id BINARY(16) NOT NULL,
    entity_id BINARY(16) NOT NULL UNIQUE,
    PRIMARY KEY (user_id, entity_id)  -- 覆蓋索引,避免回表
) ENGINE=InnoDB;

-- 按 link 精確查找
CREATE TABLE index_link (
    link VARCHAR(735) NOT NULL,       -- 767 字節限制 / utf8mb4
    entity_id BINARY(16) NOT NULL UNIQUE,
    PRIMARY KEY (link, entity_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 每個查詢維度一張索引表,**單列/複合主鍵覆蓋查詢**,無需回表。
  • 分片鍵 shard_on 決定索引落在哪個分片 (如 user_id % num_shards)。

三、 寫入流程:最終一致性 + 異步索引維護

  1. 寫入實體表:InnoDB ACID 事務保證 entities 寫入成功 (規范數據源)。
  2. 異步寫入索引表:應用代碼遍歷索引列表,向各分片寫入索引行 (非事務性,允許短暫不一致)。

一致性妥協:

  • 實體表為**唯一真相源**;索引表可能遲滯、缺失、殘留舊值。
  • 讀取時:**索引表僅用於定位 entity_id**,隨後回 entities 讀取完整實體,**在應用層用真實屬性過濾驗證**,過濾掉索引不準導致的假陽性。

四、 清理工 : 索引最終一致性的守護者

後台常駐進程 rundatastorecleaner.py --index=index_link 持續運行:

  • 優先處理近期更新實體:按 updated 倒序掃描 entities,對比索引表,補寫缺失、刪除殘留。
  • 秒級收斂:實際生產中索引不一致窗口僅幾秒。
  • 新索引回填**:創建新索引表後啟動清理工異步回填歷史數據,**不影響線上讀寫**,無需主從切換、無鎖表風險。

五、 讀取流程:索引定位 + 實體回表 + 應用層過濾

# Python 偽碼
def get_by_user_id(user_id):
    entity_ids = []  # 從所有分片 index_user_id 查詢
    for shard in shards:
        ids = shard.query("SELECT entity_id FROM index_user_id WHERE user_id=%s", user_id)
        entity_ids.extend(ids)
    
    entities = []  # 批量回 entities 表
    for shard in shards:
        ents = shard.query("SELECT body FROM entities WHERE id IN (%s)", entity_ids)
        entities.extend(ents)
    
    # 應用層二次過濾 (防禦索引髒數據)
    return [e for e in entities if e['user_id'] == user_id]

六、 性能實測:延遲大幅下降且穩定

  • 部署前:頁面加載延遲波動大,高峰期明顯抖動。
  • 部署後:延遲顯著下降並極度平穩 (見原文圖表),高峰期無抖動。
  • 核心優勢:順序寫入 (added_id) + 覆蓋索引 + 無鎖 Schema 變更 = 寫入吞吐線性擴展,讀取延遲可控。

七、 架構總結與啟示

八、 適用場景與避坑指南

適合:

  • 屬性頻繁變化、半結構化數據 (Feed、日志、IoT 遙測、用戶畫像)。
  • 寫入量大、查詢模式相對固定 (可預測索引)。
  • 團隊熟悉 MySQL 運維、不願引入新存儲組件。

不適合:

  • 複雜多表關聯、事務強一致場景 (訂單、財務)。
  • 即席分析查詢 (需 SQL 聚合、JOIN)。
  • 團隊缺乏應用層數據一致性處理能力。

九、 總結

FriendFeed 證明了:**在成熟關係型資料庫之上構建文檔存儲抽象層,是「不遷移數據、不換技術棧」前提下實現 Schema 靈活與高性能的可行路徑**。其核心思想——**實體 Blob 化、索引表獨立、異步最終一致、清理工收斂**——已被眾多大廠 (如 Instagram、Pinterest 早期) 借鑑,成為「MySQL 做 NoSQL」的經典範式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
維度傳統 RDBMSFriendFeed 無模式層
Schema 變更DDL 鎖表、風險高應用層新增屬性即可、零停機
索引管理同表、加鎖、回填慢獨立表、異步建、清理工回填
一致性模型強一致 (ACID)最終一致 (實體強、索引弱)
查詢方式SQL JOIN、WHERE索引定位 ID → 回實體 → 應用過濾
技術棧純 SQLMySQL + Python 應用層邏輯