[轉載] 360分佈式存儲系統Bada的設計和應用

原文: http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=208931479&idx=1&sn=1dc6ea4fa28a3fb527a6204a9a5c23b1&key=c76941211a49ab5849fe180925fd9816350457f931e54a80feca07c081bffea5828ae0bbb2b1f7be41501db7dea48977&ascene=0&uin=Mjk1ODMyNTYyMg%3D%3D&devicetype=iMac+MacBookPro9%2C2+OSX+OSX+10.10.3+build(14D136)&version=11020012&pass_ticket=XBh3IrGvu4uh9iuU9u54bHyVlV1hde36lZbrQOc%2F4ICtKWbJji6PTss8d%2FMelkMW前端

此文根據【QCON高可用架構羣】分享內容,由羣內【編輯組】志願整理,轉發請註明來自「高可用架構(ArchNotes)」微信公衆號。git

陳宗志:奇虎360基礎架構組 高級存儲研發工程師,目前負責360分佈式存儲系統Bada的設計和實現,同時負責360虛擬化相關技術的研究。github


 

本次分享主題

主要向你們介紹一下360自主研發的分佈式存儲系統Nosql-Bada,做爲設計者我一直以爲設計過程就是在作一些折衷,因此大部分的內容是咱們開發實現Bada過程當中的一些經驗和坑, 也有不少的權衡, 但願和你們一塊兒分享, 有不對的地方歡迎指出。redis

 

雖然項目目前還未開源, 可是咱們的一些組件, 用於異步同步數據的Mario庫等, 均已經開源,後續Bada也會開源。這是360官方的Github帳號https://github.com/Qihoo360sql


主要應用場景

咱們的定位是海量數據的持久化存儲, 爲線上的熱門應用服務。不過咱們目前沒有接入跟錢相關的業務, 由於咱們的系統畢竟是最終一致性的系統。數據庫

 

咱們傾向使用Bada的用戶數據value的大小在10k之內, 那麼咱們的延遲可以作到1ms左右。咱們爲了讀取性能有必定的優點, 通常要求機器都掛載SSD盤。若是用於存儲冷數據, 咱們會建議用戶存數據到公司的其餘存儲產品, 好比hbase,cassandra等等。後端

 

目前公司內部雲盤, 移動搜索, LBS, Onebox, 導航影視, 白名單等多個業務均在使用。微信

 

雲盤的場景是:全部上傳, 下載文件的時候經過Bada查詢文件所在的集羣。這個業務數據量大概600億, 其中某一個機房的訪問量15億。網絡

 

 

LBS這個業務是將全部的POI的信息存儲在Bada中, 業務須要在5個機房進行數據同步。天天的請求量大概7億。數據結構

 

 

總體架構

Bada SDK 是咱們提供給用戶SDK, 360 QConf 配置管理服務 你們以前也瞭解過, 咱們是QConf的重度用戶。用戶經過SDK從QConf中得到存活的Bada節點, 而後進行訪問。

 

Data Server是咱們的服務節點,其設計是學習自Amazon Dynamo(不過好像Dynamo 自己也被不少人噴), 每個節點都是對等結構, 每個節點存儲了全部的元信息。爲何這麼作?

 

目前主流的設計通常是兩種:

  • BigTable 爲表明的, 有MetaServer, DataServer的設計, MetaServer存儲元數據信息, DataServer存儲實際的數據。包括 BigTable, HBase, 百度的Mola等等。

  • Dynamo 爲表明的, 對等結構設計. 每個節點都是同樣的結構, 每個節點都保存了數據的元信息以及數據. 包括 Cassandra, Riak 等等。

     

Bada 的選擇

其實我以爲兩個結構都是合適的。爲了部署, 擴展等方便,咱們不但願部署的時候須要分開部署Meta節點, Data節點。計算機行業, 加一層能夠解決大部分問題, 所以咱們以爲對等網絡的設計更有挑戰性。我的觀點, 在數據量更大的狀況下, Meta 節點極有可能成爲瓶頸。固然Dynamo的結構確定也有自身的缺點, 好比如何保證元數據的一致性等問題。

 

Data Server主要模塊

  • Network Proxy: 用於接收客戶端的請求, 咱們的協議是定製的protobuf 協議, Network Proxy模塊負責解析協議, 而後請求轉發到對應的節點

  • Meta Info: 用於存儲公共的元信息, 元信息包括每個分片存儲在哪一個節點

  • DB Engine: 咱們底下的引擎是基於LevelDB的定製化開發, 包括支持cas, 過時時間, 多數據結構等等

     

數據分佈策略

