1. 程式人生 > >第4章 資料訪問層

第4章 資料訪問層

上一章的服務框架可以讓應用從集中式走向分散式,解決了當網站功能越來越豐富、單個應用越來越龐大的問題,使系統走向服務化的架構。隨著資料量和訪問量的上升,應用訪問資料庫也會出現瓶頸,這時資料訪問層出場!

4.1 資料庫從單機到分散式的挑戰

4.1.1 單機資料庫

當網站比較小,資料庫的數量和訪問量都比較小時,只有一個數據庫,所有的table 都在這個資料庫中;這個資料庫服務可能是單獨一臺伺服器,也可能跟應用或者其他服務共用一臺伺服器;然後應用用資料訪問層(JDBC/ODBC)來統一訪問資料庫;這個資料訪問層可以提供給開發一套API,讓開發更方便的訪問資料庫。

             APP ------> JDBC ---> DB

4.1.2 資料庫垂直、水平拆分的困難

當網頁訪問量足夠大時,除了簡單的加機器來提高訪問的效率外,還有其他的幾種思路可以緩解DB的壓力:

  • 應用的優化:可以通過業務的優化和訪問db的優化來減少DB的訪問、或者錯開高峰的訪問
  • 引入快取:可以引入快取、搜尋引擎之類的來緩解DB的壓力
  • 對資料庫進行分庫、分表;把DB 升級到分散式資料庫
垂直(分庫)、水平(分表) 都會帶來一系列問題(相對單機的DB):
  • 打破原來的ACID 原則:比如之前單機的事務,DBMS 可以自帶ACID 原則,但是分散式的DB 需要應用自己來保證事務的一致性或者引入分散式的事務
  • join 工作:因為多張table 可能不在同一個資料庫裡面,這時就不能簡單使用DBMS的join工作,需要應用從不同的DB 不同的table 裡面取出資料,然後自己程式碼中進行join
  • 分表的動作:唯一的id編號的key 就會遇到問題,這些問題都需要在分散式DB 中進行解決
  • 觸發器、儲存過程:從單機DB 遷移到分散式DB 後這些有可能都會失效,都需要重新修改後才可以使用

4.2 資料庫從單機變為多機(分散式)如何處理

4.2.1 分散式事務

事務的支援對應用來說是非常重要的特性,對單臺DB的DBMS 支援是非常到位的,但是在分散式的DB中事務的特性就需要應用自己來解決;下面看下怎麼解決的:
  • 分散式事務(DTP):
分散式事務就是一個事務要在分散式多臺機器上的資料庫進行操作,在任何一個節點上事務中任何一個動作失敗都會回滾這個事務; all  or nothing
  • 分散式事務的解決方案
對一個事務在不同分散式資料庫的執行進行記錄、維護,如果發現任何一個失敗,會發送回滾操作給所有的資料庫;來保證分散式事務的事務性!!!

4.2.2 多機的sqeuence 的處理

當水平分表時,單機中的sequence和自增的id做法就需要改變,在單機的DBMS中提供了一個機制來實現自增、不重複的id來完成編號;但在分散式資料庫中就變的困難,這個問題主要解決的思路分為2個問題:一個是唯一性、一個是連續性; 如果2個問題單獨考慮會非常簡單:唯一性可以用ip、mac地址、時間等等組成唯一的數值;如果單獨連續性也很簡單,但要滿足二者,我們採取把產生seqence的服務單獨獨立出來,寫一個單獨產生唯一、連續id的伺服器,這樣每次去訪問他就好;但每次的改進、解決問題都會引入其他的問題;比如這個服務的穩定性、容錯性

