數據庫架構演進之路

最初的數據庫架構

如上圖用戶庫,直接單庫,讀寫都在這一個庫上面

這種架構最先遇到的問題就是,隨着業務增長帶來的讀瓶頸,因爲大多數都是讀多寫少的問題

如何解決?

主從同步,讀寫分離,擴充讀性能

主從會碰到什麼問題?主從數據不一致

解決什麼問題?分組+分片, 解決存儲容量的問題,提升讀性能,提升寫性能

帶來什麼問題? sql擴展性的問題,犧牲掉sql的一些特性

沒有解決什麼問題?

1.高可用的問題

2.路由的問題

1.範圍做路由: 1-1kw在0庫,1kw-2kw在1庫等等,

一、會導致負載不均衡等等,

假如uid前1kw的都是活躍用戶,導致0庫負載高。擴展性高

2.哈希:解決負載不均,數據量負載不均,訪問不均,只要哈希的key比較均衡。

一、擴展性差,從2庫到4庫,需要遷移數據。

二、業務方需要關注路由規則。

解法一:搞個路由服務,解耦,

缺點:額外的一次調用服務

 

垂直拆分

user單庫拆分成 user_base和user_ext, 訪問頻繁但是比較短的數據放到user_base庫裏面,訪問比較少,數據比較多的放到user_ext

解決什麼問題?

帶來讀性能的提升,爲啥?數據buffer的緩存,關係型數據庫以行存儲的,數據少緩存的更多。

帶來什麼問題?

多次數據庫訪問,在業務層做聚合

沒有解決什麼問題?

高可用,擴展 

垂直拆分和業務聯繫比較緊密,具體業務具體分析

二、高可用

典型的服務架構

判斷系統是否高可用:關掉其中一臺機器,試試訪問是否正常,嘿嘿嘿

看其他層的高可用是如何保證的,對於數據層是否有啓示?

反向代理層nginx高可用

web層高可用

server層高可用

緩存高可用

可以看出基本都是複製冗餘

如何保證讀庫的高可用?

存在什麼問題:

主從延遲的問題,數據冗餘會引發一致性問題

怎麼解決:

下面

如何保證寫庫的高可用?

存在什麼問題?

數據一致問題,寫複雜

怎麼解決?

下面

讀寫高可用

讀寫一致性問題,緩解

帶來問題:

讀性能緩解不了

 

如何提升讀的性能?

1,索引

一、寫性能降低

二、索引佔用內存大,數據命中率低,讀緩存也會有問題,太多索引也會引起讀性能問題

有什麼優化方案?

一主多從, 如果後臺有大數據量的訪問需求,拿出其中一個從庫做備庫,按自己的需要建立索引,讀分離,減少對線上的影響,類似mongo的隱藏節點。

現在建索引都是在主庫鍵,然後同步到從庫上去。

主庫其實沒必要建索引:如果只寫的話,帶來的問題是配置不一致

2.提升讀性能,增加從庫

存在什麼問題?

(1)從庫越多,同步越慢

(2)數據不一致

怎麼優化?

見下面

3.增加緩存

未服務化

已經服務化

帶來的問題:

1.數據不一致 2.業務修改

 

緩存的最佳實踐

Cache Aside Patern

對於讀請求

1.先讀cache,再讀db

2.如果cache hit,則直接返回數據

3.如果cache miss,則訪問db,並將數據set回緩存

寫請求

1.淘汰緩存,而不是更新緩存

2.寫操作先操作數據庫,再淘汰緩存:

爲什麼淘汰緩存,而不是更新緩存

淘汰緩存帶來的成本:

1.是多一次cache miss

更新緩存:

1.寫併發的一致性問題

2.回寫成本可能比較高,比較複雜的業務,可能需要取回db或者緩存裏的數據,計算完再set回去。

爲什麼先寫數據庫,再淘汰緩存?

假設先淘汰緩存,會出現併發讀寫(概率大),可能不一致,但其實不根治

