隨着業務規模的不斷擴大,須要選擇合適的方案去應對數據規模的增加,以應對逐漸增加的訪問壓力和數據量。sql
關於數據庫的擴展主要包括:業務拆分、主從複製,數據庫分庫與分表。這篇文章主要講述數據庫分庫與分表數據庫
(1)業務拆分bash
在 大型網站應用之海量數據和高併發解決方案總結一二 一篇文章中也具體講述了爲何要對業務進行拆分。架構
業務起步初始,爲了加快應用上線和快速迭代,不少應用都採用集中式的架構。隨着業務系統的擴大,系統變得愈來愈複雜,愈來愈難以維護,開發效率變得愈來愈低,而且對資源的消耗也變得愈來愈大,經過硬件提升系統性能的方式帶來的成本也愈來愈高。併發
所以,在選型初期,一個優良的架構設計是後期系統進行擴展的重要保障。負載均衡
例如:電商平臺,包含了用戶、商品、評價、訂單等幾大模塊,最簡單的作法就是在一個數據庫中分別建立users、shops、comment、order四張表。異步
可是,隨着業務規模的增大,訪問量的增大,咱們不得不對業務進行拆分。每個模塊都使用單獨的數據庫來進行存儲,不一樣的業務訪問不一樣的數據庫,將本來對一個數據庫的依賴拆分爲對4個數據庫的依賴,這樣的話就變成了4個數據庫同時承擔壓力,系統的吞吐量天然就提升了。分佈式
(2)主從複製高併發
上圖是網上的一張關於MySQL的Master和Slave之間數據同步的過程圖。性能
主要講述了MySQL主從複製的原理:數據複製的實際就是Slave從Master獲取Binary log文件,而後再本地鏡像的執行日誌中記錄的操做。因爲主從複製的過程是異步的,所以Slave和Master之間的數據有可能存在延遲的現象,此時只能保證數據最終的一致性。
(3)數據庫分庫與分表
咱們知道每臺機器不管配置多麼好它都有自身的物理上限,因此當咱們應用已經能觸及或遠遠超出單臺機器的某個上限的時候,咱們唯有尋找別的機器的幫助或者繼續升級的咱們的硬件,但常見的方案仍是經過添加更多的機器來共同承擔壓力。
咱們還得考慮當咱們的業務邏輯不斷增加,咱們的機器能不能經過線性增加就能知足需求?所以,使用數據庫的分庫分表,可以立竿見影的提高系統的性能,關於爲何要使用數據庫的分庫分表的其餘緣由這裏再也不贅述,主要講具體的實現策略。請看下邊章節。
關鍵字:用戶ID、表容量
對於大部分數據庫的設計和業務的操做基本都與用戶的ID相關,所以使用用戶ID是最經常使用的分庫的路由策略。用戶的ID能夠做爲貫穿整個系統用的重要字段。所以,使用用戶的ID咱們不只能夠方便咱們的查詢,還能夠將數據平均的分配到不一樣的數據庫中。(固然,還能夠根據類別等進行分表操做,分表的路由策略還有不少方式)
接着上述電商平臺假設,訂單表order存放用戶的訂單數據,sql腳本以下(只是爲了演示,省略部分細節):
CREATE TABLE `order` (
`order_id` bigint(32) primary key auto_increment,
`user_id` bigint(32),
...
)
複製代碼
當數據比較大的時候,對數據進行分表操做,首先要肯定須要將數據平均分配到多少張表中,也就是:表容量。
這裏假設有100張表進行存儲,則咱們在進行存儲數據的時候,首先對用戶ID進行取模操做,根據 user_id%100 獲取對應的表進行存儲查詢操做,示意圖以下:
例如,user_id = 101 那麼,咱們在獲取值的時候的操做,能夠經過下邊的sql語句:
select * from order_1 where user_id= 101
複製代碼
其中,order_1是根據 101%100 計算所得,表示分表以後的第一章order表。
注意:
在實際的開發中,若是你使用MyBatis作持久層的話,MyBatis已經提供了很好得支持數據庫分表的功能,例如上述sql用MyBatis實現的話應該是:
接口定義:
/**
* 獲取用戶相關的訂單詳細信息
* @param tableNum 具體某一個表的編號
* @param userId 用戶ID
* @return 訂單列表
*/
public List<Order> getOrder(@Param("tableNum") int tableNum,@Param("userId") int userId);
複製代碼
xml配置映射文件:
<select id="getOrder" resultMap="BaseResultMap">
select * from order_${tableNum}
where user_id = #{userId}
</select>
複製代碼
其中${tableNum} 含義是直接讓參數加入到sql中,這是MyBatis支持的特性。
注意:
另外,在實際的開發中,咱們的用戶ID更多的多是經過UUID生成的,這樣的話,咱們能夠首先將UUID進行hash獲取到整數值,而後在進行取模操做。
數據庫分表可以解決單表數據量很大的時候數據查詢的效率問題,可是沒法給數據庫的併發操做帶來效率上的提升,由於分表的實質仍是在一個數據庫上進行的操做,很容易受數據庫IO性能的限制。
所以,如何將數據庫IO性能的問題平均分配出來,很顯然將數據進行分庫操做能夠很好地解決單臺數據庫的性能問題。
分庫策略與分表策略的實現很類似,最簡單的都是能夠經過取模的方式進行路由。
仍是上例,將用戶ID進行取模操做,這樣的話獲取到具體的某一個數據庫,一樣關鍵字有:
用戶ID、庫容量
路由的示意圖以下:
上圖中庫容量爲100。
一樣,若是用戶ID爲UUID請先hash而後在進行取模。
上述的配置中,數據庫分表能夠解決單表海量數據的查詢性能問題,分庫能夠解決單臺數據庫的併發訪問壓力問題。
有時候,咱們須要同時考慮這兩個問題,所以,咱們既須要對單表進行分表操做,還須要進行分庫操做,以便同時擴展系統的併發處理能力和提高單表的查詢性能,就是咱們使用到的分庫分表。
分庫分表的策略相對於前邊兩種複雜一些,一種常見的路由策略以下:
1、中間變量 = user_id%(庫數量*每一個庫的表數量);
2、庫序號 = 取整(中間變量/每一個庫的表數量);
3、表序號 = 中間變量%每一個庫的表數量;
複製代碼
例如:數據庫有256 個,每個庫中有1024個數據表,用戶的user_id=262145,按照上述的路由策略,可得:
1、中間變量 = 262145%(256*1024)= 1;
2、庫序號 = 取整(1/1024)= 0;
3、表序號 = 1%1024 = 1;
複製代碼
這樣的話,對於user_id=262145,將被路由到第0個數據庫的第1個表中。
示意圖以下:
關於分庫分表策略的選擇有不少種,上文中根據用戶ID應該是比較簡單的一種。其餘方式好比使用號段進行分區或者直接使用hash進行路由等。有興趣的能夠自行查找學習。
關於上文中提到的,若是用戶的ID是經過UUID的方式生成的話,咱們須要單獨的進行一次hash操做,而後在進行取模操做等,其實hash自己就是一種分庫分表的策略,使用hash進行路由策略的時候,咱們須要知道的是,也就是hash路由策略的優缺點,優勢是:數據分佈均勻;缺點是:數據遷移的時候麻煩,不能按照機器性能分攤數據。
上述的分庫和分表操做,查詢性能和併發能力都獲得了提升,可是還有一些須要注意的就是,例如:本來跨表的事物變成了分佈式事物;因爲記錄被切分到不一樣的數據庫和不一樣的數據表中,難以進行多表關聯查詢,而且不能不指定路由字段對數據進行查詢。分庫分表以後,若是咱們須要對系統進行進一步的擴陣容(路由策略變動),將變得很是不方便,須要咱們從新進行數據遷移。
上述中,咱們學到了如何進行數據庫的讀寫分離和分庫分表,那麼,是否是能夠實現一個可擴展、高性能、高併發的網站那?很顯然還不能夠!一個大型的網站使用到的技術遠不止這些,能夠說,這些都是其中的最基礎的一個環節,由於還有不少具體的細節咱們沒有掌握到,好比:數據庫的集羣控制,集羣的負載均衡,災難恢復,故障自動切換,事務管理等等技術。所以,還有不少須要去學習去研究的地方。
總之:
路漫漫其修遠兮,吾將上下而求索。
前方道路美好而光明,2019年新徵程,不泄步!