4.2.3 多機資料查詢的問題(join)

  • 如果垂直分庫後,查詢的多張table 還在一個DB(Data Base) ,可以藉助DB的DBMS 提供的join 功能可以簡單的進行資料的join 動作進行查詢是資料;如果查詢的table 不在一個數據庫中,而是分佈到多臺機器上;這個就不能簡單的join 動作了;
  • 上面的問題解決思路有三種:
      1). 應用層分多次來查詢資料庫:比如我要從DB1/DB2忠誠查詢手機尾號是0135的人,已經這些人的職業資訊;先從DB1中查詢出尾號是0135的所有人,然後根據每一個人去查詢這些人對應的職業;這樣自己把資料再組裝起來;但這樣效率就比較低      2).  資料冗餘:對比較常用的資料,可以冗餘到一個DB 中的一張table裡面,這樣直接查詢一張table;不用查詢join 動作      3). 藉助外界的快取、搜尋引擎來徹底解決查多張table的問題;就是把資料從DB dump出來build 成index 通過TCP 提供給使用者服務

4.2.4 跨庫查詢的問題和解決 【理解真正的分庫、分表】

  • 資料庫分庫分表的演化
合併查詢問題的產生根源在於我們進行水平分庫分表時,把一張邏輯上的表分成了多張物理上的表,比如我們有一個使用者的資訊表,根據使用者的id進行分庫、分表後,物理上就會分成 很多使用者資訊表;如下圖:
從上圖可以看出來,最初使用者資訊儲存在一個數據庫中(最左邊的),然後進行了分庫,變成了2個數據庫(中間的部分),這2個數據庫儲存的使用者資訊是不同的,一般按照使用者id分成2個db,2個數據庫的使用者資訊表加起來相當於最初的使用者資訊表;因為在一個數據庫裡面使用者資訊的表還是非常大,所以要按照一定的規則把使用者資訊的分到2個table裡面,這樣2個數據庫裡面就用了4張使用者資訊的表;這4張表加起來相當於最左邊的使用者資訊表。
  • 從邏輯概念上使用者的資訊應該放到一張表中,但是隨著使用者資訊量的增大、訪問量的上升,需要經歷分庫、分表;此時使用者的資訊會分佈到多個數據庫的多張表中;也就是一張邏輯上的表對應了多張物理上的表。那在應用中對張邏輯表的查詢就需要做跨庫跨表的合併了。
  • 跨庫跨表的解決方案
只能是在應用程式碼中解決,從不同的資料庫查詢資料,從同一個資料庫不同的表中把資料統一查詢出來,然後對多次查詢的資料進行merger
  • 排序:根據需要進行所有結果資料的排序
  • 函式處理:各種max min sum avg 處理
  • 業務邏輯的處理
  • 分頁排序的問題【資料很多,在不同的頁面顯示;要進行分頁、排序】;也會出現統一排序後然後分頁

4.3 資料訪問層的設計和實現

資料訪問層就是方便應用進行資料讀/寫訪問的抽象層,我們在這個層上解決各個應用通用的訪問資料庫的問題;在分散式系統中,我們把資料訪問層叫做分散式資料訪問層,簡稱為資料層

4.3.1 如何對外提供資料訪問層的功能

在java 應用中一般是通過JDBC 方式來訪問資料庫,資料層自身可以作為一個JDBC 的實現,也就是暴露出JDBC的介面給應用;這時應用遷移到分散式資料層的成本就很低了,和使用遠端資料庫的JDBC的驅動方式是一樣的,遷移成本也非常低。我們們採用在JDBC上面包裝一層可以讓應用方便、快捷、低成本的分散式資料訪問層

4.3.2 安裝資料層流程的順序看資料層的設計

我們在執行資料庫(分散式)操作時的流程如下: SQL 解析  ---> SQL 規則處理(根據資料的分庫分表地址)  ----> SQL 改寫(分解成直接訪問資料庫的多個sql 語句)  -----> 資料來源選擇(選擇每一個sql的資料來源)  ---->SQL 執行 -----> 查詢結果返回併合並處理;
  • SQL 解析   ---> 獲取這sql 要查詢的表名資訊
通過SQL 解析可以獲取SQL 的關鍵資訊:表名、欄位、where 條件;在資料層中一個很重要的事情就是根據執行的SQL 得到要操作的表,然後根據引數及規則來確定目標資料來源進行連線
  • 規則處理   ---> 根據表名查詢到表所在的資料來源
