自從谷歌提出分布式這個概念,這個玩意太火了,但是並不是所有的業務場景都適合用分布式的
什么場景適合用分布式架構?
- 網易分布式用的最好的兩個項目:網易考拉 && 網易雲音樂(歌單庫單表百億以上記錄、評論庫)
- 快遞行業
- 微信紅包
其他業務都是有時間維度的,可能只需要存3個月的在線數據,算下來也就2kw,那為什么還要做分布式架構呢?
所以不要迷信分布式架構和分庫分表
tips:
單台服務器幾千qps沒必要分,兩三萬的話可以分
Ⅰ、分布式數據庫特點
優點
- 可用性提高
- 可擴展性提升(扯淡,MySQL現在4個節點擴8個節點怎么辦?數據庫不是redis,redis里面內存數據可以丟掉,甚至redis單線程的做這些都好弄,MySQL並不是,8擴12的話,原來的數據都要打散一遍,原來8個機器,hash8個,現在變12個,數據全部打散,如果8*500G,4T數據打散到12個節點,怎么打散,這么多表),MySQL可擴展性的提升是很難達到的,我們借助另外一些手段來做擴展性,為什么mongodb,redis,tidb擴展性是好的,因為他們的分片策略更好(也有缺點),是未來比較好的模式,但是在關系型數據庫中很難實現,對MySQL內核和中間件的改造太大了
- 某些情況下吞吐率提升顯著 ---大大的問號,只有某些場景才會顯著提升
原來一台機器,拆成八台機器,那就性能提升八倍?這是不存在的
缺點
- 依賴中間件(不依賴中間件就要在業務層做),業界基本上找不到一個好的中間件,mycat可以用,但是生產上不建議
- SQL語句支持不足(中間件支持的sql非常有限,多表join,子查詢,派生表這些估計玩不轉,mongodb,redis,tidb不做這些)
- 運維復雜度提高
- 某些情況下性能下降巨大
Ⅱ、分布式數據庫——shard
2.1 shard相關概念
A database shard is a horizontal partition(水平分區) of data in a database. Each individual partition is referred to as a shard or database shard. Each shard is held on a separete database server instance,to spread load.
sharding和分區的異同
相同:打散數據
不同:打散的數據可能不在同一實例上
總結:sharding的實現非常復雜
舉個栗子
orders表,根據日期做分區,每個月一張訂單表,還是在一台MySQL上
做分片的話,把一張表,分到四台MySQL上去
在分區中:
partition by hash(id)
這個id叫做分區鍵
在shard中:把這個分區鍵叫作均衡字段
把id 作hash,%4 來分到四個機器上,把date 作range也可以
如果根據order_key分了四個機器
select * from orders where order_key = ?
這樣性能就提升了
怎么找到這個機器呢:
- 法1:業務層控制,根據分區規則搞,如果order_key是int類型,那就%4,根據結果下發到對應的Datanode(ip)
- 法2:中間件,client不用關心后端的分庫規則,均衡字段這些,client直接通過MySQL協議訪問中間件,中間件自動幫你計算該訪問哪個節點,sql的改寫由中間件來做,像我們上面這個sql就不用改寫,只要轉一下ip就行
兩個問題:
- 兩種方法:哪個性能更好,為什么?
法1更好,直接連數據庫,法2多了一層,中間件的優勢在於,開發人員更方便寫代碼,就和直接連數據庫一樣用,其他事情都交給中間件 - 業務里面永遠都是只有這么一個簡單的需求嘛?如果根據其他字段查詢呢?會有上面問題呢?
那就所有的分片都要找一遍,你加N多個節點,這樣還叫可擴展嗎?節點越多,性能越差了都,和分區表的是一樣的道理,orders_partition表中可以explain看下兩種情況,走索引的話,1億的數據量下,和1kw數據量下速度是一樣的,你分十個片沒什么意義
2.2 做shard的三個原則
- 盡可能大部分的業務邏輯都是根據均衡字段,至少百分之80%,更改分布式數據庫中的均衡字段非常麻煩,必須一開始就規划號表的均衡字段
- 如果不能選擇有效的均衡字段,這張表就不要進行Shard,作為全局表
- 如果業務選擇不出有效率的均衡字段,那么進行分布式數據庫的改造也將是徒勞的
tips:shard一般用hash打散,平均的,如果沒有打散就是均衡字段沒選好,可以選擇用多個字段作均衡字段
2.3 看看別人的業務怎么選擇均衡字段的
你平時怎么逛淘寶的?
點開淘寶,所有的查詢都是買家查詢,你能查你老婆買了哪些東西嗎?所有的查詢都是根據自己的id來查(登陸是通過用戶中心來登陸的)
下單了,訂單里有userid,訂單明細中也有userid,登陸到淘寶,查最近一些訂單信息,購買記錄,也是根據userid來查的
所以電商的表結構設計,有張user表,用來存用戶信息的,里面有個userid
又有一個orders表,訂單表里面也有個userid
還有個orderline表,一個訂單里下了多少個商品,里面也有個userid
可能還有個favorite表,里面也有一個userid
coupon表,還是有個userid
綜上,所有買家的數據都有一個userid的字段,這時候 上面這些表就可以根據userid放到不同的shard中
這意味着什么呢?一個用戶的所有信息是在一個分片上的,是不會跨分片的,不存在一個查詢要查很多個分片的
所以淘寶的業務是非常容易做擴展的,只要加機器,把數據打散,就是可擴展的
所以對於電商的業務來說,有個非常好的維度叫買家維度,所有的查詢基本上都是根據買家id
那網易雲音樂的歌單又是怎么弄的呢?
也是userid啊,哈哈,看自己的歌單,看別人的歌單,都是用的userid
快遞行業用的是什么?
運單號
微信紅包的均衡字段是什么?
也是userid,哦嚯
上面所有的都要用中間件,一般用mycat
Ⅲ、分布式最大的問題——分布式事務
3.1 微信紅包案例
user<--->user<--->user
中間件
client
如果一台機器
begin;
update user set money = money - 100 where userid = xxx;
update user set money = money + 100 where userid = xxx;
commit;
那走分布式事務的話,很有可能這兩個userid不在同一個分片里面
這個事務就變成了分布式事務,首先,mycat不支持分布式事務,但是這么寫是支持的
意味着着不是一個原子操作,變成了兩個事務分到兩個分片上去,做不到原子性,所以千萬不要把mycat用在關鍵的業務,特別是跟錢打交道的
現在業界的中間件,除了淘寶和網易貌似還沒有什么中間件支持分布式事務的,那怎么辦呢?
分布式事務改為單個事務,之前提過的補償機制,做一個message表,update的時候往message表中插入消息,另一張表,查看消息,然后update,最后把消息置為已接收
這時候還需要中間件嗎?no!這也叫一種柔性事務
3.2 用戶下單案例
orders表,三個分片,userid作均衡字段,還有個表,叫stock(庫存)
下單的邏輯就變得不一樣了,看tpcc樣例
begin;
先鎖定庫存
insert into orders
insert into orderline
update stock set count = count - 1 where itemid = ?
update
...
xxx
...
commit;
orders表分庫分表了,stock表沒有userid這東西,所以stock表叫全局表,我們該怎么放,可以放到單獨的實例上,也可以放到其中的一個分片上,甚至可以根據skuid去做shard
每個MySQL是用來存分片的,但並不要求一個分布式集群中所有的表均衡字段是一樣的,業務只要知道怎么存的就行,均衡字段是什么,怎么訪問,自己知道就行,對於中間件來說有個元數據庫來存這些均衡字段
這時候這個流程就是分布式的了,淘寶網易的中間件可以支持這個,如果stock做了分片就討厭了,一個訂單可以有很多件商品,很多個庫存要修改,涉及的節點可能就多了,不像轉賬了,所以訂單表放到消息隊列(mq)中做了
要下單的話,先放到mq中,后面很多worker線程,拿到這個消息然后去消費
所以,現在打開京東,天貓這些,下一個單之后,狀態顯示為出庫中,什么叫出庫中?
出庫中就表示,我現在這個下單,只是把業務邏輯丟到消息隊列中,至於消息隊列后面能不能成功並不知道,如果庫存不夠會告訴你出庫失敗,然后把你這個下單過程回滾掉
所以經常下單成功,出庫失敗,不是1111,一般都不會有問題,1111的時候就不一定了,實時庫存代價太大了
3.3 小結
到現在為止,還用得着中間件嘛?不是可以不用,而是建議不用,因為用了中間件,你避免不了分布式事務,很多核心業務有風險的
根據userid來擴展,做查詢沒問題啊,但是對於數據庫來說,分庫分表最難的點在於事務化和持久化,而不是select,甚至select放到redis里都可以
分布式事務到最后都用了消息表和消息隊列,業務邏輯都丟到消息里,后面消費線程慢慢消費,出錯了,就回滾,關閉訂單,訂單表中有個狀態字段,可見,不可見,成功,沒成功,都是通過業務來控制的
微信紅包用的就不是中間件,淘寶的下單過程也沒有用中間件做,存在分布式事務,性能不好
用分布式事務的協調器來做的,淘寶最早提出tcc,現在基於這個封裝了一堆東西了都
所以中間件這東西很危險不要想的太多了,如果用了中間件,那也是個很簡單的東西,淘寶,微信核心業務都不用中間件,所有的訪問都是業務控制的。
建議:
如果分庫分表,最好讓業務控制,而不要用中間件,用了中間件這個活兒就是你的,說難聽點,就是甩鍋,以后出了問題就跟我沒關系,第二點,核心業務,中間件不支持分布式事務,保證不了事務性和持久性,第三點,中間件性能真的不行,即使用了ddb,一個訂單三個商品垮了好多個節點,雙十一能接受嗎?不要迷信中間件
但是,快遞行業真的很適合中間件,因為快遞行業沒有很強的事務性要求,就是一個個運單,這個運單要周轉幾次,現在的中轉狀態,所以快遞行業是中間件使用的最好的場景,如果互聯網金融用了中間件,碰到了分布式事務就很也容易呵呵
Ⅳ、理解分庫和分表呢
shard是一個標准的說法,但是分庫分表根本沒有完整的定義,很模糊
最早的時候:
- 分表:orders ---> orders00,orders01,,,,
- 分庫:db1,db2中都有orders00,orders01,當然也可以分到其他實例中的db中
這樣表名改了,不好維護,網易的中間件就不支持分表,那就只分庫吧,最好是同一個實例上建很多個不同的db,擴展起來不用打散數據
所以這里又有個淘寶的規范,一開始可能就會分1024個shard,不管多少個機器,一開始放在100個機器上,雙十一放到1000個機器上,具體落到哪個shard還是用hash
不能2048個shard,MySQL數據庫擴展是不做reshard的,重分布的代價太大,所以就拆,在一開始在同一個機器上做很多個shard
一開始就把shard就划好,擴展的時候不用每次都去打散數據,換句話說,這樣做的是垂直擴展,雖然說分布式數據庫可擴展,但是因為hash的擴展很麻煩,涉及到數據的重分布,所以盡量垂直擴展,數據打散水平打散,擴展的話垂直擴展
mongodb的擴展方便,是基於chunck的,先把所有的數據放到chunck里,如果超過32M會做split,拆成兩個chunck,然后做一個balance的操作,把兩個chunck放到兩台服務器上,所以這個chunck是可以移動的,mysql的hash不可移動
mongodb的balance也有問題,牽涉到一台服務器的chunck拷貝到另一台服務器
各有優缺點,擴展方便,新加一台機器,可以把各個機器上的chunck移過來,不用hash那樣整個集群的重分布,只拿一部分chunck過來,但是io會很大