深刻淺出計算機組成原理學習筆記:第五十三講

1、上節總結回顧

上一講裏,根據DMP系統的各個應用場景,咱們從抽象的原理層面,選擇了AeroSpike做爲KV數據庫,Kafka做爲數據管道,Hadoop/Hive來做爲數據倉庫。算法

不過呢,確定有不信邪的工程師會問,爲何MongoDB,甚至是MySQL這樣的文檔數據庫或者傳統的關係型數據庫不適應呢?爲何不能經過優化SQL、添加緩存這樣的調優手段,解決這個問題呢?數據庫

今天DMP的下半場,咱們就從數據庫實現的原理,一塊兒來看一看,這背後的緣由。若是你能弄明表今天的這些更深刻、更細節的原理,對於什麼場景使用什麼數據庫,就會更加成竹在胸,而不是隻有緩存

跑了大量的性能測試才知道。下次作數據庫選型的時候,你就能夠「以理服人」了。網絡

2、關係型數據庫:不得不作的隨機讀寫

咱們先來想想,若是如今讓你本身寫一個最簡單的關係型數據庫,你的數據要怎麼存放在硬盤上?最簡單最直觀的想法是,數據結構

一、你的數據要怎麼存放在硬盤上?

一、用一個CSV文件格式。一個文件就是一個數據表。併發

二、文件裏面的每一行就是這個表裏面的一條記錄。dom

三、若是要修改數據庫裏面的某一條記錄,那麼咱們要先找到這記錄,分佈式

四、而後直接去修改這一行的數據。讀取數據也是同樣的。高併發

要找到這樣數據,最笨的辦法固然是一行一行讀,也就是遍歷整個CSV文件。不過這樣的話,至關於隨便讀取任何一條數據都要掃描全表oop

二、太浪費硬盤的吞吐量了。那怎麼辦呢?

咱們能夠試試給這個CSV文件加一個索引。好比,給數據的行號加一個索引。若是你學過數據庫原理或者算法和數據結構,那你應該知道,經過B+樹多半是能夠來創建這樣一個索引的。

一、索引裏面沒有一整行的數據,只有一個映射關係,這個映射關係可讓行號直接從硬盤的某個位置去讀。

二、因此,索引比起數據小不少。咱們能夠把索引加載到內存裏面。

三、即便不在內存裏面,要找數據的時候快速遍歷一下整個索引,也不須要讀太多的數據。

加了索引以後,咱們要讀取特定的數據,就不用去掃描整個數據表文件了。直接從特定的硬盤位置,就能夠讀到想要的行。索引不只能夠索引行號,還能夠索引某個字段。
咱們能夠建立不少個不一樣的獨立的索引。寫SQL的時候,where語句後面的查詢條件能夠用到這些索引。

不過,這樣的話,寫入數據的時候就會麻煩一些。咱們不只要在數據表裏面寫入數據,對於全部的索引也都須要進行更新。這個時候,寫一條條數據就要觸發好幾個隨機寫入的更新。

在這樣一個數據模型下,查詢操做很靈活。不管是根據哪一個字段查詢,只要有索引,咱們就能夠經過一次隨機讀,很快地讀到對應的數據。可是,這個靈活性也帶來了一個很大的問題,

三、加索引後查詢操做很靈活、可是不管幹點什麼都有大量的隨機讀寫請求

那就是不管幹點什麼,都有大量的隨機讀寫請求。而隨機讀寫請求,若是請求最終是要落到硬盤上,特別是HDD硬盤的話,咱們就很難作到高併發了。畢竟HDD硬盤只有100左右的QPS。

而這個隨時添加索引,能夠根據任意字段進行查詢,這樣表現出的靈活性,又是咱們的DMP系統裏面不太須要的。DMP的KV數據庫主要的應用場景,是根據主鍵的隨機查詢,
不須要根據其餘字段進行篩選查詢。數據管道的需求,則只須要不斷追加寫入和順序讀取就行了。即便進行數據分析的數據倉庫,一般也不是根據字段進行數據篩選,而是全量掃描數據進行分析彙總。

後面的兩個場景還好說,大不了咱們讓程序去掃描全表或者追加寫入。可是,在KV數據庫這個需求上,剛纔這個最簡單的關係型數據庫的設計,就會面臨大量的隨機寫入和隨機讀取的挑戰。

因此,在實際的大型系統中,你們都會使用專門的分佈式KV數據庫,來知足這個需求。那麼下面,咱們就一塊兒來看一看,Facebook開源的Cassandra的數據存儲和讀寫是怎麼作的,
這些設計是怎麼解決高併發的隨機讀寫問題的。

3、Cassandra:順序寫和隨機讀

一、Cassandra的數據模型

做爲一個分佈式的KV數據庫,Cassandra的鍵通常被稱爲Row Key。其實就是一個16到36個字節的字符串。每個Row Key對應的值實際上是一個哈希表,裏面能夠用鍵值對,再存入不少你須要的數據。