能夠看到咱們目前使用的是有主從的副本策略, 圖中的Primary 是主節點, Secondary 是從節點。爲何這麼作?

 

首先爲何不使用ec編碼(erasure code 糾刪碼), 由於ec編碼主要用於保存偏冷數據, ec編碼遇到的問題是若是某一個副本掛掉之後, 想要恢復副本的過程必須與其餘多個節點進行通訊來恢復數據, 會照成大量的網絡開銷. 所以這裏3副本更合適。

 

常見的分佈式系統的多副本策略主要分紅兩類:

  • 以Cassandra, Dynamo 爲主的, 沒有主從結構的設計, 讀寫的時候知足quorum W + R > N, 所以寫入的時候寫入2個副本成功才能返回。讀的時候須要讀副本而後返回最新的。這裏的最新能夠是時間戳或者邏輯時間。

  • 以MongoDB, Bada爲主的, 有主從結構的設計, 那麼讀寫的時候, 客戶端訪問的都是主副本, 經過binlog/oplog 來將數據同步給從副本。

     

兩種設計都只能知足最終一致性。那麼咱們再從CAP理論上看, 那麼都是在哪些維度作了權衡?

  • 從性能上來看,有主從的設計很明顯性能會因爲無主從的, 由於有主從的設計只須要訪問一個副本就能夠返回, 而無主從的至少兩個副本返回才能夠。

  • 從一致性來看,有主從的設計若是掛掉一個節點, 若是這個節點是主, 那麼就會形成因爲數據同步的不及時, 這段時間寫入的數據丟。若是掛掉的是從節點, 那麼則對數據沒有任何的影響。只要這個節點在接下來的時間內可以起來便可。無主從的設計若是掛掉一個節點, 理論上對結果是無影響的, 由於返回的時候會比較最新的結果。有主從的結構因爲寫入都在一個節點, 所以不存在衝突。而無主從的結構因爲寫入的是任意的兩個副本, 會存在對同一個key的修改在不一樣的副本, 致使客戶端讀取的時候是兩個不一致的版本, 這個時候就須要去解決衝突, 常見的方案就涉及到vector clock, 時間戳等等。不過, 整體來看無主從的設計一致性應該優於有主從的設計。

  • 從分區容錯來看, 兩邊都必須有一半以上的節點存活纔可以對外提供服務, 由於有主從的設計中必須得到超過一半節點的投票才能成爲主節點。而無主從的結構, 常見在W = 2, R = 2的狀況下, 必須2個副本以上才能對外提供服務。

  • 從可靠性來看,有主從的設計由於只訪問一個副本, 性能優於無主從的設計。並且無主從的設計中, 由於對單條數據必須有兩次讀取, 所以對系統的訪問壓力也會比無主從的來的多。固然有主從的設計容易形成主落在同一個機器上, 形成負載不均的狀況, 可是這裏只要將主平均到全部的機器, 就能夠解決這個問題。可是有主從的設計在切換主從的時候, 必然有一段時間沒法對外提供服務, 而無主從的設計則不存在這樣的問題。整體來講, 筆者認爲從可靠性的角度來講, 有主從的設計應該比無主歷來的可靠。

     

咱們使用的是有主從結構的設計, 緣由:

  • Bada主要的應用場景對性能的要求比較高, 大部分的請求須要在1ms左右的時間返回, 所以有主從的設計, 性能更知足需求

  • 線上服務的可靠性是咱們另一個考慮的因素

  • 具體的分析過程能夠看 http://baotiao.github.io/2015/03/Bada-design-replicaset/

     

數據分片策略,咱們叫兩次映射.

  • key -> PartitionId(hash)

  • PartitionId -> Node(MetaData)

好比上面這張圖中咱們能夠看出, 咱們將全部數據分紅10個Partition, 而後每個機器存有主節點和從節點. 咱們會盡量的保證每個機器上面的主節點是同樣多的, 這樣可以作到每個節點的負載都是均衡的。

 

請求流程

  • 當請求的數據Primary正好是當前這個節點

  • 當請求的數據Primary 不是當前節點

 

多機房架構

360的機房是比較多的, 並且某些機房之間的網絡較差。業務部署一個服務的時候, 後端的DB也須要部署在多個機房上, 所以這個經常是業務的痛點。所以咱們設計之初就考慮多機房的架構。

 

咱們的多機房架構能保證

  • 用戶不用管理多個機房, 任意一個機房數據寫入, 其餘機房可以讀取

  • 在機房存在問題的時候, 咱們能夠馬上切換機房的流量

  • 提供每個機房之間數據的統計和Check

     

總體實現

這個是目前LBS業務的場景