規則處理就是資料分配的規則,方便通過這個規則來找到資料庫和表的節點地址;比如採用雜湊演算法,根據某一個數值進行雜湊,有規則的把資料放到指定的資料庫和表中;這樣在查詢SQL 裡面的表時可以根據雜湊規則找到對應的資料來源;   
  • 改寫SQL  --> 修改表名和資訊
分庫分表後一個邏輯表(應用用到的表名)對應多張物理表,這時要把SQL 改寫成多個sql,每個sql 的表名要修改;其他的資訊也需要修改。
  • 選擇資料來源  --> 確定資料來源
上面的規則處理可以幫助我們確定查詢資料的一組資料來源(一張邏輯表分成多張物理表,規則處理能找到所有的物理表),這裡是確定查詢的資料具體在哪個物理表裡面。 我們上面說的分庫分表後,一般都會提供備庫;這樣就變成了多個數據庫、多個表;這些資料庫一般是一寫多讀(也存在多寫多讀的,但不是很多);根據當前執行SQL 的特點(讀、寫)是否在事務中已經各個資料庫的權重規則,來選擇具體是哪個DB 作為資料來源來提供使用者的訪問
  • 執行SQL 和結果處理階段
改寫SQL 、確定資料來源後就可以執行SQL 了,執行SQL 比較重要的是執行過程中的異常判斷和處理,這裡就不說了

4.4 獨立部署的資料訪問層的實現方式

資料訪問層(上面5個步驟)是作為一個整體向app 提供資料訪問的服務,對應用層應該是透明的;比如使用者表一個邏輯表可能分庫分表後分成多個數據庫多個物理表,但是的應用來說,不用關心裡面的細節,還是把使用者表作為一張表來寫程式即可。至於怎麼從一張邏輯表對映到多張物理表、SQL 的解析、處理、改寫、執行都是資料層完成的工作,對應用來說是透明的[mongodb 本就是把資料訪問層給做到資料裡面,僅僅通過配置就可以實現分庫分表],下面的圖就是資料訪問層的示意圖:

4.5 資料庫讀寫分離的挑戰和應對

隨著資料量的增大(一臺機器的磁碟當不下),DB 訪問量增大,導致資料庫壓力很大;一般對一個DB裡說,讀的次數遠遠大於寫的次數,而且讀不會改動DB的資料,跟寫的工作有明顯的區別,所以我們一般就是讀寫分離,就形成了典型的主從分離(讀寫分離): 主庫可寫、可讀,從庫只能讀。


資料讀寫分離後,資料的同步問題就變得非常重要的一個功能了,如果資料不能實時同步會造成讀取資料不是最新的,會造成使用者體驗差或者影響業務的問題;此時我們需要做的就是資料同步。

  • 主從資料庫結構一致:採用訊息機制更新從資料庫 [訊息機制不是非常友好,最友好的是根據DB的變更日誌進行跟新資料]
master 資料庫必須實現讀寫一致,主庫要分庫成多個,每個主庫所有的分庫的備份就是一個完整的從庫;所以架構如下:
  • 主從資料結構不一致:  從庫根據不同的緯度進行分庫

主庫是買家id 的取模進行分庫,這樣查詢買家資料就可以在一個數據庫裡面查詢,但是賣家查詢就需要在所有的資料都查詢一遍,成本會比較高,所以從庫採用賣家的id取模進行分庫,這樣賣家查詢就可以查詢一個庫
  • 引入資料變更平臺
複製資料庫到其他資料庫只是資料變更的一個場景,還有其他的一些資料變更的場景:比如搜尋引擎索引更新、快取資料更新等等都是資料變更的一種場景,這樣的場景都需要一個數據變更,所以我們引入一個數據變更平臺來實現資料的變更。
  • 資料的平滑遷移
資料遷移過程中平滑遷移是非常重要的一個問題,因為在資料遷移過程是資料是要發生變化的,遷移完成後要把這個資料的變化要跟新到資料庫中。 一般採取寫增量日誌的形式來完成;在資料庫遷移時先記錄遷移開始之後資料變更的日誌資訊,資料遷移完成之後再通過增量日誌把資料變更同步到資料庫中