Cache Aside Patern存在什麼問題?

如果先操作數據庫,再淘汰緩存,在原子性被破壞時:

(1)修改數據庫成功了

(2)淘汰緩存失敗了

導致,數據庫與緩存的數據不一致,原子性被破壞(概率小),可能不一致

 

四、一致性優化

爲什麼會出現?

這個不一致其實很短的

二、行業內的優化方案

方案一:忽略

任何脫離業務的架構設計都是耍流氓,絕大部分業務,例如:百度搜索,淘寶訂單,QQ消息,58帖子都允許短時間內不一致

方案二:強制讀主,緩存也會用的

方案三:選擇性讀主

1.寫時記錄

2.讀時判斷

如何實現?

中間件知道哪個庫,哪個表,哪個key是寫操作或者讀操作。 對於需要比較強一致的讀請求的話,由這個中間層去讀主庫,而不是去讀從庫,但是不需要比較一致性的讀請求,還落到從庫上。

使用redis或者memcache,將寫請求的key設置一個比較短的過期時間(主從同步延時時間)。 讀的話先去查看緩存裏有沒有,有的話表示發生過改動,去讀主庫。也就是db和緩存雙寫。

緩存不一致

爲什麼會出現這個問題?

寫後立即讀問題,主從同步延遲問題,髒數據進緩存,且一直不淘汰

優化方案:二次淘汰緩存,消除主從不一致帶來的副作用

如何解決?

1.異步淘汰緩存,確保從庫已經同步成功

2.設定超時時間,極限情況下有機會修正-》設置比較合適的過期時間,過期了會自動修正

3.獲取自己在service層定時異步 二次淘汰

如何知道從庫已經同步成功?

監聽從庫的binlog,當業務的某一個key在從庫上寫成功了,再去淘汰cache

五、擴展性優化

典型爲服務架構數據庫擴容

典型場景

特點:數據量大/吞吐量大/高可用

系統架構:微服務

數據層如何高可用

數據層如何擴展

綜合情況

要解決什麼問題?

一.吞吐量持續增大,如何進一步增加實例?

二.數據量持續增大,如何進一步水平擴展?

1.停服擴容。最簡單的

2.追日誌

1.記錄日誌(服務升級),對db寫操作的要寫一行日誌,打印時間,db,table,primary_key等等

2.數據遷移(小工具1)

3.數據補齊(小工具2),日誌裏記錄的增量數據

4.數據校驗(小工具3)

3.雙寫

假設某一條記錄在遷移,突然這條數據被刪了,舊庫沒了,新庫還在遷移,所以就又把這條數據寫進去了,所以我們需要遷移校驗工具

4.雙倍擴容法,只支持2庫變4庫,4庫變8庫等雙倍擴容,但不需要改代碼

1.改配置

2.reload配置

3.收尾

4.最終結果

各類業務場景水平切分實踐

如何拆?用哪個屬性拆分?

四類典型場景

單key 用戶庫如何拆分:user(uid,xxoo)

1對多 帖子庫如何拆分:tiezi(tid,uid,xxoo)

多對多 好友庫如何拆分:friend(uid,friend_uid,xxoo)

多key 訂單庫如何拆分:order(oid, buyer_id,seller_id, xxoo)

用戶庫,10億數據量

user(uid,uname,passwd,age,sex,create_time,...)

業務需求

(1)1%登錄請求 =》where uname=xxx and passwd=xxx

(2)99%查詢請求 =》where uid=xxx

方案:按uid分庫分表

那login_name上的查詢怎麼樣?

用戶中心實踐

1.索引表

建立個索引表有2個字段,uid和login_name

多了一次數據庫訪問

索引表存不下,再按login_name分庫

2.緩存映射法

原理相同,使用redis等記錄

3.login_name生成uid

會有衝突,很少這麼幹

4.基因法:應用場景比較侷限, 基因不可變。不支持多種登錄方式:郵箱,手機號等等。