Cassandra自己不像關係型數據庫那樣,有嚴格的Schema,在數據庫建立的一開始就定義好了有哪些列(Column)。可是,它設計了一個叫做列族(Column Family)的概念,咱們須要把常常放在一塊兒使用的
字段,放在同一個列族列族。好比,DMP裏面的人口屬性信息,咱們能夠把它當成是一個列族。用戶的興趣信息,能夠是另一個列族。這樣,既保持了不須要嚴格的Schema這樣的靈活性,也保留了
能夠把經常起使用的數據存放在一塊兒的空間局部性。

往Cassandra的裏面讀寫數據,其實特別簡單,就好像是在一個巨大的分佈式的哈希表裏面寫數據。咱們指定一個Row Key,而後插入或者更新這個Row Key的數據就行了。

二、Cassandra的寫操做

Cassandra解決隨機寫入數據的解決方案,簡單來講,就叫做「不隨機寫,只順序寫」。對於Cassandra數據庫的寫操做,一般包含兩個動做。第一個是往磁盤上寫入一條提交日誌(Commit Log)。另外一個操做

則是直接在內存的數據結構上去更新數據。後面這個往內存的數據結構裏面的數據更新,只有在提交日誌寫成功以後纔會進入。每臺機器上,都有一個可靠的硬盤可讓咱們去寫入提交日誌。寫入提交日誌都是順序
寫(Sequential Write),而不是隨機寫(Random Write),這使得咱們最大化了寫入的吞吐量。

若是你不明白這是爲何,能夠回到第47講,看看硬盤的性能評測。不管是HDD硬盤仍是SSD硬盤,順序寫入都比隨機寫入要快得多。

內存的空間比較有限,一旦內存裏面的數據量或者條母超過必定的限額,Cassandra就會把內存裏面的數據結構dump到硬盤上。這個Dump的操做,也是順序寫而不是隨機寫,因此性能也不會是一個問題。除了
Dump的數據結構文件,Cassandra還會根據row key來生成一個索引文件,方便後續基於索引來進行快速查詢。

隨着硬盤上的Dump出來的文件愈來愈多,Cassandra會在後臺進行文件的對比合並。在不少別的KV數據庫系統裏面,也有相似這種的合併動做,好比AeroSpike或者Google的BigTable。這些操做咱們通常稱之爲
Compaction。合併動做一樣是順序讀取多個文件,在內存裏面合併完成,再Dump出來一個新的文件。整個操做過程當中,在硬盤層面仍然是順序讀寫。

三、Cassandra的讀操做

 

 

當咱們要從Cassandra讀數據的時候,會從內存裏面找數據,再從硬盤讀數據,而後把兩部分的數據合併成最終結果。這些硬盤上的文件,在內存裏面會有對應的Cache,只有在Cache裏面找不到,咱們纔會去請求
硬盤裏面的數據。

若是不得不訪問硬盤,由於硬盤裏面可能Dump了不少個不一樣時間點的內存數據的快照。因此,找數據的時候,咱們也是按照時間重新的往舊的裏面找。

這也就帶來另一個問題,咱們可能要查詢不少個Dump文件,才能找到咱們想要的數據。因此,Cassandra在這一點上又作了一個優化。那就是,它會爲每個Dump的文件裏面全部Row Key生成一個
BloomFilter,而後把這個BloomFilter放在內存裏面。這樣,若是想要查詢的Row Key在數據文件裏面不存在,那麼99%以上的狀況下,它會被BloomFilter過濾掉,而不須要訪問硬盤。

這樣,只有當數據在內存裏面沒有,而且在硬盤的某個特定文件上的時候,纔會觸發一次對於硬盤的讀請求。

4、SSD:DBA們的大救星

Cassandra是Facebook在2008年開源的。那個時候,SSD硬盤尚未那麼普及。能夠看到,它的讀寫設計充分考慮了硬件自己的特性。在寫入數據進行持久化上,Cassandra沒有任何的隨機寫請求,不管是
Commit Log仍是Dump,所有都是順序寫。

一、Cassandra在數據讀的請求上作的優化

在數據讀的請求上,最新寫入的數據都會更新到內存。若是要讀取這些數據,會優先從內存讀到。這至關因而一個使用了LRU的緩存機制。只有在萬般無奈的狀況下,纔會有對於硬盤的隨機讀請求。即便在這樣的情
況下,Cassandra也在文件以前加了一層BloomFilter,把原本由於Dump文件帶來的須要屢次讀硬盤的問題,簡化成屢次內存讀和一次硬盤讀。

這些設計,使得Cassandra即便是在HDD硬盤上,也能有不錯的訪問性能。由於全部的寫入都是順序寫或者寫入到內存,因此,寫入能夠作到高併發。HDD硬盤的吞吐率仍是很不錯的,每秒能夠寫入100MB以上的
數據,若是一條數據只有1KB,那麼10萬的WPS(Writes per seconds)也是可以作到的。這足夠支撐咱們DMP指望的寫入壓力了。

而對於數據的讀,就有一些挑戰了。若是數據讀請求有很強的局部性,那咱們的內存就能搞定DMP須要的訪問量。

二、問題就出在這個局部性上

