mirror of
https://github.com/Vonng/ddia.git
synced 2024-12-06 15:20:12 +08:00
update zh-tw content
This commit is contained in:
parent
1d4b5106e2
commit
e500b0d71e
@ -87,7 +87,7 @@
|
||||
|
||||
* 資料庫通常保留資料直至顯式刪除,而大多數訊息代理在訊息成功遞送給消費者時會自動刪除訊息。這樣的訊息代理不適合長期的資料儲存。
|
||||
* 由於它們很快就能刪除訊息,大多數訊息代理都認為它們的工作集相當小—— 即佇列很短。如果代理需要緩衝很多訊息,比如因為消費者速度較慢(如果記憶體裝不下訊息,可能會溢位到磁碟),每個訊息需要更長的處理時間,整體吞吐量可能會惡化【6】。
|
||||
* 資料庫通常支援二級索引和各種搜尋資料的方式,而訊息代理通常支援按照某種模式匹配主題,訂閱其子集。雖然機制並不一樣,但對於客戶端選擇想要了解的資料的一部分,都是基本的方式。
|
||||
* 資料庫通常支援次級索引和各種搜尋資料的方式,而訊息代理通常支援按照某種模式匹配主題,訂閱其子集。雖然機制並不一樣,但對於客戶端選擇想要了解的資料的一部分,都是基本的方式。
|
||||
* 查詢資料庫時,結果通常基於某個時間點的資料快照;如果另一個客戶端隨後向資料庫寫入一些改變了查詢結果的內容,則第一個客戶端不會發現其先前結果現已過期(除非它重複查詢或輪詢變更)。相比之下,訊息代理不支援任意查詢,但是當資料發生變化時(即新訊息可用時),它們會通知客戶端。
|
||||
|
||||
這是關於訊息代理的傳統觀點,它被封裝在諸如JMS 【14】和AMQP 【15】的標準中,並且被諸如RabbitMQ、ActiveMQ、HornetQ、Qpid、TIBCO企業訊息服務、IBM MQ、Azure Service Bus和Google Cloud Pub/Sub所實現 【16】。
|
||||
|
@ -105,9 +105,9 @@
|
||||
|
||||
具有良好定義的輸入和輸出的確定性函式的原理不僅有利於容錯(請參閱“[冪等性](ch11.md#冪等性)”),也簡化了有關組織中資料流的推理【7】。無論衍生資料是搜尋索引、統計模型還是快取,採用這種觀點思考都是很有幫助的:將其視為從一個東西衍生出另一個的資料管道,透過函式式應用程式碼推送一個系統的狀態變更,並將其效果應用至衍生系統中。
|
||||
|
||||
原則上,衍生資料系統可以同步地維護,就像關係資料庫在與索引表寫入操作相同的事務中同步更新輔助索引一樣。然而,非同步是使基於事件日誌的系統穩健的原因:它允許系統的一部分故障被抑制在本地。而如果任何一個參與者失敗,分散式事務將中止,因此它們傾向於透過將故障傳播到系統的其餘部分來放大故障(請參閱“[分散式事務的限制](ch9.md#分散式事務的限制)”)。
|
||||
原則上,衍生資料系統可以同步地維護,就像關係資料庫在與索引表寫入操作相同的事務中同步更新次級索引一樣。然而,非同步是使基於事件日誌的系統穩健的原因:它允許系統的一部分故障被抑制在本地。而如果任何一個參與者失敗,分散式事務將中止,因此它們傾向於透過將故障傳播到系統的其餘部分來放大故障(請參閱“[分散式事務的限制](ch9.md#分散式事務的限制)”)。
|
||||
|
||||
我們在“[分割槽與次級索引](ch6.md#分割槽與次級索引)”中看到,二級索引經常跨越分割槽邊界。具有二級索引的分割槽系統需要將寫入傳送到多個分割槽(如果索引按關鍵詞分割槽的話)或將讀取傳送到所有分割槽(如果索引是按文件分割槽的話)。如果索引是非同步維護的,這種跨分割槽通訊也是最可靠和最可伸縮的【8】(另請參閱“[多分割槽資料處理](多分割槽資料處理)”)。
|
||||
我們在“[分割槽與次級索引](ch6.md#分割槽與次級索引)”中看到,次級索引經常跨越分割槽邊界。具有次級索引的分割槽系統需要將寫入傳送到多個分割槽(如果索引按關鍵詞分割槽的話)或將讀取傳送到所有分割槽(如果索引是按文件分割槽的話)。如果索引是非同步維護的,這種跨分割槽通訊也是最可靠和最可伸縮的【8】(另請參閱“[多分割槽資料處理](多分割槽資料處理)”)。
|
||||
|
||||
#### 應用演化後重新處理資料
|
||||
|
||||
@ -261,7 +261,7 @@
|
||||
|
||||
用於次級索引的衍生函式是如此常用的需求,以致於它作為核心功能被內建至許多資料庫中,你可以簡單地透過`CREATE INDEX`來呼叫它。對於全文索引,常見語言的基本語言特徵可能內建到資料庫中,但更復雜的特徵通常需要領域特定的調整。在機器學習中,特徵工程是眾所周知的特定於應用的特徵,通常需要包含很多關於使用者互動與應用部署的詳細知識【35】。
|
||||
|
||||
當建立衍生資料集的函式不是像建立二級索引那樣的標準搬磚函式時,需要自定義程式碼來處理特定於應用的東西。而這個自定義程式碼是讓許多資料庫掙扎的地方,雖然關係資料庫通常支援觸發器、儲存過程和使用者定義的函式,可以用它們來在資料庫中執行應用程式碼,但它們有點像資料庫設計裡的事後反思。(請參閱“[傳遞事件流](ch11.md#傳遞事件流)”)。
|
||||
當建立衍生資料集的函式不是像建立次級索引那樣的標準搬磚函式時,需要自定義程式碼來處理特定於應用的東西。而這個自定義程式碼是讓許多資料庫掙扎的地方,雖然關係資料庫通常支援觸發器、儲存過程和使用者定義的函式,可以用它們來在資料庫中執行應用程式碼,但它們有點像資料庫設計裡的事後反思。(請參閱“[傳遞事件流](ch11.md#傳遞事件流)”)。
|
||||
|
||||
#### 應用程式碼和狀態的分離
|
||||
|
||||
|
10
zh-tw/ch3.md
10
zh-tw/ch3.md
@ -299,17 +299,17 @@ B樹在資料庫體系結構中是非常根深蒂固的,為許多工作負載
|
||||
|
||||
到目前為止,我們只討論了鍵值索引,它們就像關係模型中的**主鍵(primary key)** 索引。主鍵唯一標識關係表中的一行,或文件資料庫中的一個文件或圖形資料庫中的一個頂點。資料庫中的其他記錄可以透過其主鍵(或ID)引用該行/文件/頂點,並且索引用於解析這樣的引用。
|
||||
|
||||
有二級索引也很常見。在關係資料庫中,您可以使用 `CREATE INDEX` 命令在同一個表上建立多個二級索引,而且這些索引通常對於有效地執行聯接而言至關重要。例如,在[第二章](ch2.md)中的[圖2-1](../img/fig2-1.png)中,很可能在 `user_id` 列上有一個二級索引,以便您可以在每個表中找到屬於同一使用者的所有行。
|
||||
有次級索引也很常見。在關係資料庫中,您可以使用 `CREATE INDEX` 命令在同一個表上建立多個次級索引,而且這些索引通常對於有效地執行聯接而言至關重要。例如,在[第二章](ch2.md)中的[圖2-1](../img/fig2-1.png)中,很可能在 `user_id` 列上有一個次級索引,以便您可以在每個表中找到屬於同一使用者的所有行。
|
||||
|
||||
一個二級索引可以很容易地從一個鍵值索引構建。主要的不同是鍵不是唯一的。即可能有許多行(文件,頂點)具有相同的鍵。這可以透過兩種方式來解決:或者透過使索引中的每個值,成為匹配行識別符號的列表(如全文索引中的釋出列表),或者透過向每個索引新增行識別符號來使每個關鍵字唯一。無論哪種方式,B樹和日誌結構索引都可以用作輔助索引。
|
||||
一個次級索引可以很容易地從一個鍵值索引構建。主要的不同是鍵不是唯一的。即可能有許多行(文件,頂點)具有相同的鍵。這可以透過兩種方式來解決:或者透過使索引中的每個值,成為匹配行識別符號的列表(如全文索引中的釋出列表),或者透過向每個索引新增行識別符號來使每個關鍵字唯一。無論哪種方式,B樹和日誌結構索引都可以用作次級索引。
|
||||
|
||||
#### 將值儲存在索引中
|
||||
|
||||
索引中的鍵是查詢搜尋的內容,而其值可以是以下兩種情況之一:它可以是所討論的實際行(文件,頂點),也可以是對儲存在別處的行的引用。在後一種情況下,行被儲存的地方被稱為**堆檔案(heap file)**,並且儲存的資料沒有特定的順序(它可以是僅追加的,或者可以跟蹤被刪除的行以便用新資料覆蓋它們後來)。堆檔案方法很常見,因為它避免了在存在多個二級索引時複製資料:每個索引只引用堆檔案中的一個位置,實際的資料儲存在一個地方。
|
||||
索引中的鍵是查詢搜尋的內容,而其值可以是以下兩種情況之一:它可以是所討論的實際行(文件,頂點),也可以是對儲存在別處的行的引用。在後一種情況下,行被儲存的地方被稱為**堆檔案(heap file)**,並且儲存的資料沒有特定的順序(它可以是僅追加的,或者可以跟蹤被刪除的行以便用新資料覆蓋它們後來)。堆檔案方法很常見,因為它避免了在存在多個次級索引時複製資料:每個索引只引用堆檔案中的一個位置,實際的資料儲存在一個地方。
|
||||
|
||||
在不更改鍵的情況下更新值時,堆檔案方法可以非常高效:只要新值的位元組數不大於舊值,就可以覆蓋該記錄。如果新值更大,情況會更復雜,因為它可能需要移到堆中有足夠空間的新位置。在這種情況下,要麼所有的索引都需要更新,以指向記錄的新堆位置,或者在舊堆位置留下一個轉發指標【5】。
|
||||
|
||||
在某些情況下,從索引到堆檔案的額外跳躍對讀取來說效能損失太大,因此可能希望將索引行直接儲存在索引中。這被稱為聚集索引。例如,在MySQL的InnoDB儲存引擎中,表的主鍵總是一個聚集索引,二級索引則引用主鍵(而不是堆檔案中的位置)【31】。在SQL Server中,可以為每個表指定一個聚集索引【32】。
|
||||
在某些情況下,從索引到堆檔案的額外跳躍對讀取來說效能損失太大,因此可能希望將索引行直接儲存在索引中。這被稱為聚集索引。例如,在MySQL的InnoDB儲存引擎中,表的主鍵總是一個聚集索引,次級索引則引用主鍵(而不是堆檔案中的位置)【31】。在SQL Server中,可以為每個表指定一個聚集索引【32】。
|
||||
|
||||
在 **聚集索引(clustered index)** (在索引中儲存所有行資料)和 **非聚集索引(nonclustered index)** (僅在索引中儲存對資料的引用)之間的折衷被稱為 **包含列的索引(index with included columns)** 或**覆蓋索引(covering index)**,其儲存表的一部分在索引內【33】。這允許透過單獨使用索引來回答一些查詢(這種情況叫做:索引 **覆蓋(cover)** 了查詢)【32】。
|
||||
|
||||
@ -548,7 +548,7 @@ WHERE product_sk = 31 AND store_sk = 3
|
||||
|
||||
這個想法的巧妙擴充套件在C-Store中引入,並在商業資料倉庫Vertica【61,62】中被採用。不同的查詢受益於不同的排序順序,為什麼不以幾種不同的方式來儲存相同的資料呢?無論如何,資料需要複製到多臺機器,這樣,如果一臺機器發生故障,您不會丟失資料。您可能還需要儲存以不同方式排序的冗餘資料,以便在處理查詢時,可以使用最適合查詢模式的版本。
|
||||
|
||||
在一個面向列的儲存中有多個排序順序有點類似於在一個面向行的儲存中有多個二級索引。但最大的區別在於面向行的儲存將每一行儲存在一個地方(在堆檔案或聚簇索引中),二級索引只包含指向匹配行的指標。在列儲存中,通常在其他地方沒有任何指向資料的指標,只有包含值的列。
|
||||
在一個面向列的儲存中有多個排序順序有點類似於在一個面向行的儲存中有多個次級索引。但最大的區別在於面向行的儲存將每一行儲存在一個地方(在堆檔案或聚簇索引中),次級索引只包含指向匹配行的指標。在列儲存中,通常在其他地方沒有任何指向資料的指標,只有包含值的列。
|
||||
|
||||
### 寫入列儲存
|
||||
|
||||
|
28
zh-tw/ch5.md
28
zh-tw/ch5.md
@ -305,7 +305,7 @@
|
||||
|
||||
### 多主複製的應用場景
|
||||
|
||||
在單個數據中心內部使用多個主庫很少是有意義的,因為好處很少超過複雜性的代價。 但在一些情況下,多活配置是也合理的。
|
||||
在單個數據中心內部使用多個主庫沒有太大意義,因為複雜性已經超過了能帶來的好處。 但在一些情況下,多活配置是也合理的。
|
||||
|
||||
#### 運維多個數據中心
|
||||
|
||||
@ -321,15 +321,15 @@
|
||||
|
||||
***效能***
|
||||
|
||||
在單活配置中,每個寫入都必須穿過網際網路,進入主庫所在的資料中心。這可能會增加寫入時間,並可能違背了設定多個數據中心的初心。在多活配置中,每個寫操作都可以在本地資料中心進行處理,並與其他資料中心非同步複製。因此,資料中心之間的網路延遲對使用者來說是透明的,這意味著感覺到的效能可能會更好。
|
||||
在單主配置中,每個寫入都必須穿過網際網路,進入主庫所在的資料中心。這可能會增加寫入時間,並可能違背了設定多個數據中心的初心。在多主配置中,每個寫操作都可以在本地資料中心進行處理,並與其他資料中心非同步複製。因此,資料中心之間的網路延遲對使用者來說是透明的,這意味著感覺到的效能可能會更好。
|
||||
|
||||
***容忍資料中心停機***
|
||||
|
||||
在單主配置中,如果主庫所在的資料中心發生故障,故障切換可以使另一個數據中心裡的追隨者成為領導者。在多活配置中,每個資料中心可以獨立於其他資料中心繼續執行,並且當發生故障的資料中心歸隊時,複製會自動趕上。
|
||||
在單主配置中,如果主庫所在的資料中心發生故障,故障切換必須使另一個數據中心裡的追隨者成為領導者。在多主配置中,每個資料中心可以獨立於其他資料中心繼續執行,並且當發生故障的資料中心歸隊時,複製會自動趕上。
|
||||
|
||||
***容忍網路問題***
|
||||
|
||||
資料中心之間的通訊通常穿過公共網際網路,這可能不如資料中心內的本地網路可靠。單主配置對這資料中心間的連線問題非常敏感,因為透過這個連線進行的寫操作是同步的。採用非同步複製功能的多活配置通常能更好地承受網路問題:臨時的網路中斷並不會妨礙正在處理的寫入。
|
||||
資料中心之間的通訊通常穿過公共網際網路,這可能不如資料中心內的本地網路可靠。單主配置對這資料中心間的連線問題非常敏感,因為透過這個連線進行的寫操作是同步的。採用非同步複製功能的多主配置通常能更好地承受網路問題:臨時的網路中斷並不會妨礙正在處理的寫入。
|
||||
|
||||
有些資料庫預設情況下支援多主配置,但使用外部工具實現也很常見,例如用於MySQL的Tungsten Replicator 【26】,用於PostgreSQL的BDR【27】以及用於Oracle的GoldenGate 【19】。
|
||||
|
||||
@ -353,7 +353,7 @@
|
||||
|
||||
實時協作編輯應用程式允許多個人同時編輯文件。例如,Etherpad 【30】和Google Docs 【31】允許多人同時編輯文字文件或電子表格(該演算法在“[自動衝突解決](#自動衝突解決)”中簡要討論)。我們通常不會將協作式編輯視為資料庫複製問題,但與前面提到的離線編輯用例有許多相似之處。當一個使用者編輯文件時,所做的更改將立即應用到其本地副本(Web瀏覽器或客戶端應用程式中的文件狀態),並非同步複製到伺服器和編輯同一文件的任何其他使用者。
|
||||
|
||||
如果要保證不會發生編輯衝突,則應用程式必須先取得文件的鎖定,然後使用者才能對其進行編輯。如果另一個使用者想要編輯同一個文件,他們首先必須等到第一個使用者提交修改並釋放鎖定。這種協作模式相當於在領導者上進行交易的單領導者複製。
|
||||
如果要保證不會發生編輯衝突,則應用程式必須先取得文件的鎖定,然後使用者才能對其進行編輯。如果另一個使用者想要編輯同一個文件,他們首先必須等到第一個使用者提交修改並釋放鎖定。這種協作模式相當於主從複製模型下在主節點上執行事務操作。
|
||||
|
||||
但是,為了加速協作,您可能希望將更改的單位設定得非常小(例如,一個按鍵),並避免鎖定。這種方法允許多個使用者同時進行編輯,但同時也帶來了多領導者複製的所有挑戰,包括需要解決衝突【32】。
|
||||
|
||||
@ -369,7 +369,7 @@
|
||||
|
||||
#### 同步與非同步衝突檢測
|
||||
|
||||
在單主資料庫中,第二個寫入將被阻塞,並等待第一個寫入完成,或中止第二個寫入事務,強制使用者重試。另一方面,在多活配置中,兩個寫入都是成功的,並且在稍後的時間點僅僅非同步地檢測到衝突。那時要求使用者解決衝突可能為時已晚。
|
||||
在單主資料庫中,第二個寫入將被阻塞,並等待第一個寫入完成,或中止第二個寫入事務,強制使用者重試。另一方面,在多主配置中,兩個寫入都是成功的,並且在稍後的時間點僅僅非同步地檢測到衝突。那時要求使用者解決衝突可能為時已晚。
|
||||
|
||||
原則上,可以使衝突檢測同步 - 即等待寫入被複制到所有副本,然後再告訴使用者寫入成功。但是,透過這樣做,您將失去多主複製的主要優點:允許每個副本獨立接受寫入。如果您想要同步衝突檢測,那麼您可以使用單主程式複製。
|
||||
|
||||
@ -447,13 +447,13 @@
|
||||
|
||||
**圖5-8 三個可以設定多領導者複製的示例拓撲。**
|
||||
|
||||
最普遍的拓撲是全部到全部([圖5-8 (c)](../img/fig5-8.png)),其中每個領導者將其寫入每個其他領導。但是,也會使用更多受限制的拓撲:例如,預設情況下,MySQL僅支援**環形拓撲(circular topology)**【34】,其中每個節點接收來自一個節點的寫入,並將這些寫入(加上自己的任何寫入)轉發給另一個節點。另一種流行的拓撲結構具有星形的形狀[^v]。一個指定的根節點將寫入轉發給所有其他節點。星型拓撲可以推廣到樹。
|
||||
最普遍的拓撲是全部到全部([圖5-8 (c)](../img/fig5-8.png)),其中每個領導者將其寫入每個其他領導。但是,也會使用更多受限制的拓撲:例如,預設情況下,MySQL僅支援**環形拓撲(circular topology)**【34】,其中每個節點接收來自一個節點的寫入,並將這些寫入(加上自己的任何寫入)轉發給另一個節點。另一種流行的拓撲結構具有星形的形狀[^v]。一個指定的根節點將寫入轉發給所有其他節點。星形拓撲可以推廣到樹。
|
||||
|
||||
[^v]: 不要與星型模式混淆(請參閱“[星型和雪花型:分析的模式](ch3.md#星型和雪花型:分析的模式)”),其中描述了資料模型的結構,而不是節點之間的通訊拓撲。
|
||||
|
||||
在圓形和星形拓撲中,寫入可能需要在到達所有副本之前透過多個節點。因此,節點需要轉發從其他節點收到的資料更改。為了防止無限複製迴圈,每個節點被賦予一個唯一的識別符號,並且在複製日誌中,每個寫入都被標記了所有已經過的節點的識別符號【43】。當一個節點收到用自己的識別符號標記的資料更改時,該資料更改將被忽略,因為節點知道它已經被處理過。
|
||||
在環形和星形拓撲中,寫入可能需要在到達所有副本之前透過多個節點。因此,節點需要轉發從其他節點收到的資料更改。為了防止無限複製迴圈,每個節點被賦予一個唯一的識別符號,並且在複製日誌中,每個寫入都被標記了所有已經過的節點的識別符號【43】。當一個節點收到用自己的識別符號標記的資料更改時,該資料更改將被忽略,因為節點知道它已經被處理過。
|
||||
|
||||
迴圈和星型拓撲的問題是,如果只有一個節點發生故障,則可能會中斷其他節點之間的複製訊息流,導致它們無法通訊,直到節點修復。拓撲結構可以重新配置為在發生故障的節點上工作,但在大多數部署中,這種重新配置必須手動完成。更密集連線的拓撲結構(例如全部到全部)的容錯性更好,因為它允許訊息沿著不同的路徑傳播,避免單點故障。
|
||||
環形和星形拓撲的問題是,如果只有一個節點發生故障,則可能會中斷其他節點之間的複製訊息流,導致它們無法通訊,直到節點修復。拓撲結構可以重新配置為在發生故障的節點上工作,但在大多數部署中,這種重新配置必須手動完成。更密集連線的拓撲結構(例如全部到全部)的容錯性更好,因為它允許訊息沿著不同的路徑傳播,避免單點故障。
|
||||
|
||||
另一方面,全部到全部的拓撲也可能有問題。特別是,一些網路連結可能比其他網路連結更快(例如,由於網路擁塞),結果是一些複製訊息可能“超過”其他複製訊息,如[圖5-9](../img/fig5-9.png)所示。
|
||||
|
||||
@ -479,7 +479,7 @@
|
||||
|
||||
[^vi]: Dynamo不適用於Amazon以外的使用者。 令人困惑的是,AWS提供了一個名為DynamoDB的託管資料庫產品,它使用了完全不同的體系結構:它基於單領導者複製。
|
||||
|
||||
在一些無領導者的實現中,客戶端直接將寫入傳送到到幾個副本中,而另一些情況下,一個 **協調者(coordinator)** 節點代表客戶端進行寫入。但與主庫資料庫不同,協調者不執行特定的寫入順序。我們將會看到,這種設計上的差異對資料庫的使用方式有著深遠的影響。
|
||||
在一些無領導者的實現中,客戶端直接將寫入傳送到幾個副本中,而另一些情況下,一個 **協調者(coordinator)** 節點代表客戶端進行寫入。但與主庫資料庫不同,協調者不執行特定的寫入順序。我們將會看到,這種設計上的差異對資料庫的使用方式有著深遠的影響。
|
||||
|
||||
### 當節點故障時寫入資料庫
|
||||
|
||||
@ -513,7 +513,7 @@
|
||||
|
||||
#### 讀寫的法定人數
|
||||
|
||||
在[圖5-10](../img/fig5-10.png)的示例中,我們認為即使僅在三個副本中的兩個上進行處理,寫入仍然是成功的。如果三個副本中只有一個接受了寫入,會怎樣?我們能推多遠呢?
|
||||
在[圖5-10](../img/fig5-10.png)的示例中,我們認為即使僅在三個副本中的兩個上進行處理,寫入仍然是成功的。如果三個副本中只有一個接受了寫入,會怎樣?以此類推,究竟多少個副本完成才可以認為寫成功?
|
||||
|
||||
如果我們知道,每個成功的寫操作意味著在三個副本中至少有兩個出現,這意味著至多有一個副本可能是陳舊的。因此,如果我們從至少兩個副本讀取,我們可以確定至少有一個是最新的。如果第三個副本停機或響應速度緩慢,則讀取仍可以繼續返回最新值。
|
||||
|
||||
@ -573,7 +573,7 @@
|
||||
|
||||
然而,在無領導者複製的系統中,沒有固定的寫入順序,這使得監控變得更加困難。而且,如果資料庫只使用讀修復(沒有反熵過程),那麼對於一個值可能會有多大的限制是沒有限制的 - 如果一個值很少被讀取,那麼由一個陳舊副本返回的值可能是古老的。
|
||||
|
||||
已經有一些關於衡量無主複製資料庫中的複製陳舊度的研究,並根據引數n,w和r來預測陳舊讀取的預期百分比【48】。不幸的是,這還不是很常見的做法,但是將陳舊測量值包含在資料庫的度量標準集中是一件好事。最終一致性是一種有意模糊的保證,但是從可操作性角度來說,能夠量化“最終”是很重要的。
|
||||
已經有一些關於衡量無主複製資料庫中的複製陳舊度的研究,並根據引數n,w和r來預測陳舊讀取的預期百分比【48】。不幸的是,這還不是很常見的做法,但是將陳舊測量值包含在資料庫的度量標準集中是一件好事。雖然最終一致性是一種有意模糊的保證,但是從可操作性角度來說,能夠量化“最終”也是很重要的。
|
||||
|
||||
### 寬鬆的法定人數與提示移交
|
||||
|
||||
@ -668,7 +668,7 @@
|
||||
[圖5-13](../img/fig5-13.png)顯示了兩個客戶端同時向同一購物車新增專案。 (如果這樣的例子讓你覺得太麻煩了,那麼可以想象,兩個空中交通管制員同時把飛機新增到他們正在跟蹤的區域)最初,購物車是空的。在它們之間,客戶端向資料庫發出五次寫入:
|
||||
|
||||
1. 客戶端 1 將牛奶加入購物車。這是該鍵的第一次寫入,伺服器成功儲存了它併為其分配版本號1,最後將值與版本號一起回送給客戶端。
|
||||
2. 客戶端 2 將雞蛋加入購物車,不知道客戶端 1 同時添加了牛奶(客戶端 2 認為它的雞蛋是購物車中的唯一物品)。伺服器為此寫入分配版本號 2,並將雞蛋和牛奶儲存為兩個單獨的值。然後它將這兩個值**都**反回給客戶端 2 ,並附上版本號 2 。
|
||||
2. 客戶端 2 將雞蛋加入購物車,不知道客戶端 1 同時添加了牛奶(客戶端 2 認為它的雞蛋是購物車中的唯一物品)。伺服器為此寫入分配版本號 2,並將雞蛋和牛奶儲存為兩個單獨的值。然後它將這兩個值**都**返回給客戶端 2 ,並附上版本號 2 。
|
||||
3. 客戶端 1 不知道客戶端 2 的寫入,想要將麵粉加入購物車,因此認為當前的購物車內容應該是 [牛奶,麵粉]。它將此值與伺服器先前向客戶端 1 提供的版本號 1 一起傳送到伺服器。伺服器可以從版本號中知道[牛奶,麵粉]的寫入取代了[牛奶]的先前值,但與[雞蛋]的值是**併發**的。因此,伺服器將版本 3 分配給[牛奶,麵粉],覆蓋版本1值[牛奶],但保留版本 2 的值[蛋],並將所有的值返回給客戶端 1 。
|
||||
4. 同時,客戶端 2 想要加入火腿,不知道客端戶 1 剛剛加了麵粉。客戶端 2 在最後一個響應中從伺服器收到了兩個值[牛奶]和[蛋],所以客戶端 2 現在合併這些值,並新增火腿形成一個新的值,[雞蛋,牛奶,火腿]。它將這個值傳送到伺服器,帶著之前的版本號 2 。伺服器檢測到新值會覆蓋版本 2 [雞蛋],但新值也會與版本 3 [牛奶,麵粉]**併發**,所以剩下的兩個是v3 [牛奶,麵粉],和v4:[雞蛋,牛奶,火腿]
|
||||
5. 最後,客戶端 1 想要加培根。它以前在v3中從伺服器接收[牛奶,麵粉]和[雞蛋],所以它合併這些,新增培根,並將最終值[牛奶,麵粉,雞蛋,培根]連同版本號v3發往伺服器。這會覆蓋v3[牛奶,麵粉](請注意[雞蛋]已經在最後一步被覆蓋),但與v4[雞蛋,牛奶,火腿]併發,所以伺服器保留這兩個併發值。
|
||||
@ -700,7 +700,7 @@
|
||||
|
||||
以購物車為例,一種合理的合併兄弟方法就是集合求並集。在[圖5-14](../img/fig5-14.png)中,最後的兩個兄弟是[牛奶,麵粉,雞蛋,燻肉]和[雞蛋,牛奶,火腿]。注意牛奶和雞蛋同時出現在兩個兄弟裡,即使他們每個只被寫過一次。合併的值可以是[牛奶,麵粉,雞蛋,培根,火腿],沒有重複。
|
||||
|
||||
然而,如果你想讓人們也可以從他們的手推車中**刪除**東西,而不是僅僅新增東西,那麼把兄弟求並集可能不會產生正確的結果:如果你合併了兩個兄弟手推車,並且只在其中一個兄弟值裡刪掉了它,那麼被刪除的專案會重新出現在兄弟的並集中【37】。為了防止這個問題,一個專案在刪除時不能簡單地從資料庫中刪除;相反,系統必須留下一個具有合適版本號的標記,以指示合併兄弟時該專案已被刪除。這種刪除標記被稱為**墓碑(tombstone)**。 (我們之前在“[雜湊索引”](ch3.md#雜湊索引)中的日誌壓縮的上下文中看到了墓碑。)
|
||||
然而,如果你想讓人們也可以從他們的購物車中**刪除**東西,而不是僅僅新增東西,那麼把兄弟求並集可能不會產生正確的結果:如果你合併了兩個兄弟購物車,並且只在其中一個兄弟值裡刪掉了它,那麼被刪除的專案會重新出現在並集終值中【37】。為了防止這個問題,一個專案在刪除時不能簡單地從資料庫中刪除;相反,系統必須留下一個具有合適版本號的標記,以指示合併兄弟時該專案已被刪除。這種刪除標記被稱為**墓碑(tombstone)**。 (我們之前在“[雜湊索引”](ch3.md#雜湊索引)中的日誌壓縮的上下文中看到了墓碑。)
|
||||
|
||||
因為在應用程式程式碼中合併兄弟是複雜且容易出錯的,所以有一些資料結構被設計出來用於自動執行這種合併,如“[自動衝突解決](#自動衝突解決)”中討論的。例如,Riak的資料型別支援使用稱為CRDT的資料結構家族【38,39,55】可以以合理的方式自動合併兄弟,包括保留刪除。
|
||||
|
||||
|
33
zh-tw/ch6.md
33
zh-tw/ch6.md
@ -117,33 +117,33 @@
|
||||
|
||||
到目前為止,我們討論的分割槽方案依賴於鍵值資料模型。如果只通過主鍵訪問記錄,我們可以從該鍵確定分割槽,並使用它來將讀寫請求路由到負責該鍵的分割槽。
|
||||
|
||||
如果涉及次級索引,情況會變得更加複雜(參考“[其他索引結構](ch3.md#其他索引結構)”)。輔助索引通常並不能唯一地標識記錄,而是一種搜尋記錄中出現特定值的方式:查詢使用者123的所有操作,查詢包含詞語`hogwash`的所有文章,查詢所有顏色為紅色的車輛等等。
|
||||
如果涉及次級索引,情況會變得更加複雜(參考“[其他索引結構](ch3.md#其他索引結構)”)。次級索引通常並不能唯一地標識記錄,而是一種搜尋記錄中出現特定值的方式:查詢使用者123的所有操作,查詢包含詞語`hogwash`的所有文章,查詢所有顏色為紅色的車輛等等。
|
||||
|
||||
次級索引是關係型資料庫的基礎,並且在文件資料庫中也很普遍。許多鍵值儲存(如HBase和Volde-mort)為了減少實現的複雜度而放棄了次級索引,但是一些(如Riak)已經開始新增它們,因為它們對於資料模型實在是太有用了。並且次級索引也是Solr和Elasticsearch等搜尋伺服器的基石。
|
||||
|
||||
次級索引的問題是它們不能整齊地對映到分割槽。有兩種用二級索引對資料庫進行分割槽的方法:**基於文件的分割槽(document-based)** 和**基於關鍵詞(term-based)的分割槽**。
|
||||
次級索引的問題是它們不能整齊地對映到分割槽。有兩種用次級索引對資料庫進行分割槽的方法:**基於文件的分割槽(document-based)** 和**基於關鍵詞(term-based)的分割槽**。
|
||||
|
||||
### 基於文件的二級索引進行分割槽
|
||||
### 基於文件的次級索引進行分割槽
|
||||
|
||||
假設你正在經營一個銷售二手車的網站(如[圖6-4](../img/fig6-4.png)所示)。 每個列表都有一個唯一的ID——稱之為文件ID——並且用文件ID對資料庫進行分割槽(例如,分割槽0中的ID 0到499,分割槽1中的ID 500到999等)。
|
||||
|
||||
你想讓使用者搜尋汽車,允許他們透過顏色和廠商過濾,所以需要一個在顏色和廠商上的次級索引(文件資料庫中這些是**欄位(field)**,關係資料庫中這些是**列(column)** )。 如果您聲明瞭索引,則資料庫可以自動執行索引[^ii]。例如,無論何時將紅色汽車新增到資料庫,資料庫分割槽都會自動將其新增到索引條目`color:red`的文件ID列表中。
|
||||
|
||||
[^ii]: 如果資料庫僅支援鍵值模型,則你可能會嘗試在應用程式程式碼中建立從值到文件ID的對映來實現輔助索引。 如果沿著這條路線走下去,請萬分小心,確保您的索引與底層資料保持一致。 競爭條件和間歇性寫入失敗(其中一些更改已儲存,但其他更改未儲存)很容易導致資料不同步 - 請參閱“[多物件事務的需求](ch7.md#多物件事務的需求)”。
|
||||
[^ii]: 如果資料庫僅支援鍵值模型,則你可能會嘗試在應用程式程式碼中建立從值到文件ID的對映來實現次級索引。 如果沿著這條路線走下去,請萬分小心,確保您的索引與底層資料保持一致。 競爭條件和間歇性寫入失敗(其中一些更改已儲存,但其他更改未儲存)很容易導致資料不同步 - 請參閱“[多物件事務的需求](ch7.md#多物件事務的需求)”。
|
||||
|
||||
![](../img/fig6-4.png)
|
||||
|
||||
**圖6-4 基於文件的二級索引進行分割槽**
|
||||
**圖6-4 基於文件的次級索引進行分割槽**
|
||||
|
||||
在這種索引方法中,每個分割槽是完全獨立的:每個分割槽維護自己的二級索引,僅覆蓋該分割槽中的文件。它不關心儲存在其他分割槽的資料。無論何時您需要寫入資料庫(新增,刪除或更新文件),只需處理包含您正在編寫的文件ID的分割槽即可。出於這個原因,**文件分割槽索引**也被稱為**本地索引(local index)**(而不是將在下一節中描述的**全域性索引(global index)**)。
|
||||
在這種索引方法中,每個分割槽是完全獨立的:每個分割槽維護自己的次級索引,僅覆蓋該分割槽中的文件。它不關心儲存在其他分割槽的資料。無論何時您需要寫入資料庫(新增,刪除或更新文件),只需處理包含您正在編寫的文件ID的分割槽即可。出於這個原因,**文件分割槽索引**也被稱為**本地索引(local index)**(而不是將在下一節中描述的**全域性索引(global index)**)。
|
||||
|
||||
但是,從文件分割槽索引中讀取需要注意:除非您對文件ID做了特別的處理,否則沒有理由將所有具有特定顏色或特定品牌的汽車放在同一個分割槽中。在[圖6-4](../img/fig6-4.png)中,紅色汽車出現在分割槽0和分割槽1中。因此,如果要搜尋紅色汽車,則需要將查詢傳送到所有分割槽,併合並所有返回的結果。
|
||||
|
||||
|
||||
這種查詢分割槽資料庫的方法有時被稱為**分散/聚集(scatter/gather)**,並且可能會使二級索引上的讀取查詢相當昂貴。即使並行查詢分割槽,分散/聚集也容易導致尾部延遲放大(請參閱“[實踐中的百分位點](ch1.md#實踐中的百分位點)”)。然而,它被廣泛使用:MongoDB,Riak 【15】,Cassandra 【16】,Elasticsearch 【17】,SolrCloud 【18】和VoltDB 【19】都使用文件分割槽二級索引。大多數資料庫供應商建議您構建一個能從單個分割槽提供二級索引查詢的分割槽方案,但這並不總是可行,尤其是當在單個查詢中使用多個二級索引時(例如同時需要按顏色和製造商查詢)。
|
||||
這種查詢分割槽資料庫的方法有時被稱為**分散/聚集(scatter/gather)**,並且可能會使次級索引上的讀取查詢相當昂貴。即使並行查詢分割槽,分散/聚集也容易導致尾部延遲放大(請參閱“[實踐中的百分位點](ch1.md#實踐中的百分位點)”)。然而,它被廣泛使用:MongoDB,Riak 【15】,Cassandra 【16】,Elasticsearch 【17】,SolrCloud 【18】和VoltDB 【19】都使用文件分割槽次級索引。大多數資料庫供應商建議您構建一個能從單個分割槽提供次級索引查詢的分割槽方案,但這並不總是可行,尤其是當在單個查詢中使用多個次級索引時(例如同時需要按顏色和製造商查詢)。
|
||||
|
||||
|
||||
### 基於關鍵詞(Term)的二級索引進行分割槽
|
||||
### 基於關鍵詞(Term)的次級索引進行分割槽
|
||||
|
||||
我們可以構建一個覆蓋所有分割槽資料的**全域性索引**,而不是給每個分割槽建立自己的次級索引(本地索引)。但是,我們不能只把這個索引儲存在一個節點上,因為它可能會成為瓶頸,違背了分割槽的目的。全域性索引也必須進行分割槽,但可以採用與主鍵不同的分割槽方式。
|
||||
|
||||
@ -151,7 +151,7 @@
|
||||
|
||||
![](../img/fig6-5.png)
|
||||
|
||||
**圖6-5 基於關鍵詞對二級索引進行分割槽**
|
||||
**圖6-5 基於關鍵詞對次級索引進行分割槽**
|
||||
|
||||
我們將這種索引稱為**關鍵詞分割槽(term-partitioned)**,因為我們尋找的關鍵詞決定了索引的分割槽方式。例如,一個關鍵詞可能是:`color:red`。**關鍵詞(Term)** 這個名稱來源於全文搜尋索引(一種特殊的次級索引),指文件中出現的所有單詞。
|
||||
|
||||
@ -161,9 +161,9 @@
|
||||
|
||||
理想情況下,索引總是最新的,寫入資料庫的每個文件都會立即反映在索引中。但是,在關鍵詞分割槽索引中,這需要跨分割槽的分散式事務,並不是所有資料庫都支援(請參閱[第七章](ch7.md)和[第九章](ch9.md))。
|
||||
|
||||
在實踐中,對全域性二級索引的更新通常是**非同步**的(也就是說,如果在寫入之後不久讀取索引,剛才所做的更改可能尚未反映在索引中)。例如,Amazon DynamoDB聲稱在正常情況下,其全域性次級索引會在不到一秒的時間內更新,但在基礎架構出現故障的情況下可能會有延遲【20】。
|
||||
在實踐中,對全域性次級索引的更新通常是**非同步**的(也就是說,如果在寫入之後不久讀取索引,剛才所做的更改可能尚未反映在索引中)。例如,Amazon DynamoDB聲稱在正常情況下,其全域性次級索引會在不到一秒的時間內更新,但在基礎架構出現故障的情況下可能會有延遲【20】。
|
||||
|
||||
全域性關鍵詞分割槽索引的其他用途包括Riak的搜尋功能【21】和Oracle資料倉庫,它允許您在本地和全域性索引之間進行選擇【22】。我們將在[第十二章](ch12.md)中繼續關鍵詞分割槽二級索引實現的話題。
|
||||
全域性關鍵詞分割槽索引的其他用途包括Riak的搜尋功能【21】和Oracle資料倉庫,它允許您在本地和全域性索引之間進行選擇【22】。我們將在[第十二章](ch12.md)中繼續關鍵詞分割槽次級索引實現的話題。
|
||||
|
||||
## 分割槽再平衡
|
||||
|
||||
@ -268,7 +268,7 @@
|
||||
|
||||
**圖6-7 將請求路由到正確節點的三種不同方式。**
|
||||
|
||||
這是一個具有挑戰性的問題,因為重要的是所有參與者都同意 - 否則請求將被髮送到錯誤的節點,得不到正確的處理。 在分散式系統中有達成共識的協議,但很難正確地實現(見[第九章](ch9.md))。
|
||||
這是一個具有挑戰性的問題,因為重要的是所有參與者都達成共識 - 否則請求將被髮送到錯誤的節點,得不到正確的處理。 在分散式系統中有達成共識的協議,但很難正確地實現(見[第九章](ch9.md))。
|
||||
|
||||
許多分散式資料系統都依賴於一個獨立的協調服務,比如ZooKeeper來跟蹤叢集元資料,如[圖6-8](../img/fig6-8.png)所示。 每個節點在ZooKeeper中註冊自己,ZooKeeper維護分割槽到節點的可靠對映。 其他參與者(如路由層或分割槽感知客戶端)可以在ZooKeeper中訂閱此資訊。 只要分割槽分配發生了改變,或者叢集中新增或刪除了一個節點,ZooKeeper就會通知路由層使路由資訊保持最新狀態。
|
||||
|
||||
@ -286,7 +286,7 @@
|
||||
|
||||
### 執行並行查詢
|
||||
|
||||
到目前為止,我們只關注讀取或寫入單個鍵的非常簡單的查詢(加上基於文件分割槽的二級索引場景下的分散/聚集查詢)。這也是大多數NoSQL分散式資料儲存所支援的訪問層級。
|
||||
到目前為止,我們只關注讀取或寫入單個鍵的非常簡單的查詢(加上基於文件分割槽的次級索引場景下的分散/聚集查詢)。這也是大多數NoSQL分散式資料儲存所支援的訪問層級。
|
||||
|
||||
然而,通常用於分析的**大規模並行處理(MPP, Massively parallel processing)** 關係型資料庫產品在其支援的查詢型別方面要複雜得多。一個典型的資料倉庫查詢包含多個連線,過濾,分組和聚合操作。 MPP查詢最佳化器將這個複雜的查詢分解成許多執行階段和分割槽,其中許多可以在資料庫叢集的不同節點上並行執行。涉及掃描大規模資料集的查詢特別受益於這種並行執行。
|
||||
|
||||
@ -314,10 +314,10 @@
|
||||
|
||||
兩種方法搭配使用也是可行的,例如使用複合主鍵:使用鍵的一部分來標識分割槽,而使用另一部分作為排序順序。
|
||||
|
||||
我們還討論了分割槽和二級索引之間的相互作用。次級索引也需要分割槽,有兩種方法:
|
||||
我們還討論了分割槽和次級索引之間的相互作用。次級索引也需要分割槽,有兩種方法:
|
||||
|
||||
* 基於文件分割槽(本地索引),其中二級索引儲存在與主鍵和值相同的分割槽中。這意味著只有一個分割槽需要在寫入時更新,但是讀取二級索引需要在所有分割槽之間進行分散/收集。
|
||||
* 基於關鍵詞分割槽(全域性索引),其中二級索引存在不同的分割槽中。輔助索引中的條目可以包括來自主鍵的所有分割槽的記錄。當文件寫入時,需要更新多個分割槽中的二級索引;但是可以從單個分割槽中進行讀取。
|
||||
* 基於文件分割槽(本地索引),其中次級索引儲存在與主鍵和值相同的分割槽中。這意味著只有一個分割槽需要在寫入時更新,但是讀取次級索引需要在所有分割槽之間進行分散/收集。
|
||||
* 基於關鍵詞分割槽(全域性索引),其中次級索引存在不同的分割槽中。次級索引中的條目可以包括來自主鍵的所有分割槽的記錄。當文件寫入時,需要更新多個分割槽中的次級索引;但是可以從單個分割槽中進行讀取。
|
||||
|
||||
最後,我們討論了將查詢路由到適當的分割槽的技術,從簡單的分割槽負載平衡到複雜的並行查詢執行引擎。
|
||||
|
||||
@ -406,4 +406,3 @@
|
||||
| 上一章 | 目錄 | 下一章 |
|
||||
| :--------------------: | :-----------------------------: | :--------------------: |
|
||||
| [第五章:複製](ch5.md) | [設計資料密集型應用](README.md) | [第七章:事務](ch7.md) |
|
||||
|
||||
|
@ -191,7 +191,7 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true
|
||||
|
||||
* 在關係資料模型中,一個表中的行通常具有對另一個表中的行的外來鍵引用。(類似的是,在一個圖資料模型中,一個頂點有著到其他頂點的邊)。多物件事務使你確保這些引用始終有效:當插入幾個相互引用的記錄時,外來鍵必須是正確的和最新的,不然資料就沒有意義。
|
||||
* 在文件資料模型中,需要一起更新的欄位通常在同一個文件中,這被視為單個物件——更新單個文件時不需要多物件事務。但是,缺乏連線功能的文件資料庫會鼓勵非規範化(請參閱“[關係型資料庫與文件資料庫在今日的對比](ch2.md#關係型資料庫與文件資料庫在今日的對比)”)。當需要更新非規範化的資訊時,如 [圖7-2](../img/fig7-2.png) 所示,需要一次更新多個文件。事務在這種情況下非常有用,可以防止非規範化的資料不同步。
|
||||
* 在具有二級索引的資料庫中(除了純粹的鍵值儲存以外幾乎都有),每次更改值時都需要更新索引。從事務角度來看,這些索引是不同的資料庫物件:例如,如果沒有事務隔離性,記錄可能出現在一個索引中,但沒有出現在另一個索引中,因為第二個索引的更新還沒有發生。
|
||||
* 在具有次級索引的資料庫中(除了純粹的鍵值儲存以外幾乎都有),每次更改值時都需要更新索引。從事務角度來看,這些索引是不同的資料庫物件:例如,如果沒有事務隔離性,記錄可能出現在一個索引中,但沒有出現在另一個索引中,因為第二個索引的更新還沒有發生。
|
||||
|
||||
這些應用仍然可以在沒有事務的情況下實現。然而,**沒有原子性,錯誤處理就要複雜得多,缺乏隔離性,就會導致併發問題**。我們將在“[弱隔離級別](#弱隔離級別)”中討論這些問題,並在[第十二章](ch12.md)中探討其他方法。
|
||||
|
||||
@ -651,7 +651,7 @@ VoltDB還使用儲存過程進行復制:但不是將事務的寫入結果從
|
||||
|
||||
由於跨分割槽事務具有額外的協調開銷,所以它們比單分割槽事務慢得多。 VoltDB報告的吞吐量大約是每秒1000個跨分割槽寫入,比單分割槽吞吐量低幾個數量級,並且不能透過增加更多的機器來增加【49】。
|
||||
|
||||
事務是否可以是劃分至單個分割槽很大程度上取決於應用資料的結構。簡單的鍵值資料通常可以非常容易地進行分割槽,但是具有多個二級索引的資料可能需要大量的跨分割槽協調(請參閱“[分割槽與次級索引](ch6.md#分割槽與次級索引)”)。
|
||||
事務是否可以是劃分至單個分割槽很大程度上取決於應用資料的結構。簡單的鍵值資料通常可以非常容易地進行分割槽,但是具有多個次級索引的資料可能需要大量的跨分割槽協調(請參閱“[分割槽與次級索引](ch6.md#分割槽與次級索引)”)。
|
||||
|
||||
#### 序列執行小結
|
||||
|
||||
|
@ -586,7 +586,7 @@
|
||||
|
||||
因此,在單個節點上,事務的提交主要取決於資料持久化落盤的**順序**:首先是資料,然後是提交記錄【72】。事務提交或終止的關鍵決定時刻是磁碟完成寫入提交記錄的時刻:在此之前,仍有可能中止(由於崩潰),但在此之後,事務已經提交(即使資料庫崩潰)。因此,是單一的裝置(連線到單個磁碟的控制器,且掛載在單臺機器上)使得提交具有原子性。
|
||||
|
||||
但是,如果一個事務中涉及多個節點呢?例如,你也許在分割槽資料庫中會有一個多物件事務,或者是一個按關鍵詞分割槽的二級索引(其中索引條目可能位於與主資料不同的節點上;請參閱“[分割槽與次級索引](ch6.md#分割槽與次級索引)”)。大多數“NoSQL”分散式資料儲存不支援這種分散式事務,但是很多關係型資料庫叢集支援(請參閱“[實踐中的分散式事務](#實踐中的分散式事務)”)。
|
||||
但是,如果一個事務中涉及多個節點呢?例如,你也許在分割槽資料庫中會有一個多物件事務,或者是一個按關鍵詞分割槽的次級索引(其中索引條目可能位於與主資料不同的節點上;請參閱“[分割槽與次級索引](ch6.md#分割槽與次級索引)”)。大多數“NoSQL”分散式資料儲存不支援這種分散式事務,但是很多關係型資料庫叢集支援(請參閱“[實踐中的分散式事務](#實踐中的分散式事務)”)。
|
||||
|
||||
在這些情況下,僅向所有節點發送提交請求並獨立提交每個節點的事務是不夠的。這樣很容易發生違反原子性的情況:提交在某些節點上成功,而在其他節點上失敗:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user