第一章 可靠性、可擴展性、可維護性
? 可靠性: 系統在困境(adversity)(硬體故障、軟體故障、人為錯誤)中仍可正常作業(正確完成功能,并能達到期望的性能水準,
? 可靠性(Reliability) 意味著即使發生故障,系統也能正常作業,故障可能發生在硬體(通常是隨機的和不相關的),軟體(通常是系統性的Bug,很難處理),和人類(不可避免地時不時出錯), 容錯技術 可以對終端用戶隱藏某些型別的故障,
? 可擴展性: 有合理的辦法應對系統的增長(資料量、流量、復雜性)
? 可擴展性(Scalability) 意味著即使在負載增加的情況下也有保持性能的策略,為了討論可擴展性,我們首先需要定量描述負載和性能的方法,我們簡要了解了推特主頁時間線的例子,介紹描述負載的方法,并將回應時間百分位點作為衡量性能的一種方式,在可擴展的系統中可以添加 處理容量(processing capacity) 以在高負載下保持可靠,
? 可維護性:許多不同的人(工程師、運維)在不同的生命周期,都能高效地在系統上作業(使系統保持現有行為,并適應新的應用場景)
? 可維護性(Maintainability) 有許多方面,但實質上是關于工程師和運維團隊的生活質量的,良好的抽象可以幫助降低復雜度,并使系統易于修改和適應新的應用場景,良好的可操作性意味著對系統的健康狀態具有良好的可見性,并擁有有效的管理手段,
第二章 資料模型與查詢語言
? 檔案資料庫的應用場景是:資料通常是自我包含的,而且檔案之間的關系非常稀少
? 圖形資料庫用于相反的場景:任意事物都可能與任何事物相關聯
第三章 存盤與檢索
? 在高層次上,我們看到存盤引擎分為兩大類:優化 事務處理(OLTP) 或 在線分析(OLAP) ,這些用例的訪問模式之間有很大的區別:
-
OLTP系統通常面向用戶,這意味著系統可能會收到大量的請求,為了處理負載,應用程式通常只訪問每個查詢中的少部分記錄,應用程式使用某種鍵來請求記錄,存盤引擎使用索引來查找所請求的鍵的資料,磁盤尋道時間往往是這里的瓶頸,
-
資料倉庫和類似的分析系統會低調一些,因為它們主要由業務分析人員使用,而不是由最終用戶使用,它們的查詢量要比OLTP系統少得多,但通常每個查詢開銷高昂,需要在短時間內掃描數百萬條記錄,磁盤帶寬(而不是查找時間)往往是瓶頸,列式存盤是這種作業負載越來越流行的解決方案,
第四章 編碼與演化
? Json XML 編碼 --> 二進制編碼的發展
? 二進制編碼技術:Apache Thrift / Protocol Buffers(protobuf)/

? 服務中的資料流:REST & RPC
? REST不是一個協議,而是一個基于HTTP原則的設計哲學,它強調簡單的資料格式,使用URL來標識資源,并使用HTTP功能進行快取控制,身份驗證和內容型別協商,與SOAP相比,REST已經越來越受歡迎,至少在跨組織服務集成的背景下,并經常與微服務相關,根據REST原則設計的API稱為restful, 通常涉及較少的代碼生成和自動化工具,
? RPC呼叫和本地函式呼叫的不同:
- 本地函式呼叫是可預測的,并且成功或失敗,這僅取決于受您控制的引數,網路請求是不可預知的:由于網路問題,請求或回應可能會丟失,或者遠程計算機可能很慢或不可用,這些問題完全不在您的控制范圍之內,網路問題是常見的,所以你必須預測他們,例如通過重試失敗的請求,
- 本地函式呼叫要么回傳結果,要么拋出例外,或者永遠不回傳(因為進入無限回圈或行程崩潰),網路請求有另一個可能的結果:由于超時,它可能會回傳沒有結果,在這種情況下,你根本不知道發生了什么:如果你沒有得到來自遠程服務的回應,你無法知道請求是否通過,
- 如果您重試失敗的網路請求,可能會發生請求實際上正在通過,只有回應丟失,在這種情況下,重試將導致該操作被執行多次,除非您在協議中引入除重( 冪等(idempotence))機制,本地函式呼叫沒有這個問題,
- 每次呼叫本地功能時,通常需要大致相同的時間來執行,網路請求比函式呼叫要慢得多,而且其延遲也是非常可變的:在不到一毫秒的時間內它可能會完成,但是當網路擁塞或者遠程服務超載時,可能需要幾秒鐘的時間完全一樣的東西,
- 呼叫本地函式時,可以高效地將參考(指標)傳遞給本地記憶體中的物件,當你發出一個網路請求時,所有這些引數都需要被編碼成可以通過網路發送的一系列位元組,沒關系,如果引數是像數字或字串這樣的基本型別,但是對于較大的物件很快就會變成問題,
第五章 復制
? 復制意味著在通過網路連接的多臺機器上保留相同資料的副本,需要復制的原因:
- 使得資料與用戶在地理上接近(從而減少延遲)
- 即使系統的一部分出現故障,系統也能繼續作業(從而提高可用性)
- 擴展可以接受讀請求的機器數量(從而提高讀取吞吐量)
? 復制演算法:單領導者(single leader),多領導者(multi leader)和無領導者(leaderless)
? 基于領導者的復制原理:
- 副本之一被指定為 領導者(leader),也稱為 主庫(master|primary) ,當客戶端要向資料庫寫入時,它必須將請求發送給領導者,領導者會將新資料寫入其本地存盤,
- 其他副本被稱為追隨者(followers),亦稱為只讀副本(read replicas),從庫(slaves),備庫( sencondaries),熱備(hot-standby)i,每當領導者將新資料寫入本地存盤時,它也會將資料變更發送給所有的追隨者,稱之為復制日志(replication log)記錄或變更流(change stream),每個跟隨者從領導者拉取日志,并相應更新其本地資料庫副本,方法是按照領導者處理的相同順序應用所有寫入,
- 當客戶想要從資料庫中讀取資料時,它可以向領導者或追隨者查詢, 但只有領導者才能接受寫操作(從客戶端的角度來看從庫都是只讀的),
? 
? 同步復制和異步復制:
? 
? 同步復制的優點是,從庫保證有與主庫一致的最新資料副本,如果主庫突然失效,我們可以確信這些資料仍然能在從庫上上找到,缺點是,如果同步從庫沒有回應(比如它已經崩潰,或者出現網路故障,或其它任何原因),主庫就無法處理寫入操作,主庫必須阻止所有寫入,并等待同步副本再次可用,
? 因此,將所有從庫都設定為同步的是不切實際的:任何一個節點的中斷都會導致整個系統停滯不前,實際上,如果在資料庫上啟用同步復制,通常意味著其中一個跟隨者是同步的,而其他的則是異步的,如果同步從庫變得不可用或緩慢,則使一個異步從庫同步,這保證你至少在兩個節點上擁有最新的資料副本:主庫和同步從庫, 這種配置有時也被稱為 半同步,
? 通常情況下,基于領導者的復制都配置為完全異步, 在這種情況下,如果主庫失效且不可恢復,則任何尚未復制給從庫的寫入都會丟失, 這意味著即使已經向客戶端確認成功,寫入也不能保證 持久(Durable), 然而,一個完全異步的配置也有優點:即使所有的從庫都落后了,主庫也可以繼續處理寫入,
? 如何確保新的從庫擁有主庫資料的精確副本?
- 在某個時刻獲取主庫的一致性快照(如果可能),而不必鎖定整個資料庫,大多數資料庫都具有這個功能,因為它是備份必需的,對于某些場景,可能需要第三方工具,例如MySQL的innobackupex 【12】,
- 將快照復制到新的從庫節點,
- 從庫連接到主庫,并拉取快照之后發生的所有資料變更,這要求快照與主庫復制日志中的位置精確關聯,該位置有不同的名稱:例如,PostgreSQL將其稱為 日志序列號(log sequence number, LSN),MySQL將其稱為 二進制日志坐標(binlog coordinates),
- 當從庫處理完快照之后積壓的資料變更,我們說它趕上(caught up)了主庫,現在它可以繼續處理主庫產生的資料變化了,
? 節點宕機:
? 從庫失效:追趕恢復
? 在其本地磁盤上,每個從庫記錄從主庫收到的資料變更,如果從庫崩潰并重新啟動,或者,如果主庫和從庫之間的網路暫時中斷,則比較容易恢復:從庫可以從日志中知道,在發生故障之前處理的最后一個事務,因此,從庫可以連接到主庫,并請求在從庫斷開連接時發生的所有資料變更,當應用完所有這些變化后,它就趕上了主庫,并可以像以前一樣繼續接收資料變更流,
? 主庫失效:故障切換
- 確認主庫失效,有很多事情可能會出錯:崩潰,停電,網路問題等等,沒有萬無一失的方法來檢測出現了什么問題,所以大多數系統只是簡單使用 超時(Timeout) :節點頻繁地相互來回傳遞訊息,并且如果一個節點在一段時間內(例如30秒)沒有回應,就認為它掛了(因為計劃內維護而故意關閉主庫不算),
- 選擇一個新的主庫,這可以通過選舉程序(主庫由剩余副本以多數選舉產生)來完成,或者可以由之前選定的控制器節點(controller node)來指定新的主庫,主庫的最佳人選通常是擁有舊主庫最新資料副本的從庫(最小化資料損失),讓所有的節點同意一個新的領導者,是一個共識問題,將在第9章詳細討論,
- 重新配置系統以啟用新的主庫,客戶端現在需要將它們的寫請求發送給新主庫,如果老領導回來,可能仍然認為自己是主庫,沒有意識到其他副本已經讓它下臺了,系統需要確保老領導認可新領導,成為一個從庫,
故障切換的麻煩:
? 如果使用異步復制,則新主庫可能沒有收到老主庫宕機前最后的寫入操作
? 如果資料庫需要和其他外部存盤相協調,那么丟棄寫入內容是極其危險的操作,
? 發生某些故障時可能會出現兩個節點都以為自己是主庫的情況,這種情況稱為 腦裂(split brain),一些系統采取了安全防范措施:當檢測到兩個主庫節點同時存在時會關閉其中一個節點ii,但設計粗糙的機制可能最后會導致兩個節點都被關閉,
復制日志的實作:
? 1、基于陳述句的復制
? 2、傳輸預寫式日志(WAL)
? 3、邏輯日志復制(基于行)
? 另一種方法是,復制和存盤引擎使用不同的日志格式,這樣可以使復制日志從存盤引擎內部分離出來,這種復制日志被稱為邏輯日志,以將其與存盤引擎的(物理)資料表示區分開來,
? 4、基于觸發器的復制
復制延遲問題
? 1、讀己之寫
? 
? 圖 用戶寫入后從舊副本中讀取資料,需要寫后讀(read-after-write)的一致性來防止這種例外
? 2、單調讀

? 圖 用戶首先從新副本讀取,然后從舊副本讀取,時光倒流,為了防止這種例外,我們需要單調的讀取,
? 3、一致前綴讀
? 
? 圖 如果某些磁區的復制速度慢于其他磁區,那么觀察者在看到問題之前可能會看到答案,
復制延遲問題的解決方案:事務(性能和可用性代價過高)和其他替代機制
多主復制
? 應用場景
? 1、運維多個資料中心
? 
? 圖 跨多個資料中心的多主復制
? 2、需要離線的客戶端
? 考慮手機,筆記本電腦和其他設備上的日歷應用,無論設備目前是否有互聯網連接,你需要能隨時查看你的會議(發出讀取請求),輸入新的會議(發出寫入請求),如果在離線狀態下進行任何更改,則設備下次上線時,需要與服務器和其他設備同步,
在這種情況下,每個設備都有一個充當領導者的本地資料庫(它接受寫請求),并且在所有設備上的日歷副本之間同步時,存在異步的多主復制程序,復制延遲可能是幾小時甚至幾天,具體取決于何時可以訪問互聯網,
從架構的角度來看,這種設定實際上與資料中心之間的多領導者復制類似,每個設備都是一個“資料中心”,而它們之間的網路連接是極度不可靠的,從歷史上各類日歷同步功能的破爛實作可以看出,想把多活配好是多么困難的一件事,
? 3、協同編輯
? 我們通常不會將協作式編輯視為資料庫復制問題,但與前面提到的離線編輯用例有許多相似之處,當一個用戶編輯檔案時,所做的更改將立即應用到其本地副本(Web瀏覽器或客戶端應用程式中的檔案狀態),并異步復制到服務器和編輯同一檔案的任何其他用戶,
如果要保證不會發生編輯沖突,則應用程式必須先取得檔案的鎖定,然后用戶才能對其進行編輯,如果另一個用戶想要編輯同一個檔案,他們首先必須等到第一個用戶提交修改并釋放鎖定,這種協作模式相當于在領導者上進行交易的單領導者復制,
但是,為了加速協作,您可能希望將更改的單位設定得非常小(例如,一個按鍵),并避免鎖定,這種方法允許多個用戶同時進行編輯,但同時也帶來了多領導者復制的所有挑戰,包括需要解決沖突,
? 處理寫入沖突(多領導者復制的最大問題)
? 
圖 兩個主庫同時更新同一記錄引起的寫入沖突
無主復制
? 當節點故障時寫入資料庫
? 
? 圖5-10 仲裁寫入,法定讀取,并在節點中斷后讀修復,
? 檢測并發寫入
? 
? 圖 并發寫入Dynamo風格的資料存盤:沒有明確定義的順序,
? 捕獲"此前發生"關系
? 最初,購物車是空的,在它們之間,客戶端向資料庫發出五次寫入:
- 客戶端 1 將牛奶加入購物車,這是該鍵的第一次寫入,服務器成功存盤了它并為其分配版本號1,最后將值與版本號一起回送給客戶端,
- 客戶端 2 將雞蛋加入購物車,不知道客戶端 1 同時添加了牛奶(客戶端 2 認為它的雞蛋是購物車中的唯一物品),服務器為此寫入分配版本號 2,并將雞蛋和牛奶存盤為兩個單獨的值,然后它將這兩個值都反回給客戶端 2 ,并附上版本號 2 ,
- 客戶端 1 不知道客戶端 2 的寫入,想要將面粉加入購物車,因此認為當前的購物車內容應該是 [牛奶,面粉],它將此值與服務器先前向客戶端 1 提供的版本號 1 一起發送到服務器,服務器可以從版本號中知道[牛奶,面粉]的寫入取代了[牛奶]的先前值,但與[雞蛋]的值是并發的,因此,服務器將版本 3 分配給[牛奶,面粉],覆寫版本1值[牛奶],但保留版本 2 的值[蛋],并將所有的值回傳給客戶端 1 ,
- 同時,客戶端 2 想要加入火腿,不知道客端戶 1 剛剛加了面粉,客戶端 2 在最后一個回應中從服務器收到了兩個值[牛奶]和[蛋],所以客戶端 2 現在合并這些值,并添加火腿形成一個新的值,[雞蛋,牛奶,火腿],它將這個值發送到服務器,帶著之前的版本號 2 ,服務器檢測到新值會覆寫版本 2 [雞蛋],但新值也會與版本 3 [牛奶,面粉]并發,所以剩下的兩個是v3 [牛奶,面粉],和v4:[雞蛋,牛奶,火腿]
- 最后,客戶端 1 想要加培根,它以前在v3中從服務器接收[牛奶,面粉]和[雞蛋],所以它合并這些,添加培根,并將最終值[牛奶,面粉,雞蛋,培根]連同版本號v3發往服務器,這會覆寫v3[牛奶,面粉](請注意[雞蛋]已經在最后一步被覆寫),但與v4[雞蛋,牛奶,火腿]并發,所以服務器保留這兩個并發值,

第六章 磁區
磁區與復制
? 磁區通常與復制結合使用,使得每個磁區的副本存盤在多個節點上,

? 圖6-1 組合使用復制和磁區:每個節點充當某些磁區的領導者,其他磁區充當追隨者,
鍵值資料的磁區
? 1、根據鍵的范圍磁區(字典)
? 2、根據鍵的散列磁區
磁區與次級索引
? 次級索引是關系型資料庫的基礎,并且在檔案資料庫中也很普遍,許多鍵值存盤(如HBase和Volde-mort)為了減少實作的復雜度而放棄了次級索引,但是一些(如Riak)已經開始添加它們,因為它們對于資料模型實在是太有用了,并且次級索引也是Solr和Elasticsearch等搜索服務器的基石,
? 次級索引的問題是它們不能整齊地映射到磁區,有兩種用二級索引對資料庫進行磁區的方法:基于檔案的磁區(document-based)和基于關鍵詞(term-based)的磁區
? 按檔案的二級索引
? 
? 根據關鍵詞(Term)的二級索引
? 
磁區再平衡
? 隨著時間的推移,資料庫會有各種變化,
- 查詢吞吐量增加,所以您想要添加更多的CPU來處理負載,
- 資料集大小增加,所以您想添加更多的磁盤和RAM來存盤它,
- 機器出現故障,其他機器需要接管故障機器的責任,
所有這些更改都需要資料和請求從一個節點移動到另一個節點, 將負載從集群中的一個節點向另一個節點移動的程序稱為再平衡(reblancing),
無論使用哪種磁區方案,再平衡通常都要滿足一些最低要求:
- 再平衡之后,負載(資料存盤,讀取和寫入請求)應該在集群中的節點之間公平地共享,
- 再平衡發生時,資料庫應該繼續接受讀取和寫入,
- 節點之間只移動必須的資料,以便快速再平衡,并減少網路和磁盤I/O負載,
? 平衡策略:
* 反面教材:hash mod N
* 固定數量的磁區

* 動態磁區
* 按節點比例磁區
請求路由(客戶端發送請求時連接資料庫的那個節點?)
- 允許客戶聯系任何節點(例如,通過回圈策略的負載均衡(Round-Robin Load Balancer)),如果該節點恰巧擁有請求的磁區,則它可以直接處理該請求;否則,它將請求轉發到適當的節點,接識訓復并傳遞給客戶端,
- 首先將所有來自客戶端的請求發送到路由層,它決定了應該處理請求的節點,并相應地轉發,此路由層本身不處理任何請求;它僅負責磁區的負載均衡,
- 要求客戶端知道磁區和節點的分配,在這種情況下,客戶端可以直接連接到適當的節點,而不需要任何中介,

? 許多分布式資料系統都依賴于一個獨立的協調服務,比如ZooKeeper來跟蹤集群元資料, 下圖每個節點在ZooKeeper中注冊自己,ZooKeeper維護磁區到節點的可靠映射, 其他參與者(如路由層或磁區感知客戶端)可以在ZooKeeper中訂閱此資訊, 只要磁區分配發生的改變,或者集群中添加或洗掉了一個節點,ZooKeeper就會通知路由層使路由資訊保持最新狀態,

第七章 事務
ACID
? 原子性(Atomicity)
? 在多執行緒編程中,如果一個執行緒執行一個原子操作,這意味著另一個執行緒無法看到該操作的一半結果,系統只能處于操作之前或操作之后的狀態,而不是介于兩者之間的狀態,
? ACID原子性的定義特征是:能夠在錯誤時中止事務,丟棄該事務進行的所有寫入變更的能力,如果這些寫操作被分組到一個原子事務中,并且該事務由于錯誤而不能完成(提交),則該事務將被中止,并且資料庫必須丟棄或撤消該事務中迄今為止所做的任何寫入,
? 一致性(Consistency)
? 對資料的一組特定陳述必須始終成立,即不變數(invariants),例如,在會計系統中,所有賬戶整體上必須借貸相抵,如果一個事務開始于一個滿足這些不變數的有效資料庫,且在事務處理期間的任何寫入操作都保持這種有效性,那么可以確定,不變數總是滿足的,
? 原子性,隔離性和持久性是資料庫的屬性,而一致性(在ACID意義上)是應用程式的屬性,應用可能依賴資料庫的原子性和隔離屬性來實作一致性,但這并不僅取決于資料庫,
? 隔離性(Isolation)
? 同時執行的事務是相互隔離的:它們不能相互冒犯,大多數資料庫都會同時被多個客戶端訪問,如果它們各自讀寫資料庫的不同部分,這是沒有問題的,但是如果它們訪問相同的資料庫記錄,則可能會遇到并發問題,
? 
? 持久性(Durability)
? 持久性 是一個承諾,即一旦事務成功完成,即使發生硬體故障或資料庫崩潰,寫入的任何資料也不會丟失,
單物件和多物件操作
? 
? 圖 違反隔離性:一個事務讀取另一個事務的未被執行的寫入(“臟讀”)
? 沒有原子性,錯誤處理就要復雜得多,缺乏隔離性,就會導致并發問題,
事務隔離級別
? 讀已提交(Read Committed)
- 從資料庫讀時,只能看到已提交的資料(沒有臟讀(dirty reads)),
- 寫入資料庫時,只會覆寫已經寫入的資料(沒有臟寫(dirty writes)),
? 
圖 沒有臟讀:用戶2只有在用戶1的事務已經提交后才能看到x的新值,

圖 如果存在臟寫,來自不同事務的沖突寫入可能會混淆在一起
? 讀取偏差(不可重復讀)
? 在同一個事務中,客戶端在不同的時間點會看見資料庫的不同狀態,快照隔離經常用于解決這個問題,快照隔離的實作通常使用寫鎖來防止臟寫,從性能的角度來看,快照隔離的一個關鍵原則是:讀不阻塞寫,寫不阻塞讀,為了實作快照隔離,資料庫必須可能保留一個物件的幾個不同的提交版本,這種技術被稱為多版本并發控制,
? 如果一個資料庫只需要提供讀已提交的隔離級別,而不提供快照隔離,那么保留一個物件的兩個版本就足夠了:提交的版本和被覆寫但尚未提交的版本,支持快照隔離的存盤引擎通常也使用MVCC來實作讀已提交隔離級別,一種典型的方法是讀已提交為每個查詢使用單獨的快照,而快照隔離對整個事務使用相同的快照,
? 
? 圖中,當事務12 從賬戶2 讀取時,它會看到 $500 的余額,因為 $500 余額的洗掉是由事務13 完成的(根據規則3,事務12 看不到事務13 執行的洗掉),且400美元記錄的創建也是不可見的(按照相同的規則)
圖 使用多版本物件實作快照隔離
? 更新丟失
兩個客戶端同時執行**讀取-修改-寫入序列**,其中一個寫操作,在沒有合并另一個寫入變更情況下,直接覆寫了另一個寫操作的結果,所以導致資料丟失,快照隔離的一些實作可以自動防止這種例外,而另一些實作則需要手動鎖定(`SELECT FOR UPDATE`),
? 幻讀
? 事務讀取符合某些搜索條件的物件,另一個客戶端進行寫入,影響搜索結果,快照隔離可以防止直接的幻像讀取,但是寫入歪斜環境中的幻影需要特殊處理,例如索引范圍鎖定,
在存盤程序中封裝事務
? 即使人類已經找到了關鍵路徑,事務仍然以互動式的客戶端/服務器風格執行,一次一個陳述句,應用程式進行查詢,讀取結果,可能根據第一個查詢的結果進行另一個查詢,依此類推,查詢和結果在應用程式代碼(在一臺機器上運行)和資料庫服務器(在另一臺機器上)之間來回發送,
在這種互動式的事務方式中,應用程式和資料庫之間的網路通信耗費了大量的時間,如果不允許在資料庫中進行并發處理,且一次只處理一個事務,則吞吐量將會非常糟糕,因為資料庫大部分的時間都花費在等待應用程式發出當前事務的下一個查詢,在這種資料庫中,為了獲得合理的性能,需要同時處理多個事務,
出于這個原因,具有單執行緒串行事務處理的系統不允許互動式的多陳述句事務,取而代之,應用程式必須提前將整個事務代碼作為存盤程序提交給資料庫,這些方法之間的差異如圖所示,如果事務所需的所有資料都在記憶體中,則存盤程序可以非常快地執行,而不用等待任何網路或磁盤I/O,
? 
? 圖 互動式事務和存盤程序之間的區別
? 存盤程序與記憶體存盤,使得在單個執行緒上執行所有事務變得可行,由于不需要等待I/O,且避免了并發控制機制的開銷,它們可以在單個執行緒上實作相當好的吞吐量,
可序列化快照隔離(SSI, serializable snapshot isolation)
? 檢測舊MVCC讀取(讀之前存在未提交的寫入)
? 
? 當一個事務從MVCC資料庫中的一致快照讀時,它將忽略取快照時尚未提交的任何其他事務所做的寫入,上圖中,事務43 認為Alice的 on_call = true ,因為事務42(修改Alice的待命狀態)未被提交,然而,在事務43想要提交時,事務42 已經提交,這意味著在讀一致性快照時被忽略的寫入已經生效,事務43 的前提不再為真,
? 為了防止這種例外,資料庫需要跟蹤一個事務由于MVCC可見性規則而忽略另一個事務的寫入,當事務想要提交時,資料庫檢查是否有任何被忽略的寫入現在已經被提交,如果是這樣,事務必須中止,
為什么要等到提交?當檢測到陳舊的讀取時,為什么不立即中止事務43 ?因為如果事務43 是只讀事務,則不需要中止,因為沒有寫入偏差的風險,當事務43 進行讀取時,資料庫還不知道事務是否要稍后執行寫操作,此外,事務42 可能在事務43 被提交的時候中止或者可能仍然未被提交,因此讀取可能終究不是陳舊的,通過避免不必要的中止,SSI 保留快照隔離對從一致快照中長時間運行的讀取的支持,
? 檢測影響之前讀取的寫入(讀之后寫入)

? 上圖中,事務42 和43 都在班次1234 查找值班醫生,如果在shift_id上有索引,則資料庫可以使用索引項1234 來記錄事務42 和43 讀取這個資料的事實, (如果沒有索引,這個資訊可以在表級別進行跟蹤),這個資訊只需要保留一段時間:在一個事務完成(提交或中止)之后,所有的并發事務完成之后,資料庫就可以忘記它讀取的資料了,
? 當事務寫入資料庫時,它必須在索引中查找最近曾讀取受影響資料的其他事務,這個程序類似于在受影響的鍵范圍上獲取寫鎖,但鎖并不會阻塞事務到其他事務完成,而是像一個引線一樣只是簡單通知其他事務:你們讀過的資料可能不是最新的啦,
? 上圖中,事務43 通知事務42 其先前讀已過時,反之亦然,事務42首先提交并成功,盡管事務43 的寫影響了42 ,但因為事務43 尚未提交,所以寫入尚未生效,然而當事務43 想要提交時,來自事務42 的沖突寫入已經被提交,所以事務43 必須中止,
第八章 分布式系統的麻煩
? 部分失效是分布式系統的決定性特征,為了容忍錯誤,第一步是檢測它們,但即使這樣也很難,大多數系統沒有檢測節點是否發生故障的準確機制,所以大多數分布式演算法依靠超時來確定遠程節點是否仍然可用, 一旦檢測到故障,使系統容忍它也并不容易:沒有全域變數,沒有共享記憶體,沒有共同的知識,或機器之間任何其他種類的共享狀態,
? 大多數非安全關鍵系統會選擇便宜而不可靠,而不是昂貴和可靠,分布式系統可以永久運行而不會在服務層面中斷,因為所有的錯誤和維護都可以在節點級別進行處理——至少在理論上是如此, (實際上,如果一個錯誤的配置變更被應用到所有的節點,仍然會使分布式系統癱瘓),
第九章 一致性與共識
一致性保證
? 最終一致性:非常弱的保證
? 線性一致性:最強一致性模型之一
? 因果一致性
線性一致性
? 
? 圖 這個系統是非線性一致的,導致了球迷的困惑
? 線性一致性背后的基本思想很簡單:使系統看起來好像只有一個資料副本,

? 圖 可視化讀取和寫入看起來已經生效的時間點, B的最后讀取不是線性一致性的
? 上圖中有一些有趣的細節需要指出:
-
第一個客戶端B發送一個讀取
x的請求,然后客戶端D發送一個請求將x設定為0,然后客戶端A發送請求將x設定為1,盡管如此,回傳到B的讀取值為1(由A寫入的值),這是可以的:這意味著資料庫首先處理D的寫入,然后是A的寫入,最后是B的讀取,雖然這不是請求發送的順序,但這是一個可以接受的順序,因為這三個請求是并發的,也許B的讀請求在網路上略有延遲,所以它在兩次寫入之后才到達資料庫, -
在客戶端A從資料庫收到回應之前,客戶端B的讀取回傳
1,表示寫入值1已成功,這也是可以的:這并不意味著在寫之前讀到了值,這只是意味著從資料庫到客戶端A的正確回應在網路中略有延遲, -
此模型不假設有任何事務隔離:另一個客戶端可能隨時更改值,例如,C首先讀取
1,然后讀取2,因為兩次讀取之間的值由B更改,可以使用原子比較并設定(cas)操作來檢查該值是否未被另一客戶端同時更改:B和C的cas請求成功,但是D的cas請求失敗(在資料庫處理它時,x的值不再是0), -
客戶B的最后一次讀取(陰影條柱中)不是線性一致性的, 該操作與C的cas寫操作并發(它將
x從2更新為4),在沒有其他請求的情況下,B的讀取回傳2是可以的,然而,在B的讀取開始之前,客戶端A已經讀取了新的值4,因此不允許B讀取比A更舊的值,再次,與圖9-1中的Alice和Bob的情況相同,這就是線性一致性背后的直覺, 正式的定義【6】更準確地描述了它, 通過記錄所有請求和回應的時序,并檢查它們是否可以排列成有效的順序,測驗一個系統的行為是否線性一致性是可能的(盡管在計算上是昂貴的)【11】,
線性一致性的有效場景
? 鎖定和領導選舉 (zookeeper etcd 使用一致性演算法以容錯方式保證)
? 約束和唯一性保證 (用戶名或電子郵件地址必須唯一標識一個用戶)
? 跨信道的時序依賴

? 圖 Web服務器和影像調整器通過檔案存盤和訊息佇列進行通信,打開競爭條件的可能性
? 出現這個問題是因為Web服務器和縮放器之間存在兩個不同的信道:檔案存盤與訊息佇列,沒有線性一致性的新鮮性保證,這兩個信道之間的競爭條件是可能的,
因果一致性
? 因果性對系統中的事件施加了順序(什么發生在什么之前,基于因與果),與線性一致不同,線性一致性將所有操作放在單一的全序時間線中,因果一致性為我們提供了一個較弱的一致性模型:某些事件可以是并發的,所以版本歷史就像是一條不斷分叉與合并的時間線,因果一致性沒有線性一致性的協調開銷,而且對網路問題的敏感性要低得多,
分布式事務與共識
? 共識:所有節點一致同意所做決定,且這一決定不可撤銷,
? 共識問題:
? 線性一致性的CAS暫存器
暫存器需要基于當前值是否等于操作給出的引數,原子地**決定**是否設定新值,
? 原子事務提交
資料庫必須**決定**是否提交或中止分布式事務,
? 全序廣播
訊息系統必須**決定**傳遞訊息的順序,
? 鎖和租約
當幾個客戶端爭搶鎖或租約時,由鎖來**決定**哪個客戶端成功獲得鎖,
? 成員/協調服務
給定某種故障檢測器(例如超時),系統必須**決定**哪些節點活著,哪些節點因為會話超時需 要被宣告死亡,
? 唯一性約束
當多個事務同時嘗試使用相同的鍵創建沖突記錄時,約束必須**決定**哪一個被允許,哪些因為 違反約束而失敗,
第十章 批處理
三種系統型別
? *服務(在線系統)*:每收到一個,服務會試圖盡快處理它,并發回一個回應,回應時間通常 是服務性能的主要衡量指標,可用性通常非常重要
? 批處理系統(離線系統)*:大量的輸入資料,跑一個作業(job)*來處理它,并生成一些輸出 資料,這往往需要一段時間(從幾分鐘到幾天),所以通常不會有用戶等待作業完成,相反,批 量作業通常會定期運行(例如,每天一次),批處理作業的主要性能衡量標準通常是吞吐量(處 理特定大小的輸入所需的時間).
? *流處理系統(準實時系統)*: 介于兩者之間,流處理消費輸入并產生輸出,在事件發生后不久就會對事件進行操作,不會等待一組固定的輸入資料(批處理的特點),因此具有低延遲的特點,
UNIX 分析簡單日志
cat /var/log/nginx/access.log | #1
awk '{print $7}' | #2
sort | #3
uniq -c | #4
sort -r -n | #5
head -n 5 #6
1. 讀取日志檔案
2. 將每一行按空格分割成不同的欄位,每行只輸出第七個欄位,恰好是請求的URL,在我們的例子中是`/css/typography.css`,
3. 按字母順序排列請求的URL串列,如果某個URL被請求過n次,那么排序后,檔案將包含連續重復出現n次的該URL,
4. `uniq`命令通過檢查兩個相鄰的行是否相同來過濾掉輸入中的重復行, `-c`則表示還要輸出一個計數器:對于每個不同的URL,它會報告輸入中出現該URL的次數,
5. 第二種排序按每行起始處的數字(`-n`)排序,這是URL的請求次數,然后逆序(`-r`)回傳結果,大的數字在前,
6. 最后,只輸出前五行(`-n 5`),并丟棄其余的
output:
4189 /favicon.ico
3631 /2013/05/24/improving-security-of-ssh-private-keys.html
2124 /2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html
1369 /
915 /css/typography.css
? 優點:接受任意形式輸入,符合萬物皆檔案的概念 (行程的輸出可以是下一個行程的輸入)
? 各命令之間的性能高,
? 缺點:只能在一臺機器上運行(行程到行程) ---> 引出Hadoop
MapReduce
? MapReduce是一個編程框架,你可以使用它撰寫代碼來處理HDFS等分布式檔案系統中的大型資料集,支持多臺機器上進行并行計算,Mapper和Reducer一次只能處理一條記錄;它們不需要知道它們的輸入來自哪里,或者輸出去往什么地方,所以框架可以處理在機器之間移動資料的復雜性,
? 要創建MapReduce作業,你需要實作兩個回呼函式,Mapper和Reducer,
? Mapper
? Mapper會在每條輸入記錄上呼叫一次,其作業是從輸入記錄中提取鍵值,對于每個輸入,它可以生成任意數量的鍵值對(包括None),它不會保留從一個輸入記錄到下一個記錄的任何狀態,因此每個記錄都是獨立處理的,
? Reducer
? MapReduce框架拉取由Mapper生成的鍵值對,收集屬于同一個鍵的所有值,并使用在這組值串列上迭代呼叫Reducer, Reducer可以產生輸出記錄(例如相同URL的出現次數),
? 分布式執行MapReduce
? 
? 每個輸入檔案的大小通常是數百兆位元組, MapReduce調度器(圖中未顯示)試圖在其中一臺存盤輸入檔案副本的機器上運行每個Mapper,只要該機器有足夠的備用RAM和CPU資源來運行Mapper任務【26】,這個原則被稱為將計算放在資料附近【27】:它節省了通過網路復制輸入檔案的開銷,減少網路負載并增加區域性,
? 計算的Reduce端也被磁區,雖然Map任務的數量由輸入檔案塊的數量決定,但Reducer的任務的數量是由作業作者配置的(它可以不同于Map任務的數量),為了確保具有相同鍵的所有鍵值對最終落在相同的Reducer處,框架使用鍵的散列值來確定哪個Reduce任務應該接收到特定的鍵值對,
? 只要當Mapper讀取完輸入檔案,并寫完排序后的輸出檔案,MapReduce調度器就會通知Reducer可以從該Mapper開始獲取輸出檔案,Reducer連接到每個Mapper,并下載自己相應磁區的有序鍵值對檔案,按Reducer磁區,排序,從Mapper向Reducer復制磁區資料,這一整個程序被稱為混洗(shuffle)
? Reduce任務從Mapper獲取檔案,并將它們合并在一起,并保留有序特性,因此,如果不同的Mapper生成了鍵相同的記錄,則在Reducer的輸入中,這些記錄將會相鄰,Reducer呼叫時會收到一個鍵,和一個迭代器作為引數,迭代器會順序地掃過所有具有該鍵的記錄(因為在某些情況可能無法完全放入記憶體中),Reducer可以使用任意邏輯來處理這些記錄,并且可以生成任意數量的輸出記錄,這些輸出記錄會寫入分布式檔案系統上的檔案中
? 排序合并連接
? 
? 為了在批處理程序中實作良好的吞吐量,計算必須(盡可能)限于單臺機器上進行,為待處理的每條記錄發起隨機訪問的網路請求實在是太慢了,更好的方法是獲取用戶資料庫的副本,并將它和用戶行為日志放入同一個分布式檔案系統中,
? 當MapReduce框架通過鍵對Mapper輸出進行磁區,然后對鍵值對進行排序時,效果是具有相同ID的所有活動事件和用戶記錄在Reducer輸入中彼此相鄰, Map-Reduce作業甚至可以也讓這些記錄排序,使Reducer總能先看到來自用戶資料庫的記錄,緊接著是按時間戳順序排序的活動事件 —— 這種技術被稱為二次排序(secondary sort)
? 由于Reducer一次處理一個特定用戶ID的所有記錄,因此一次只需要將一條用戶記錄保存在記憶體中,而不需要通過網路發出任何請求,這個演算法被稱為排序合并連接(sort-merge join),因為Mapper的輸出是按鍵排序的,然后Reducer將來自連接兩側的有序記錄串列合并在一起,
? 處理傾斜
? 在單個Reducer中收集與某個名流相關的所有活動(例如他們發布內容的回復)可能導致嚴重的傾斜(也稱為熱點(hot spot))—— 也就是說,一個Reducer必須比其他Reducer處理更多的記錄(參見“負載傾斜與消除熱點“),由于MapReduce作業只有在所有Mapper和Reducer都完成時才完成,所有后續作業必須等待最慢的Reducer才能啟動,
? Pig中的傾斜連接(skewed join)方法首先運行一個抽樣作業來確定哪些鍵是熱鍵,連接實際執行時,Mapper會將熱鍵的關聯記錄隨機(相對于傳統MapReduce基于鍵散列的確定性方法)發送到幾個Reducer之一,對于另外一側的連接輸入,與熱鍵相關的記錄需要被復制到所有處理該鍵的Reducer上
? 廣播散列連接
? 兩個連接輸入之一很小,所以它并沒有磁區,而且能被完全加載進一個哈希表中,因此,你可以為連接輸入大端的每個磁區啟動一個Mapper,將輸入小端的散串列加載到每個Mapper中,然后掃描大端,一次一條記錄,并為每條記錄查詢散串列,
? 磁區散列連接
? 如果兩個連接輸入以相同的方式磁區(使用相同的鍵,相同的散列函式和相同數量的磁區),則可以獨立地對每個磁區應用散串列方法,
? 回呼函式
? 分布式批處理引擎有一個刻意限制的編程模型:回呼函式(比如Mapper和Reducer)被假定是無狀態的,而且除了指定的輸出外,必須沒有任何外部可見的副作用,這一限制允許框架在其抽象下隱藏一些困難的分布式系統問題:當遇到崩潰和網路問題時,任務可以安全地重試,任何失敗任務的輸出都被丟棄,如果某個磁區的多個任務成功,則其中只有一個能使其輸出實際可見,
? 得益于這個框架,你在批處理作業中的代碼無需操心實作容錯機制:框架可以保證作業的最終輸出與沒有發生錯誤的情況相同,也許不得不重試各種任務,在線服務處理用戶請求,并將寫入資料庫作為處理請求的副作用,比起在線服務,批處理提供的這種可靠性語意要強得多,
批處理的特點總結
? 批處理作業的顯著特點是,它讀取一些輸入資料并產生一些輸出資料,但不修改輸入—— 換句話說,輸出是從輸入衍生出的,最關鍵的是,輸入資料是有界的(bounded):它有一個已知的,固定的大小(例如,它包含一些時間點的日志檔案或資料庫內容的快照),因為它是有界的,一個作業知道自己什么時候完成了整個輸入的讀取,所以一個作業在做完后,最終總是會完成的,
第十一章 流處理
? 訊息代理和事件日志可以視作檔案系統的流式等價物,
訊息代理(訊息佇列)
? 與資料庫的差異
- 資料庫通常保留資料直至顯式洗掉,而大多數訊息代理在訊息成功遞送給消費者時會自動洗掉訊息,這樣的訊息代理不適合長期的資料存盤,
- 由于它們很快就能洗掉訊息,大多數訊息代理都認為它們的作業集相當小—— 即佇列很短,如果代理需要緩沖很多訊息,比如因為消費者速度較慢(如果記憶體裝不下訊息,可能會溢位到磁盤),每個訊息需要更長的處理時間,整體吞吐量可能會惡化【6】,
- 資料庫通常支持二級索引和各種搜索資料的方式,而訊息代理通常支持按照某種模式匹配主題,訂閱其子集,機制并不一樣,對于客戶端選擇想要了解的資料的一部分,這是兩種基本的方式,
- 查詢資料庫時,結果通常基于某個時間點的資料快照;如果另一個客戶端隨后向資料庫寫入一些改變了查詢結果的內容,則第一個客戶端不會發現其先前結果現已過期(除非它重復查詢或輪詢變更),相比之下,訊息代理不支持任意查詢,但是當資料發生變化時(即新訊息可用時),它們會通知客戶端,
? 多個消費者
? 負載均衡與扇出
? 
? 圖(a)負載平衡:在消費者間共享消費主題;(b)扇出:將每條訊息傳遞給多個消費者,
? 兩種模式可以組合使用:例如,兩個獨立的消費者組可以每組各訂閱一個主題,每一組都共同收到所有訊息,但在每一組內部,每條訊息僅由單個節點處理,
? 磁區日志(基于日志的訊息代理)
? 
圖 生產者通過將訊息追加寫入主題磁區檔案來發送訊息,消費者依次讀取這些檔案
? 變更資料捕獲(CDC)
? 
圖 將資料按順序寫入一個資料庫,然后按照相同的順序將這些更改應用到其他系統
流處理的三種型別
? 流流連接
? 兩個輸入流都由活動事件組成,而連接算子在某個時間視窗內搜索相關的事件,例如,它可能會將同一個用戶30分鐘內進行的兩個活動聯系在一起,如果你想要找出一個流內的相關事件,連接的兩側輸入可能實際上都是同一個流(自連接(self-join)),
? 流表連接
? 一個輸入流由活動事件組成,另一個輸入流是資料庫變更日志,變更日志保證了資料庫的本地副本是最新的,對于每個活動事件,連接算子將查詢資料庫,并輸出一個擴展的活動事件,
? 表表連接
? 兩個輸入流都是資料庫變更日志,在這種情況下,一側的每一個變化都與另一側的最新狀態相連接,結果是兩表連接所得物化視圖的變更流,
第十二章 資料系統的未來
? 某些系統被指定為記錄系統,而其他資料則通過轉換衍生自記錄系統,通過這種方式,我們可以維護索引,物化視圖,機器學習模型,統計摘要等等,通過使這些衍生和轉換操作異步且松散耦合,能夠防止一個區域中的問題擴散到系統中不相關部分,從而增加整個系統的穩健性與容錯性,
? 將資料流表示為從一個資料集到另一個資料集的轉換也有助于演化應用程式:如果你想變更其中一個處理步驟,例如變更索引或快取的結構,則可以在整個輸入資料集上重新運行新的轉換代碼,以便重新衍生輸出,同樣,出現問題時,你也可以修復代碼并重新處理資料以便恢復,
? 這些程序與資料庫內部已經完成的程序非常類似,因此我們將資料流應用的概念重新改寫為,分拆(unbundling) 資料庫組件,并通過組合這些松散耦合的組件來構建應用程式,
? 衍生狀態可以通過觀察底層資料的變更來更新,此外,衍生狀態本身可以進一步被下游消費者觀察,我們甚至可以將這種資料流一路傳送至顯示資料的終端用戶設備,從而構建可動態更新以反映資料變更,并在離線時能繼續作業的用戶界面,
? 接下來,我們討論了如何確保所有這些處理在出現故障時保持正確,我們看到可擴展的強完整性保證可以通過異步事件處理來實作,通過使用端到端操作識別符號使操作冪等,以及通過異步檢查約束,客戶端可以等到檢查通過,或者不等待繼續前進,但是可能會冒有違反約束需要道歉的風險,這種方法比使用分布式事務的傳統方法更具可擴展性與可靠性,并且在實踐中適用于很多業務流程,
? 通過圍繞資料流構建應用,并異步檢查約束,我們可以避免絕大多數的協調作業,創建保證完整性且性能仍然表現良好的系統,即使在地理散布的情況下與出現故障時亦然,然后,我們對使用審計來驗證資料完整性,以及損壞檢測進行了一些討論,
? 最后,我們退后一步,審視了構建資料密集型應用的一些道德問題,我們看到,雖然資料可以用來做好事,但它也可能造成很大傷害:作出嚴重影響人們生活的決定卻難以申訴,導致歧視與剝削,監視常態化,曝光私密資訊,我們也冒著資料被泄露的風險,并且可能會發現,即使是善意地使用資料也可能會導致意想不到的后果,
? 由于軟體和資料對世界產生了如此巨大的影響,我們工程師們必須牢記,我們有責任為我們想要的那種世界而努力:一個尊重人們,尊重人性的世界,我希望我們能夠一起為實作這一目標而努力,
——世界上從來不乏優秀的人,我只是想接近他們一點,轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/412849.html
標籤:Go
