360海量數據存儲是怎麼做的?超過800+實例穩定運行

NoSQL BigTable Spanner 數據結構 IT168企業級 2017-05-29

360作為一家科技公司,相信大家對於它們的數據存儲都十分好奇?這次我們邀請到了360 web平臺部基礎架構組技術經理陳宗志,和我們一起分享360的數據存儲系統zeppelin的設計與實現。

360海量數據存儲是怎麼做的?超過800+實例穩定運行

陳宗志

陳宗志,360 web平臺部基礎架構組技術經理,主要負責360內部的數據庫和存儲系統 bada、 pika、zeppelin、ceph 等開發和維護。

本次分享主要向大家介紹我們這一年多做的另外一個存儲項目 zeppelin. 各位可能知道我們團隊有bada, Pika. Pika (https://github.com/Qihoo360/pika) 已經開源, 目前應該也有各個大公司使用到他們的線上環境中, 在線上我們有800+ 實例在線上穩定運行。為什麼我們還要開發另一套存儲系統呢?

我一直覺得不同的場景需要有不同的存儲系統去解決, 有在線存儲的需求, 有離線存儲的需求. 因此肯定不是一套存儲系統能夠通吃所有的場景(不過貌似spanner 在做這個事情)。

本次分享將闡述 Zeppelin 系統產生的背景,重點介紹 Zeppelin 系統的整個設計過程,並分享在分佈式系統開發中的一些經驗。通過帶領大家重走 Zeppelin 的設計之路,讓大家瞭解如何設計一個分佈式存儲系統,會遇到哪些問題,有哪些可能的解決思路。

我們公司的github 地址

https://github.com/Qihoo360

我們團隊開發的 pika, pink, zeppelin, floyd 等等代碼都在上面

我們先來談談在線存儲和離線存儲的區別

離線存儲的需求很統一, 就是離線數據分析, 產生報表等等. 也因為這統一的需求, 所以目前hdfs 為首的離線存儲基本統一了離線存儲這個平臺. 離線存儲最重要的就是吞吐, 以及資源的利用率. 對性能, 可靠性的要求其實並不多. (所以這也是為什麼java系在離線存儲這塊基本一統的原因, java提供的大量的基礎庫, 包等等. 而離線存儲又對性能, 可靠性沒有比較高的要求, 因此java GC等問題也不明顯)

所以我們可以看到雖然現在離線的分析工具一直在變, 有hadoop, spark, storm 等等, 但是離線的存儲基本都沒有變化. 還是hdfs 一統這一套. 所以我認為未來離線存儲這塊不會有太大的變化。

360海量數據存儲是怎麼做的?超過800+實例穩定運行

在線存儲

指的是直接面向用戶請求的存儲類型. 由於用戶請求的多樣性, 因此在線存儲通常需要滿足各種不同場景的需求.

比如用戶系統存儲是提供對象的服務, 能夠直接通過HTTP接口來訪問, 那麼自然就誕生了對象存儲s3這樣的服務

比如用戶希望所存儲的數據是關係性數據庫的模型, 能夠以SQL 的形式來訪問, 那麼其實就是mysql, 或者現在比較火熱的NewSql

比如用戶只希望訪問key, value的形式, 那麼我們就可以用最簡單的kv接口, 那麼就有Nosql, bada, cassandra, zeppelin 等等就提供這樣的服務

當然也有多數據結構的請求, hash, list 等等就有了redis, 有POSIX文件系統接口了請求, 那麼就有了cephfs. 有了希望提供跟磁盤一樣的iSCSI 這樣接口的塊設備的需求, 就有了塊存儲, 就是ceph.

從上面可以看到和離線存儲對比, 在線存儲的需求更加的複雜, 從接口類型, 從對訪問延期的需求, 比如對於kv的接口, 我們一般希望是2ms左右, 那麼對於對象存儲的接口我們一般在10ms~20ms. 對於SQL, 我們的容忍度可能更高一些, 可以允許有100 ms. 處理延遲的需求, 我們還會有數據可靠性的不同, 比如一般在SQL 裡面我們一般需要做到強一致. 但是在kv接口裡面我們一般只需要做到最終一致性即可. 同樣對於資源的利用也是不一樣, 如果存儲的是稍微偏冷的數據, 一定是EC編碼, 然後存在大的機械盤. 對於線上比較熱的數據, 延遲要求比較高. 一定是3副本, 存在SSD盤上

從上面可以看到在線存儲的需求多樣性, 並且對服務的可靠性要求各種不一樣, 因此我們很難看到有一個在線存儲能夠統一滿足所有的需求. 這也是為什麼現在沒有一個開源的在線存儲服務能夠像hdfs 那樣的使用率. 因此一定是在不同的場景下面有不同的存儲的解決方案

總結一下在線存儲的要求

360海量數據存儲是怎麼做的?超過800+實例穩定運行

可以看到Facebook infrastructure stack 裡面就包含的各種的在線存儲需求. 裡面包含了熱的大對象存儲Haystack, 一般熱的大對象存儲f4, 圖數據庫Tao. key-value 存儲memcached 集群等等

360海量數據存儲是怎麼做的?超過800+實例穩定運行

對應於google 也會有不同的在線存儲產品. 對應於Google 有MegaStore, Spanner 用於線上的SQL 類型的在線存儲, BigTable 用於類似稀疏map 的key-value存儲等等。

360海量數據存儲是怎麼做的?超過800+實例穩定運行

個人認為對於在線存儲還是比較適合C++來做這一套東西, 因為比較在線存儲一般對性能, 可靠性, 延遲的要求比較高.

那麼這些不同的存儲一般都怎麼實現呢?, 很多在線存儲比如對象存儲的實現一般都是基於底下的key-value進行封裝來實現對象存儲的接口. ceph 就是這方面這個做法的極致.

ceph 底下的rados 本質是一個對象存儲, 這裡的對象存儲跟s3 的對象存儲還不一樣, 只是提供了存儲以為key 對應的value 是對象的形式. 然後基於上層基於librados 封裝了librbd 就實現了塊設備的協議, 那麼就是一個塊存儲. 基於librados 實現了Rados Gateway 提供了s3 的對象存儲的協議就封裝成s3對象存儲. 基於librados 實現了POSIX 文件系統的接口, 就封裝成了分佈式文件系統Ceph FS. (不過我認為ceph 底下的rados實現的還不夠純粹, 因為rados對應的value 是類似於一個對象文件. 比如在基於librados 實現librbd的時候很多對象屬性的一些方法是用不上的)

360海量數據存儲是怎麼做的?超過800+實例穩定運行

同樣google 的F1 是基於spanner 的key-value 接口實現了SQL了接口. 就封裝成了NewSql

因此其實我們也可以這麼說對於這麼多接口的實現, 其實後續都會轉換成基於key-value 接口實現另一種接口的形式, 因為key-value 接口足夠簡單, 有了穩定的key-value 存儲, 只需要在上層提供不同接口轉換成key-value 接口的實現即可. 當然不同的接口實現難度還是不太一樣, 比如實現SQL接口, POSIX文件系統接口, 圖數據庫肯定要比實現一個對象存儲的接口要容易很多

所以**zeppelin 定位的是高可用, 高性能, 可定製一致性的key-value 服務**, 上層可以對接各個協議的實現, 目前zeppelin 已經實現支持key-value 接口, 用於線上搜索系統中. 標準的S3 接口實現, 並且用於公司內部存儲docker 鏡像, 代碼發佈系統等等

這個是目前360 的存儲體系

360海量數據存儲是怎麼做的?超過800+實例穩定運行

講了這麼多我對存儲的瞭解, 我們對zeppelin 的定位. 那麼接下來聊聊zeppelin 具體的實現

360海量數據存儲是怎麼做的?超過800+實例穩定運行

CAP 理論指的是 CAP 並不能同時滿足, 而P 是基本都需要滿足的, 所以基本都是AP, CP. 但是這裡並不是說只能選AP 就沒有C, 而是Consistency 的級別不一樣, 同樣CP 也值得並不是A, 只是A的級別不一樣而已

360海量數據存儲是怎麼做的?超過800+實例穩定運行

數據分佈

  • 均勻性(uniformity)

  • 穩定性(consistency)

所有的分片策略都是在均勻性和穩定性之間的折衷

常見策略

  • 一致性Hash

  • 固定Hash 分片

  • Range Hash

  • crush

zeppelin 的選擇

固定Hash 分片

1. 實現簡單

2. Partition Number > Server Number 可以解決擴展性問題

3. 固定Hash 分片便於運維管理

4. 通過合理設置Hash 函數已經Server 對應的Partition數, 解決均勻性問題

360海量數據存儲是怎麼做的?超過800+實例穩定運行

360海量數據存儲是怎麼做的?超過800+實例穩定運行

有中心節點的設計.

  • 為什麼這麼做?

  • 目前主流的設計一般是兩種

  • Bigtable 為代表的, 有MetaServer, DataServer的設計, MetaServer存儲元數據信息, DataServer存儲實際的數據. 包括 百度的Mola, bigtable, Hbase等等

  • Dynamo 為代表的, 對等結構設計. 每一個節點都是一樣的結構, 每一個節點都保存了數據的元信息以及數據. 包括 cassandra, Riak 等等

zeppelin 的選擇

有中心節點優點是簡單, 清晰, 更新及時, 可擴展性強. 缺點是存在單點故障

無中心節點優點是無單點故障, 水平擴展能力強. 缺點是消息傳播慢, 限制集群規模等等

因為後續我們會考慮支持zeppelin 到千個節點的規模, 因此無中心節點的設計不一定能夠滿足我們後期的擴展性, 所以zeppelin 是有中心節點的設計, 那麼我們就需要做大量的事情去減少對Meta Server 的壓力

zeppelin 選擇有中心節點的設計, 但是我們操作大量的優化去儘可能避免中心節點的壓力, 同時通過一致性協議來保證元數據更新的強一致

1. Client 緩存大量元信息, 只有Client 出錯是才有訪問Meta Server

2. 以節點為維度的心跳設計

副本策略

1. Master - Slave

以MongoDB, redis-cluster, bada 為主的, 有主從結構的設計, 那麼讀寫的時候, 客戶端訪問的都是主副本, 通過binlog/oplog 來將數據同步給從副本

2. Quorum(W+R>N)

以cassandra, dynamo 為主的, 沒有主從結構的設計, 讀寫的時候滿足quorum W + R > N, 因此寫入的時候寫入2個副本成功才能返回. 讀的時候需要讀副本然後返回最新的. 這裡的最新可以是時間戳或者邏輯時間

3. EC (erasure code)

EC 其實是一個CPU 換存儲的策略, ec 編碼主要用於保存偏冷數據, 可以以減少的副本數實現和3副本一樣的可用性. ec編碼遇到的問題是如果某一個副本掛掉以後, 想要恢復副本的過程必須與其他多個節點進行通信來恢復數據, 會照成大量的網絡開銷.

zeppelin 的選擇

目前zeppelin 只實現的Master-Slave 策略, 後續會根據業務場景, 存儲成本的需求實現EC, Quorum.

存儲引擎

360海量數據存儲是怎麼做的?超過800+實例穩定運行

360海量數據存儲是怎麼做的?超過800+實例穩定運行

360海量數據存儲是怎麼做的?超過800+實例穩定運行

360海量數據存儲是怎麼做的?超過800+實例穩定運行

Manos Athanassoulis [**Designing Access Methods: The RUM Conjecture**](http://101.96.8.165/stratos.seas.harvard.edu/files/stratos/files/rum.pdf)

RUM 是 寫放大, 讀放大, 空間放大 之前的權衡

寫放大: 寫入引擎的數據和實際存儲的數據大小比

讀放大: 讀放大是一次讀取需要的IO 次數大小比

空間放大: 實際的數據總量和引擎中存儲的數據總量關係大小比

360海量數據存儲是怎麼做的?超過800+實例穩定運行

當然這裡主要根據DAM 模型(disk access model), 得出結論

當然這裡並沒有考慮 LSM Tree 裡面場景的 bloom filter 等等

這裡B+ tree 主要用在 數據庫相關, 支持範圍查找的操作, 因為B+ Tree 在底下有序數據是連續的

zeppelin 的選擇

zeppelin 目前使用的是改過的rocksdb, nemo-rocksdb. nemo-rocksdb 支持TTL, 支持後臺定期compaction 等等功能

https://github.com/Qihoo360/nemo-rocksdb

一致性協議

floyd 是c++ 實現的raft 協議, 元信息模塊的管理主要通過floyd 來維護.

360海量數據存儲是怎麼做的?超過800+實例穩定運行

1. 關於paxos, multi-paxos 的關係

其實paxos 是關於對某一個問題達成一致的一個協議. paxos make simple 花大部分的時間解釋的就是這個一個提案的問題, 然後在結尾的Implementing a State Machine 的章節介紹了我們大部分的應用場景是對一堆連續的問題達成一致, 所以最簡單的方法就是實現每一個問題獨立運行一個Paxos 的過程, 但是這樣每一個問題都需要Prepare, Accept 兩個階段才能夠完成. 所以我們能不能把這個過程給減少. 那麼可以想到的解決方案就是把Prepare 減少, 那麼就引入了leader, 引入了leader 就必然有選leader 的過程. 才有了後續的事情, 這裡可以看出其實lamport 對multi-paxos 的具體實現其實是並沒有細節的指定的, 只是簡單提了一下. 所以才有各種不同的multi-paxos 的實現

那麼paxos make live 這個文章裡面主要講的是如何使用multi paxos 實現chubby 的過程, 以及實現過程中需要解決的問題, 比如需要解決磁盤衝突, 如何優化讀請求, 引入了Epoch number等, 可以看成是對實現multi-paxos 的實踐

2. 關於 multi-paxos 和 raft 的關係

從上面可以看出其實我們對比的時候不應該拿paxos 和 raft 對比, 因為paxos 是對於一個問題達成一致的協議, 而raft 本身是對一堆連續的問題達成一致的協議. 所以應該比較的是multi-paxos 和raft

那麼multi-paxos 和 raft 的關係是什麼呢?

raft 是基於對multi paxos 的兩個限制形成的

* 發送的請求的是連續的, 也就是說raft 的append 操作必須是連續的. 而paxos 可以併發的. (其實這裡併發只是append log 的併發提高, 應用的state machine 還是必須是有序的)

* 選主是有限制的, 必須有最新, 最全的日誌節點才可以當選. 而multi-paxos 是隨意的 所以raft 可以看成是簡化版本的multi paxos(這裡multi-paxos 因為允許併發的寫log, 因此不存在一個最新, 最全的日誌節點, 因此只能這麼做. 這樣帶來的麻煩就是選主以後, 需要將主裡面沒有的log 給補全, 並執行commit 過程)

基於這兩個限制, 因此raft 的實現可以更簡單, 但是multi-paxos 的併發度理論上是更高的.

可以對比一下multi-paxos 和 raft 可能出現的日誌

multi-paxos

360海量數據存儲是怎麼做的?超過800+實例穩定運行

raft

360海量數據存儲是怎麼做的?超過800+實例穩定運行

可以看出, raft 裡面follower 的log 一定是leader log 的子集, 而multi-paxos 不做這個保證

3. 關於paxos, multi-paxos, raft 的關係

所以我覺得multi-paxos, raft 都是對一堆連續的問題達成一致的協議, 而paxos 是對一個問題達成一致的協議, 因此multi-paxos, raft 其實都是為了簡化paxos 在多個問題上面達成一致的需要的兩個階段, 因此都簡化了prepare 階段, 提出了通過有leader 來簡化這個過程. multi-paxos, raft 只是簡化不一樣, raft 讓用戶的log 必須是有序, 選主必須是有日誌最全的節點, 而multi-paxos 沒有這些限制. 因此raft 的實現會更簡單.

因此從這個角度來看, Diego Ongaro 實現raft 這個論文實現的初衷應該是達到了, 讓大家更容易理解這個paxos 這個東西

zeppelin 的選擇

zeppelin MetaServer 一致性是由自己實現的raft 庫floyd 來保證. 寫入和讀取可以通過raft 協議實現強一致, 同時為了性能考慮我們在讀取的時候還提供DirtyRead 的接口, floyd 已經在github上面開源, 是用c++實現的raft 協議, 實現的非常的簡潔明瞭。https://github.com/Qihoo360/floyd

floyd 的壓測報告

https://github.com/Qihoo360/floyd/wiki/5-性能測試

整體實現

360海量數據存儲是怎麼做的?超過800+實例穩定運行

Meta Server 總體結構

360海量數據存儲是怎麼做的?超過800+實例穩定運行

2. Data Server 總體結構

360海量數據存儲是怎麼做的?超過800+實例穩定運行

Zeppelin自上而下的層次如圖所示。

- Network Proxy:負責網絡的壓包解包,採用Protobuf協議通Meta Server, Client, 及其他Node Server進行交互;

- Zeppelin Process:Zeppline主要邏輯處理層,包括分表分片,數據同步,命令處理等;

- Binlog:操作日誌,同時是同步模塊的數據來源;

- 存儲層:採用Rocksdb進行數據存儲。

3. 線程模型

360海量數據存儲是怎麼做的?超過800+實例穩定運行

Zeppelin採用多線程的方式進行工作,Zeppline中的所有線程都是與Node綁定的,不會隨著Table或Partiiton的個數增加而增加。根據不同線程的任務及交互對象將線程分為三大類:

1,元信息線程,包括Heartbeat Thread及MetaCmd Thread

- Heartbeat Thread:負責與Meta Server保持的心跳連接,並通過PING信息感知Meta Server元信息的更新;

- MetaCmd Thread:Heartbeat Thread感知到元信息的更新後由MetaCmd Thread從Meta Server獲取最新的元信息。通過元信息中的副本信息,MetaCmd Thread會負責修改和維護改Node Server與其他Node Server的Peer關係;

2,用戶命令線程,包括Dispatch Thread及Worker Thread

- Dispatch Thread:接受用的鏈接請求並將客戶端鏈接交個某個Worker Thread線程處理;

- Worker Thread:處理用戶請求,寫命令會先寫Binlog,之後訪問DB完成用戶命令的執行。

3, 同步線程,包括服務於副本間數據同步功能的多個線程

- TrySync Thread: 負責發起主從同步請求。MetaCmd Thread修改副本狀態後,TrySync Thread會一次對當前Node Server負責的所有需要建立主從關係的Partition的主Partition發送Sync命令,該Sync命令會首先獲取本地的binlog位置作為當前主從同步的同步點;

- Binlog Sender Thread:Partition的不同副本之間建立主從關係後會由Binlog Sender Thread讀取並向從Parition的Binlog Receiver Thread 發送binlog項。這個過程通用戶命令的執行異步進行,所以從的Partition可能會落後於主。同一個Sender會負責多個Partition;

- Binlog Receiver Thread:接受Binlog Sender Thread發來的Binlog項,寫Binlog並將寫DB的操作分發給不同的Binlog BgWorker;

- Binlog Receive BgWorker:接受Binlog Receiver Thread發來的請求,寫DB完成操作。

360海量數據存儲是怎麼做的?超過800+實例穩定運行

4,後臺工作線程,包括BGSave and DBSync Thread,Binlog Purge Thread

- Binlog Purge Thread:為了減少對磁盤空間的佔用,Binlog Purge Thread會定期刪除過期的Binlog

- BGSave and DBSync Thread:建立主從關係時,如果主Partition發現同步點已經落後於當前保留的最早的binlog,則需要進行全量同步。該線程會首先將整個數據內容dump一份併發送給對應從Partition。全同步過程利用Rsync實現。

4. 客戶端請求

客戶端需要訪問針對某個業務Table進行操作時,會先向Meta Server請求改Table的元信息。之後每個訪問請求,都會根據key計算出其所屬於的Partition,通過元信息計算器Master所在的Node Server。直接請求改Node Server。

360海量數據存儲是怎麼做的?超過800+實例穩定運行

5. 故障檢測及處理

Node Server定期向Meta Server發送PING消息,當節點宕機或者網絡中斷髮生時。Meta Server會感知並修改其維護的元信息,並將元信息Epoch加一。元信息修改後,其他Node Server會從PING消息的回覆中獲得新Epoch,由於與本地記錄不同,Node Server會由MetaCmd Thread向Meta Server 發送PULL消息主動拉去最新元信息。

元信息中記錄各個Table中每個Partition所負責的Master Node Server及兩個Slave Node Server。Node Server獲得最新的元信息,並根據該信息修改自己維護的Partitions的主從角色,建立主從關係,提供服務。

本文整理自【微學堂】第二十八期活動實錄。

相關推薦

推薦中...