能夠看出咱們這裏有一個專門的隊列用於同步機房之間的數據。這個QBus 是咱們團隊內部基於kafka開發的消息隊列服務。

 

目前主流的機房同步方法也是兩種:

  • 節點負責機房數據的同步, 好比Cassandra, CouchBase, Riak

  • 由外部的隊列來同步機房之間的數據, 好比 Yahoo pnuts

     

Cassandra 作法

在寫入的時候, 每個機房的協調者。好比這個圖裏面10這個節點。會把寫入發送給其它機房的某一個節點, 這個時候Client這邊收到的只是根據配置的一致性級別就能夠返回, 好比這裏配置的只要1個返回便可, 那麼Client寫入成功10這個節點之後,便可返回。至於與其餘機房同步是10這個節點的事情, 這樣子客戶端的寫入就能夠在本地寫入, 不用管多機房的latency。

 

這裏咱們能夠看到是Eventual Consistency. 那麼Cassandra是如何作到衝突修復的呢. 這裏Cassandra 讀的時候有一個Read Repair 機制, 就是讀取的時候讀取本地多個副本. 若是副本不一致, 那麼就選時間戳最新的從新寫入. 讓數據從新同步, 這裏Cassandra只是說修復本地多副本數據不一致的方法, 一樣的方法咱們也能夠用在多個IDC裏面, 能夠同時跑多個任務check不一樣機房的數據, 而後修復他們。

 

CouchBase 作法

Continuous Replication提供配置的不一樣Server之間同步的Stream的個數,也就是不一樣的機房之間鏈接的數目是可配置的。解決衝突辦法.CouchBase提供的是最終一致性的方法,不一樣的版本之間首先根據修改的次數, 而後是修改時間等信息。

 

咱們最後考慮的是使用團隊內部的QBus做爲咱們通訊的隊列, 主要考慮

  • 省去了本身實現隊列的麻煩

  • 穩定運行於線上, 有專門的同事維護. 減小的不少問題

     

Bada 目前線上3種多機房的使用場景

  • 單機房寫入, 任意機房讀取

  • 跨機房寫入, 任意機房讀取

  • 任意機房寫入, 任意機房讀取

咱們的實現方案也是經過QConf來實現。客戶端訪問的時候, 從QConf中讀取目前須要訪問的機房, 默認是訪問本機房, 若是須要跨機房訪問, 將QConf中的配置制定成須要訪問的機房就能夠了。

 

多機房寫入的衝突解決方案

時間戳最新

任意機房寫入數據, 根據時間戳來進行衝突解決。

 

Yahoo Pnuts Primary Key

這裏咱們對每個Key 有一個Primary IDC, 也就是這個Key的修改刪除等操做都只會在當前這個IDC完成, 而後讀取能夠有多個IDC去讀取. 那麼由於對於同一個Key的修改, 咱們都在同一個IDC上. 咱們經過給每個Key加上一個Version信息, 相似Memcached的cas操做, 那麼咱們就能夠保證作到支持單條數據的事務。若是這條數據的Primary IDC是在本機房, 那麼插入操做很快。

 

若是這條數據的Primary IDC不是本機房, 那麼就有一個Cross IDC的修改操做, 延遲將會比較高。不過咱們考慮一下咱們大部分的應用場景,好比微博, 90%的數據的修改應該會在同一個機房。好比一個用戶有一個profile信息, 那麼和修改這個信息的基本都是這個用戶本人, 90%的狀況下應該就是在同一個地點改, 固然寫入也會在同一個機房. 因此大部分的修改應該是同一個機房的修改。可是訪問可能來自各個地方,固然爲了作優化, 有些數據可能在一個地方修改過了之後, 屢次在其餘地方修改, 那麼咱們就能夠修改這個Key的Primary IDC到另外這個機房。

 

Vector Lock

Vector Lock的核心思想就是Client對這個數據的瞭解是遠遠超過服務端的, 由於對於服務端而言, 這個Key 對應的Value 對於Server 端只是一個字符串。而Client端可以具體瞭解這個Value所表明的含義, 對這個Value進行解析。那麼對於這個例子,當這兩個不同的Value寫入到兩個副本中的時候, Client進行一次讀取操做讀取了多個副本。

 

Client發現讀到的兩個副本的結果是有衝突的, 這裏咱們假設原始的Key的Vector Lock信息是[X:1], 那麼第一次修改就是[X:1,Y:1], 另外一個客戶端是基於[X:1]的Vector Lock修改的, 因此它的Vector Lock信息就應該是[X:1,Z:1]。這個時候咱們只要檢查這個Vector Lock信息就能夠能夠發現他們衝突, 這個就是就交給客戶端去處理這個衝突.並把結果從新Update便可。

 

