單機數據庫
分布式數據庫
TDDL原理與最佳實踐
1. 數據庫的結構
1.1. KV存儲(id是K)
1.2. B+樹與紅黑樹
B+樹的特點是葉子節點是塊狀,一個葉子里面有多個數據,相鄰數據是存在一起的,123,456起等,
而磁盤也是按塊的,B+樹的數據是按塊存儲的正好和磁盤的塊的概念是相符的,
所以在數據庫里面大多采用了B+樹或者類似的一種結構來存儲數據。
在java中實現treemap時選擇是紅黑樹而不是B+樹,B+面向的是磁盤的結構,
java的treemap面向的是內存,隨機讀寫數據快;
另外一個原因是,B+樹為了減少分裂的次數會在每個葉子結點中預留幾個空洞來存入未來的數據,
這個特點在磁盤中是可以的,因為磁盤都是白菜價,浪費一點沒有關系,但內存就不一樣了;
紅黑樹本質是一顆二叉樹,每個葉子只有一條數據,不需要預留任何的空間,
在內存中就不會造成空間的浪費,也就是這兩點,才選擇了紅黑樹來實現java中的treemap.
任何一種技術脫離了它的場景都是沒有意義的;
有些數據庫不是按行存的,是按列存的,這樣可對列的特點進行壓縮,減少存儲空間,
進行max/min等運算也快,但這種特點不適合更新;
沒有一種數據庫讀的快寫的也快,讀寫性能的差距也是由於背后所使用的存儲不同導致的,
如hbase使用的是lsm-tree這種存儲,它的特點是寫的很快,順序寫,但讀的稍微有點慢,
所以hbase經常用來存日志,產生的量,對讀的性能要求不高。
要判斷讀的多還是寫的多,如果讀的多,還要判斷讀的特點,是隨機讀還是范圍的讀,寫的話,
是只寫還是會有刪除和更新;B樹是面向磁盤的,紅黑樹是面向內存的;
2. 索引查詢優化器
主表和索引表都是映射,查詢如果使用到了索引,就會分為兩步,第一步先查索引,第二步通過索引再查主表,
這一步叫回表。如果查詢中的列(seletc a,b)都是索引中的列,那么就只有一步,只要查索引表就可以了,
就不需要回表了,這叫索引覆蓋。 如果索引包含所有的列,也就是主鍵對記錄的索引,就是聚簇索引。
如果索引包含部分的列,就是非聚簇索引, 非聚簇索引在查詢的時候就可能需要做一個回表的操作才能查出所有的列來。
索引太多時有兩個缺點, 占空間,對記錄進行寫操作時也要修改索引,寫入性能的下降。
沒有索引時就會全表掃描。
主表的每一條記錄都是一個KV映射,K就是主鍵,V就是整條記錄,而索引就是列對K的索引而已。
區分度比較高就比較適合做索引列。
從某種程度上說,索引也是一種關系型數據庫的表。
關系型的數據庫都有索引的概念,而NoSQL是沒有索引的概念的,比如hbase就沒有索引,只能按照rowkey來查,
不能按照其他字段來查。
sql語句會經過解析器,經過解析變成一個數據結構, 查詢優化器會分析這個數據結構是什么意思,怎樣執行會比較快,
會生成一個執行計划,這個計划就包括查字典的整個過程。
SQL->Optimizer->plan,底下的存儲按照plan就會得到高效而且准確的結果。
那么查詢優化器會做哪些事情呢?
1.索引選擇
當where有兩個或者多個列都有索引時,就會涉及到索引選擇的問題。一般會根據區分度選擇合適的索引。
2.下推
select t1.name,t2.name from t1 join t2 on t1.id = t2.id
就是說會讓一些操作提前執行來減少中間過程的數據,也就是優化的過程。
3.join策略的選擇(自己了解)
index netxt roup、block next roup,
mysql語句前加上extern就會展示這條語句的執行計划,能大概讀懂
事務也是區分關系型數據庫與nosql的指標
3. 分布式數據庫
產生分布式數據庫的原因,最重要的有兩個:
1.單機的硬盤容量(可能還包括內存容量、cpu容量、網絡容量)不夠,需要存儲到多塊硬盤上
2.安全性原因,數據庫存到多個地方備份
RAID磁盤陣列的出現與分布式數據庫的出現相似,一個是空間,一個是安全,
RAID0的概念是把數據拆開存儲在兩塊硬盤上,比如C盤數據存儲在A上,D盤存儲在B上,RAID1的概念是把數據復制存儲在兩個地方,比如CD盤在A盤上存一份,也在B盤上存一份。
RAID01或者RAID10就是把兩者結合起來,數據既分塊,又對每塊數據進行備份。
對應到分布式數據庫里面,就有了這樣的概念:
RAID0對應分布式數據庫里的Partition(擴展性,一個分區不夠,繼續擴)
RAID1對應分布式數據庫里的Replication(安全性,一個掛了,不會掉數據)
如何將數據復制到不同的地方去?
1.異步?
2.同步?
假設有兩塊磁盤0和1(或者就是兩個數據庫),那如何保證兩個數據是完全一樣的?
一種方式是同時向兩個地方寫,寫完就是成功了;
另一種方式是只寫0,讓0去寫1,這就產生了如何返回數據寫成功了?同步是0寫完1才返回成功,
異步是寫完0就返回成功(如果此時就去讀1就會出現還沒有寫入的數據,就會出現延遲,好處是不用等所有的庫都寫完,它的寫入的延遲會低一些),這是兩個點的情況,如果有更多的點(就是把一個點的數據復制了好幾份),則就不能接受只有同步或者只有異步(只寫一個點就返回)的情況了,就要把同步和異步結合起來,即每次同步寫數個點后返回成功,剩下異步寫剩下的點。
那在分布式數據庫中如何才能保證讀到的數據是一致或者最新的呢?
W+R>N,W代表同步寫的個數,N表示總結點個數,即寫的少,讀的就越多,寫的多,讀的就越少。
mysql是有主庫和備庫的區別的,主備庫數據是完全一樣的。內部的方案是讀寫都在主庫,備庫全部采用異步的方式備份。這是犧牲了備庫的讀性能來換取的,可以講到同步的數據又不要同步的復制,來保證寫的性能的提升。
如果主庫掛掉的話,怎么從剩下的備庫中選舉主庫呢?大多是用PAXOS算法或者衍生的算法來選舉主庫,主要原理是獲取多數支持的那一個才能是主庫。這樣就是自動的的過程,內部往往簡單粗暴的,當主庫掛掉的時候人工選擇一個備庫當主庫。
partition和replication在單機數據庫中也是存在的,在單機數據庫是也有分區表的概念,雖然是同一個機器,也會把表分成了好幾個部分, 在單機數據庫里面也會用磁盤陳列的方式做一些冗余,在單機數據庫里也會這兩種概念,那在分布式數據庫要把這兩點單獨的拿出來說呢?最重要的是因為分布式數據庫多了網絡,延遲變大了,在單機里cpu總線的延遲是納秒級,走網絡的話延遲可能是毫秒級的。正是因為有延遲在在分布式數據庫中才會把這兩個東西單獨拿出來說。
以上主要是replication的講解;
partition的講解在tddl里;
4. Taobao Distributed Data Layer
TDDL產生背景:
單一數據庫無法滿足性能需求(數據切分 讀寫分離(只寫主庫只讀備庫))
容災(主備切換)
配置變更(動態數據源(不需要數據庫的賬號信息,哪天數據庫ip地址換了也沒關系 只需要依賴appname tddl會自動地讀取數據庫賬號用戶名密碼 當這些發生變更的時候會自動的通知到tddl,對用戶無感知))
切分有兩種水平切分和垂直切分
垂直切分不能無限切分, 因為列是有限的,而水平切分是可以無限擴展的, 只要行數是無限的。tddl能做的就是水平拆分,因為垂直拆分就是sql中的join操作
主備切換也是tddl的功能,當主庫掛掉的時候,會自動切換到備庫上。
水平拆分不一定按照id字段來拆,也可以選擇其他字段,比如某個字段全是字符串,可以先求出它的hash值,再對1024取模(假如拆了1024張表,此時也可以再對表進行分庫,如0-155屬於庫1),總之要按照字段本身的特點。
這就是所謂的拆分算法。
分布式數據庫都會有兩個特點一個是partition,一個是replication.
那么hbase使用了一種B樹的拆分方式,也就是說hbase各個存數據的節點(也就是分表的表)可以看作B樹里面的一個節點,它在路由的時候就會根據樹的算法來算出這條數據應該在哪一個節點里面,因為它使用了樹,所以它繼承了樹的一些特點,所以在hbase中可以很方便來做一些范圍的查詢, 比如rowkey大於一個值或者小於一個值。但tddl默認使用的是一種hash算法,就很難做一個范圍的查詢, 比如1-1000在hbase中可能也就跨了幾個分區布局,但是如果在tddl中可能就跨了所有的分區,因為hash算法是一種零散的算法。但為什么內部會默認使用hash算法來分表呢,因為內部經常做的操作是根據買家id\賣家id\羊肉串id\定單id來查,這樣的話用hash算法就比較快。
拆分字段的選擇跟選索引差不多,選拆分字段的時候基本會選查詢都會帶上的那種列,比如說買家id賣家id,還有就是區分度的概念,用這個字段會路由到比較少的表中,而不是會路由1000張表。但如果像定單這種東西,它既有買家id又有賣家id,同時買家和賣家都會去關注,那選個字段作為拆分字段呢? 答案是兩個都選,數據復制一份,一份按買家id來路由分表,另一份按賣家id來進行路由分表,也就是數據會存兩份,相對應的我們就可以把買家和賣家理解為兩種索引,因為是兩種映射嘛。並且這種索引也可以做成聚簇,也可以做成非聚簇的,比如說在冗余的時候按所有字段進行冗余一遍,這樣在查的時候就會查的比較快。
規則路由—擴容
怎樣擴容才能減少數據的移動,比如以前是模32,現在變成模40了,就要把以前的結果按40重新模一遍,怎樣選擇才能減少數據移動呢?
一般做法是一倍一倍的擴,比如以前是32,擴容后就變成64了。因為這種情況下只有一半的數據是需要移動的。
多維拆分實際是把數據按照兩維度冗余了一份
組合索引就是先按一個列索引,再按另一個列索引
多個字段同時作為拆分字段,實際上數據只存了一份
兩個字段就是二維,只提供一個字段就會定位到好幾個張分表
三個字段就是三維
tddl三層數據源
最底層是atom層,對應的是一個數據庫,對應的就是一個ip用戶名密碼,一主一備就構成了第二層,就會變成一個group,同一個group會包含多個atom,但atom數據是一樣的,也就是說group會完成讀寫分離這樣一個操作,也可以完成主備切換,這兩層就是絕大數應用使用的模型,因為絕大多數應用是小應用不需要做拆分,只要做讀分離和主備切換這樣的功能。多個group之后會組成一個matrix層,當我們分庫之后,多個庫會組成一個matrix,matrix層負責來做切分這樣一個操作,就會有規則計算這樣一個功能,就會算出sql應該到哪個group去。這就是tddl三層數據源的一個概念。
TDDL5分為三層數據源結構
Matrix,主要負責數據的水平切分
Group,主要負責主備與讀寫分離
Atom,主要負責物理數據源的管理
TDDL5中一個查詢操作在三層數據源中的流程如下圖:
使用者通過JDBC,將SQL傳遞到TDDL5中,會按照以下流程進行執行:
SQL Parser會將SQL解析並生成關系查詢樹;
Query Optimizer對關系查詢樹進行優化,生成由KV查詢組成的執行計划;
在這個過程中,還將根據切分規則,對條件id=2與id=3進行計算,得出數據所在的節點。如在此例中,根據id % 3,得出id=2的數據在Group1上,id=3的數據在Group0上;
Client會將執行計划發送給Matrix層,Matrix層會按照執行計划中指定的Group,將執行計划發送給對應的Group,在Group層,由於非強一致的讀請求,會將讀操作隨機分配到數據節點Atom上;
最終數據在Matrix上進行合並之后,結果返回給Client。
matrix層:
Parser
Optimizer
語法解析
規則計算
查詢優化
Executor
聚合/Join/函數計算
Repo(存儲層)
Mysql
Group:主備切換,讀寫分離
Atom:動態數據源
Oceanbase/Hbase/Skiplist/Search(支持多存儲)
TDDL的查詢優化器有一個最為核心的理念:offload,也是單機優化器里面講到的一個基本的概念主是下推,下推的意思就是說盡量多的操作會讓mysql自己去執行,比如說一些條件的過濾,一些join能讓mysql去做的話就讓它先來做,比如說索引選擇,列的過濾,一些聚合函數的計算,一些排序,一些distinct去重這樣的操作,能讓mysql來做就讓它來做,為什么呢?和單機數據庫一樣的道理,盡量減少中間數據,減少跨網絡的延遲,offload的思想也是貫穿tddl基本的理念。也是tddl最佳實踐最基本的思想。