最初的數據庫架構
如上圖用戶庫,直接單庫,讀寫都在這一個庫上面
這種架構最先遇到的問題就是,隨着業務增長帶來的讀瓶頸,因爲大多數都是讀多寫少的問題
如何解決?
主從同步,讀寫分離,擴充讀性能
主從會碰到什麼問題?主從數據不一致
解決什麼問題?分組+分片, 解決存儲容量的問題,提升讀性能,提升寫性能
帶來什麼問題? 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