咱們線上目前支持的是時間戳最新, 以及Primary Key的方案. 大部分使用的是時間戳最新來進行衝突解決。

 

多數據結構支持

  • 咱們開發了一套基於leveldb的多數據結構的引擎。目前支持 Hash, List, Set, Zset等結構。

  • 主要是因爲用戶習慣了Redis提供的多數據結構, 可以知足用於快速開發業務的過程, 所以咱們也提供了多數據結構的支持。

     

爲何不使用ZooKeeper

  • ZooKeeper 和 mnesia對比, ZooKeeper 是一個服務, 而 mnesia是一個庫, 所以若是使用ZooKeeper的話, 咱們須要額外的維護一套服務。而 mnesia能夠直接集成在代碼裏面,使用更方便。

  • mnesia和 Erlang 集成的更好,mnesia自己就是用Erlang 來開發。

     

Bada 和 MongoDB對比

  • 360的MongoDB 以前也是咱們團隊在維護, 在使用MongoDB的過程當中, 咱們也遇到一些問題, 好比MongoDB 的擴容很是不方便, 擴容須要很長的時間, 由於MongoDB 擴容的過程是將一條一條的數據寫入的. 咱們開發的時候考慮到這些問題, 所以Bada 使用的是leveldb, 當須要擴容的時候, 只要將某一個分片下面的數據文件拷貝過去便可. 前提是初始化的時候分片設置的足夠大, 咱們現實默認的分片是1024

  • MongoDB 的數據膨脹度比較大, 由於MongoDB 畢竟是文檔型數據庫, 確定會保持一些冗餘信息. 咱們底下使用leveldb, leveldb 自己的壓縮功能基於snappy 壓縮. 仍是作的比較好. 線上實際的磁盤空間大小相對於MongoDB 4:1

     

Bada 和 Cassandra 對比

Cassandra的定位和Bada是不同的, 咱們面向的是線上頻繁訪問的熱數據, 所以咱們偏向於存儲小value數據, 熱數據, 對latency 的要求會苛刻。

 

好比在雲盤的場景, 咱們存儲的就是文件的索引信息, 而Cassandra存儲的是具體的Cassandra的數據, 也所以咱們線上部署Bada的機器是掛載SSD盤的。

 

Bada 和 Redis 對比

  • Bada 的性能比Redis 低, 可是目前redis cluster 還沒發展完善. 咱們公司的DBA也在跟進Redis cluster之中. 因此當數據量比較大的時候, Redis可能就不適用於這麼大量的數據存儲。

  • Bada 的多數據結構支持不如Redis來得完善. 所以咱們也在逐步的支持Bada的多數據結構。

  • Redis 畢竟是內存型的服務. 所以假如用戶是偏向於存儲持久化數據, 可能Redis不太合適。

     

一些非技術的經驗

技術是爲業務服務, 包括咱們Bada在公司內部推廣的過程當中也發現, 咱們不少業務很頭疼的問題在於360的機房較多, 每個小業務都須要維護在多個機房, 所以爲了下降用戶的開發試錯成本, 咱們將能標準化的事情都作了。包括咱們組的定位也是專一底層技術, 加速產品團隊開發效率, 儘量下降業務對服務端集羣架構的關注。

 

Q&A:

Q1:客戶端訪問Bada時,怎麼確保數據的均衡?從qconf拿到的是一個ip列表吧?

是的。從QConf 中得到是隨機的一個節點的ip,因此對每個節點的訪問基本的均衡的。服務端這邊, 由於咱們是有主從結構的。可是咱們的主從是分片級別的主從,這點和redis cluster 不同。好比 Redis cluster 有Master 節點, slave節點,通常狀況slave 節點不接受任何的線上訪問,可是從下面的圖中能夠看到 Bada 每個節點都有主, 從分片。 由於每個節點的訪問基本是均衡的。

 

Q2:我有一個問題,對於kv存儲,選擇leveldb的動機是什麼?其餘leveldb分支是否考慮過?

對於存儲的考慮, 咱們以前對 Rocksdb 和 leveldb 作過對比.在數據量小的狀況下, leveldb 的性能和 Rocksdb 性能差很少. 數據量大的時候 Rocksdb 會有性能優點. 由於咱們以前對leveldb 作了修改. 因此後續咱們會遷移過去。 這裏咱們的讀寫都走的是 Master 節點. 只有當主節點掛掉之後, 纔會訪問從節點。

這個截圖是以前對 leveldb 和 rocksdb 在數據量比較小的狀況下的對比

 

Q3:可否說一下擴容,新增節點,以及摘除失效節點的處理?