可是,問題就出在這個局部性上。DMP的數據訪問分佈,實際上是缺乏局部性的。你仔細想想DMP的應用場景就明白了。DMP裏面的Row Key都是用戶的惟一標識符。普通用戶的上網時長怎麼會有局部性呢?每一個
人上網的時間和訪問網頁的次數就那麼多。上網多的人,一天最多也就24小時。大部分用戶一天也要上網2〜3小時。咱們沒辦法說,把這些用戶的數據放在內存裏面,那些用戶不放。

那麼,咱們可不可能有⼀定的時間局部性呢?若是是Facebook那樣的全球社交⽹絡,那可能還有⼀定的時間局部性。畢竟不一樣國家的用的時區不同。咱們能夠說,在印度人民的一天,把印度人民的數據加載到內
存裏面,美國人民的數據就放在硬盤上。到了印度人民的晚上,再把美國人民的數據換到內存裏面來。若是你的主要業務是在國內,那這個時間局部性就沒有了。你們的上網高峯時段,都是在早上上班路上、中
午休息的時候以及晚上下班以後的時間,沒有什麼區分度。

面臨這個狀況,若是大家的CEO或者CTO問你,是否是能夠經過優化程序來解決這個問題?若是你沒有仔細從數據分佈和原理的層面思考這個問題,而直接一下答應下來,那你可能以後要頭疼了,由於這個問題頗有
多是搞不定的。

由於缺乏了時間局部性,咱們內存的緩存可以起到的做用就很小了,大部分請求最終仍是要落到HDD硬盤的隨機讀上。可是,HDD硬盤的隨機讀的性能太差了,咱們在第45講看過,也就是100QPS左右。而若是全都
放內存,那就太貴了,成本在HDD硬盤100倍以上。

三、2010年SSD硬盤的大規模商用解決了局部性問題

不過,幸運的是,從2010年開始,SSD硬盤的大規模商用幫助咱們解決了這個問題。它的價格在HDD硬盤的10倍,可是隨機讀的訪問能力在HDD硬盤的百倍以上。也就是說,用上了SSD硬盤,咱們能夠用1/10的成
本得到和HDD硬盤一樣的QPS。一樣的價格的SSD硬盤,容量則是內存的10倍,也可以知足咱們的需求,比較低的成本存下整個互聯網絡信息。

不誇張地說,過去幾年的「大數據」「高併發」「千人千面」,有一半的功勞應該歸在讓SSD容量不斷上升、價格不斷降低的硬盤產業上。回到咱們看到的Cassandra的讀寫設計,你會發現,Cassandra的寫入機制完美匹配了咱們在第46和47講所說的SSD硬盤的優缺點。

在數據寫入層面,Cassandra的數據寫入都是Commit Log的順序寫入,也就是不斷地在硬盤上日後追加內容,而不是去修改現有的文件內容。一旦內存裏面的數據超過必定的閾值,Cassandra就會完整地Dump一
個新文件到文件系統上。這一樣是一個追加寫入。

數據的對等和緊湊化(Compaction),一樣是讀取現有的多個文件,而後寫一個新的文件出來。寫入操做只追加不修改的特性,正好自然地符合SSD硬盤只能按塊進行擦除寫入的操做。在這樣的寫入模式下,
Cassandra用到的SSD硬盤,不須要頻繁地進行後臺的Compaction,可以最大化SSD硬盤的使用壽命。這也是爲何,Cassandra在SSD硬盤普及以後,可以得到進一步快速發展。

5、延伸總結

好了,關於DMP和存儲器的內容,講到這裏就差很少了。但願今天的這一講,可以讓你從Cassandra的數據庫實現的細節層面,完全理解怎麼運用好存儲器的性能特性和原理。

傳統的關係型數據庫,咱們把一條條數據存放在一個地方,同時再把索引存放在另一個地放。這樣的存儲方式,其實很方便咱們進行單次的隨機讀和隨機寫,數據的存儲也能夠很緊湊。可是問題也在於此,大部分的SQL請求,都會帶來大量的隨機讀寫的請求。這使得傳統的關係型數據庫,其實並不適合用在真的高併發的場景下。

咱們的DMP須要的訪問場景,其實沒有複雜的索引需求,可是會有比較高的併發性。我帶你一看了Facebook開源的Cassandra這個分佈式KV數據庫的讀寫設計。經過在追加寫入Commit Log和更新內存,
Cassandra避開了隨機寫的問題。內存數據的Dump和後臺的對比合並,一樣也都避開了隨機寫的問題,使得Cassandra的併發寫入性能極高。

在數據讀取層面,經過內存緩存和BloomFilter,Cassandra已經儘量地減小了須要隨機讀取硬盤裏面數據的狀況。不過挑戰在於,DMP系統的局部性不強,使得咱們最終的隨機讀的請求仍是要到硬盤上。幸運的是,SSD硬盤在數據海量增加的那幾年裏價格不斷降低,使得咱們最終經過SSD硬盤解決了這個問題。而SSD硬盤自己的擦除後才能寫入的機制,正好很是適合Cassandra的數據讀寫模式,最終使得Cassandra在SSD硬盤普及以後獲得了更大的發展。

相關文章
相關標籤/搜索