這個3位最多支持8個庫,應該多考慮最多的庫數,決定多少位。

除了login_name後臺複雜,批量,分頁的查詢怎麼辦?

前臺與後臺分離:

通過數據層解耦

通過服務層解耦

帖子庫拆分?

帖子庫,15億數據量

tiezi(tid, uid, title, content, time);

業務需求如下:

(1)查詢帖子詳情(90%請求)

select * from tiezi where tid = $tid

(2)查詢用戶所有發帖(10%請求)

select *from tiezi where uid=$uid

帖子中心實戰

按照uid分庫

同一個uid的所有tid在一個庫

tid中融入uid基因

當然也可以和上面引入臨時表來解決,存儲的就比較多了

除了uid/tid,標題/內容的查詢怎麼辦?使用搜索引擎

引入的問題: 數據不一致

定期全量檢測並修復數據

好友庫拆分實戰

好友庫,1億數據量

friend(uid,friend_uid,nick,memo,xxoo);

業務需求如下:

(1)查詢我的好友(50%請求)=》用於界面展示

select friend_uid from friend where uid = $my_uid

(2)查詢加我爲好友的用戶(50%請求)=》用戶反向通知

select uid from friend where friend_uid = $my_uid

不管通過uid/fuid哪個分庫,只能滿足一個查詢,這時候的基因法不好用了,因爲我們控制不了fuid,意思就是我們不知道哪個uid會加好友,所以加不了基因。

好友中心實踐:數據冗餘

服務同步冗餘

服務異步冗餘

線下異步冗餘

數據冗餘有什麼副作用?

數據不一致

如何保證數據一致性?

最終一致性是常見實踐

 

實踐:

1.線下掃全庫:

缺點:慢, 一致性的數據會被多次掃描

2.線下掃增量

3.線上實時檢測

 

訂單庫如何拆分?

訂單庫,10億數據量

order(oid,buyer_id,seller_id, order_info, xxoo)

業務需求如下:

(1)查詢訂單信息(80%請求)

select * from order where oid = $oid

(2)查詢我買的東西(19%請求)

select * from order where buyer_id = $my_uid

(3)查詢我賣出的東西(1%請求)

select * from order where seller_id = $my_uid

基因法和數據冗餘綜合應用

 

---------------------------------------------------------------------------------以下內容廢棄----------------------------------------------------------------------------------------

 

 

 

 

1.中間件 

中間件知道哪個庫,哪個表,哪個key是寫操作或者讀操作。 對於需要比較強一致的讀請求的話,由這個中間層去讀主庫,而不是去讀從庫,但是不需要比較一致性的讀請求,還落到從庫上。

如何實現?

使用redis或者memcache,將寫請求的key設置一個比較短的過期時間(主從同步延時時間)。 讀的話先去查看緩存裏有沒有,有的話表示發生過改動,去讀主庫。也就是db和緩存雙寫。

2.強制讀主庫 

 

這個從庫就沒啥意義了,讀寫都在寫庫上

會有什麼問題?

主庫單點問題,不高可用,也沒有解決讀性能

怎麼解決?

使用雙主, 主主之間同步。

如何解決讀性能問題

服務化+緩存

有什麼問題? 緩存不一致

緩存不一致,如何解決?

1.Cache Aside Patern

淘汰緩存,而不是更新緩存

讀請求有緩存先讀緩存,如果數據命中直接返回,未命中讀從庫

寫請求,先寫數據庫,再淘汰緩存:

 

爲什麼會不一致?

問題1:先寫數據庫,再淘汰緩存,立刻發生了個讀操作, 主從同步延時,從庫還是舊數據,又把髒數據放到了緩存裏面。

如何解決?

異步淘汰緩存,確保從庫已經同步成功

設定超時時間,極限情況下有機會修正-》設置比較合適的過期時間,過期了會自動修正

如何知道從庫已經同步成功?

監聽從庫的binlog,當業務的某一個key在從庫上寫成功了,再去淘汰cache

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章