從上面兩張圖中能夠看出, 咱們會將新增的節點中, 均衡的將新的主節點遷移的新節點上。目前擴容的過程是這樣 咱們先把當前這個節點加入到集羣。而後經過 rebalance 來進行平衡。咱們通常預先分配1024 個分配。這個應該也是業內場景的作法, 以前對騰訊的CKV 也是這麼作,Riak 也是這麼作。

 

Q4:遷移是直接對leveldb複製,延時會有多少,在遷移過程當中的訪問如何處理呢?

遷移是直接對 leveldb 的文件進行復制, 這個時候性能是取決於網絡的開銷。這也是咱們比mongo擴容快的地方, mongo 在擴容的時候須要將數據一條一條寫。遷移以前, 咱們會將當前這個節點進行切主操做, 就是將全部的主切走。那麼這個時候是不會影響線上訪問,帶來的最多的影響就是這個節點的網絡有額外的開銷,可是這個節點不是面向用戶的請求的,因此影響不 大。

 

Q5 :主切走也須要有一個時間吧?這個時間段內,若是要訪問原來主上的數據,怎麼處理?

這裏是這樣的一個過程, 遷移的時候好比A 節點。 那麼A節點上有主分片, 那麼在遷移以前,咱們會先將A節點上的主讓給其餘節點。這裏就涉及到追Binlog 的問題,若是這個時候用戶有大量的數據寫入, 會致使Binlog 一直追不齊。確實會致使沒法遷移。

 

Q6:關於leveldb的遷移,可否詳細介紹一下?

leveldb 的遷移很簡單,就是直接經過scp 就能夠了。這個是leveldb 自己的功能,就是經過scp leveldb 對應的數據文件就能夠。其實咱們在binlog 這塊也作了挺多的事情, 不過太細了有機會下次講。使用binlog 來同步的副本策略之中, 常見的問題好比,分佈式系統中因爲主從切換致使的數據丟失,而後咱們也開發了binlog merge 來減小這種問題帶來的影響。

 

Q7:leveldb的部分數據在內存中,這個遷移的時候怎麼解決的?

這個沒有影響。由於leveldb 的memtable 的數據在磁盤上有對應的.log 文件。leveldb 啓動的時候會默認讀取.log文件, 將裏面的內容加載到內存中。

 

Q8 : 我仍是沒太明白,擴容的時候,A節點切到其餘節點,是把A的meta信息作切換,而後再複製數據,最後再映射meta?

擴容的時候是這樣一個過程。先將新增的節點加入到現有的集羣,不過這個節點不負責任何的分片, 所以沒有任何數據在這個節點上;而後咱們遷移的過程是節點上的一個個的分片進行遷移。好比A 這個節點有 10~20 這幾個分片, 而且這個時候 10~20 這個分片是主, 那麼依次咱們先將A這個節點的10~20變成從, 這個時候須要修改meta信息。而後接下來是複製對應的數據文件到新節點, 複製結束之後, 修改10~20 這幾個分片到新的主上.最後修改meta 信息 ,和大部分系統比最大的不一樣在於 Bada 的主從是分片級別的主從, 不是節點級別的主從.這樣任何操做形成的影響都是很是小. 而且能夠作到每一個節點的負載儘量的均衡。

 

Q9:mnesia用來存儲meta信息嗎?

mnesia 對於咱們的定位就相似於ZooKeeper。有兩個用途, 一個是選主的過程提供一個全局的鎖, 一個是保存元信息。

爲何不使用ZooKeeper

  • ZooKeeper 和 mnesia 對比, ZooKeeper 是一個服務, 而mnesia是一個庫, 所以若是使用ZooKeeper的話, 咱們須要額外的維護一套服務. 而mnesia能夠直接集成在代碼裏面. 使用更方便

  • mnesia 和 erlang 集成的更好. mnesia自己就是用Erlang 來開發

 

Q10:meta信息是存儲在單獨的機器上,而不是分佈在存儲節點上嗎?

不是, 存儲在每個節點上. 每個節點都部有mnesia

 

Q11:既然用mnesia,那你前端機器連在一個集羣?規模多大?

前端是按照業務劃分的,最大的有36個節點.

 

感謝王傑的記錄與整理,國忠和四正的校對,其餘多位編輯組志願者對本文亦有貢獻。更多關於架構方面的內容,讀者能夠經過搜索「ArchNotes」或長按下面圖片,關注「高可用架構」公衆號,查看更多架構方面內容,獲取通往架構師之路的寶貴經驗。轉載請註明來自「高可用架構(ArchNotes)」微信公衆號。

相關文章
相關標籤/搜索