(1)可用性設計html
解決思路:複製+冗餘sql
反作用:複製+冗餘必定會引起一致性問題數據庫
保證「讀」高可用的方法:複製從庫,冗餘數據,以下圖緩存
解決方案:見下文網絡
保證「寫」高可用的通常方法:雙主模式,即複製主庫(不少公司用單master,此時沒法保證寫的可用性),冗餘數據,以下圖架構
解決方案:數據庫設計
a)方案一:由數據庫或者業務層保證key在兩個主上不衝突分佈式
b)方案二:見下文
58同城保證「寫」高可用的方法:「雙主」當「主從」用,不作讀寫分離,在「主」掛掉的狀況下,「從」(實際上是另一個主),頂上,以下圖
優勢:讀寫都到主,解決了一致性問題;「雙主」當「主從」用,解決了可用性問題
帶來的問題:讀性能如何擴充?解決方案見下文
(2)讀性能設計:如何擴展讀性能
最經常使用的方法是,創建索引
創建很是多的索引,反作用是:
a)下降了寫性能
b)索引佔內存多了,放在內存中的數據就少了,數據命中率就低了,IO次數就多了
可是否想到,不一樣的庫能夠創建不一樣的索引呢?以下圖
主庫只提供寫,不創建索引
online從庫只提供online讀,創建online讀索引
offline從庫只提供offline讀,創建offline讀索引
提升讀性能常見方案二,增長從庫
上文已經提到,這種方法會引起主從不一致問題,從庫越多,主從時延越長,不一致問題越嚴重
這種方案很常見,但58沒有采用
提升讀性能方案三,增長緩存
傳統緩存的用法是:
a)發生寫請求時,先淘汰緩存,再寫數據庫
b)發生讀請求時,先讀緩存,hit則返回,miss則讀數據庫並將數據入緩存(此時可能舊數據入緩存),以下圖
a)如上文所述,數據複製會引起一致性問題,因爲主從延時的存在,可能引起緩存與數據庫數據不一致
b)全部app業務層都要關注緩存,沒法屏蔽「主+從+緩存」的複雜性
58同城緩存使用方案:服務+數據+緩存
1)引入服務層屏蔽「數據庫+緩存」
2)不作讀寫分離,讀寫都到主的模式,不會引起不一致
(3)一致性設計
主從不一致解決方案
方案一:引入中間件
中間件將key上的寫路由到主,在必定時間範圍內(主從同步完成的經驗時間),該key上的讀也路由到主
方案二:讀寫都到主
上文已經提到,58同城採用了這種方法,不作讀寫分離,不會不一致
數據庫與緩存不一致解決方案
兩次淘汰法
異常的讀寫時序,或致使舊數據入緩存,一次淘汰不夠,要進行二次淘汰
a)發生寫請求時,先淘汰緩存,再寫數據庫,額外增長一個timer,必定時間(主從同步完成的經驗時間)後再次淘汰
b)發生讀請求時,先讀緩存,hit則返回,miss則讀數據庫並將數據入緩存(此時可能舊數據入緩存,但會被二次淘汰淘汰掉,最終不會引起不一致)
(4)擴展性設計
(4.1)58同城秒級別數據擴容
需求:原來水平切分爲N個庫,如今要擴充爲2N個庫,但願不影響服務,在秒級別完成
最開始,分爲2庫,0庫和1庫,均採用「雙主當主從用」的模式保證可用性
因爲是2擴4,不會存在數據遷移,原來的0庫變爲0庫+2庫,原來的1庫變爲1庫和3庫
此時損失的是數據的可用性
最後,解除舊的雙主同步(0庫和2庫不會數據衝突),爲了保證可用性增長新的雙主同步,並刪除掉多餘的數據
這種方案能夠秒級完成N庫到2N庫的擴容。
存在的問題:只能完成N庫擴2N庫的擴容(不須要數據遷移),非通用擴容方案(例如3庫擴4庫就沒法完成)
(4.2)非指數擴容,數據庫增長字段,數據遷移
[這些方法在(上)篇中都已經介紹過,此處再也不冗餘,有興趣的朋友回覆「同城」回看(上)篇]
方案一:追日誌方案
方案二:雙寫方案
(4.3)水平切分怎麼切
四類場景覆蓋99%拆庫業務
a)「單key」場景,用戶庫如何拆分: user(uid, XXOO)
b)「1對多」場景,帖子庫如何拆分: tiezi(tid, uid, XXOO)
c)「多對多」場景,好友庫如何拆分: friend(uid, friend_uid, XXOO)
d)「多key」場景,訂單庫如何拆分:order(oid, buyer_id, seller_id, XXOO)
[這些拆庫方案在(上)篇中都已經介紹過,此處再也不冗餘,有興趣的朋友回覆「同城」回看(上)篇]
(5)海量數據下,SQL怎麼玩
不會這麼玩
a)各類聯合查詢
b)子查詢
c)觸發器
d)用戶自定義函數
e)「事務」都用的不多
緣由:對數據庫性能影響極大
拆庫後,IN查詢怎麼玩[回覆「同城」回看(上)篇]
拆庫後,非Partition key的查詢怎麼玩[回覆「同城」回看(上)篇]
拆庫後,誇庫分頁怎麼玩?[回覆「同城」回看(上)篇]
問題的提出與抽象:ORDER BY xxx OFFSET xxx LIMIT xxx
單機方案:ORDER BY time OFFSET 10000 LIMIT 100
分庫後的難題:如何確認全局偏移量
分庫後傳統解決方案:查詢改寫+內存排序
a)ORDER BY time OFFSET 0 LIMIT 10000+100
b)對20200條記錄進行排序
c)返回第10000至10100條記錄
優化方案一:增長輔助id,以減小查詢量
優化方案二:模糊查詢
a)業務上:禁止查詢XX頁以後的數據
b)業務上:容許模糊返回 => 第100頁數據的精確性真這麼重要麼?
最後的大招!!!(因爲時間問題,只在DTCC2015上分享了喲)
優化方案三:終極方案,業務無損,查詢改寫與兩段查詢
需求:ORDER BY x OFFSET 10000 LIMIT 4; 如何在分庫下實現(假設分3庫)
步驟1、查詢改寫: ORDER BY x OFFSET 3333 LIMIT 4
[4,7,9,10] <= 1庫返回
[3,5,6,7] <= 2庫返回
[6,8,9,11] <= 3庫返回
步驟2、找到步驟一返回的min和max,即3和11
步驟3、經過min和max二次查詢:ORDER BY x WHERE x BETWEEN 3 AND 11
[3,4,7,9,10] <= 1庫返回,4在1庫offset是3333,因而3在1庫的offset是3332
[3,5,6,7,11] <= 2庫返回,3在2庫offset是3333
[3,5,6,8,9,11] <= 3庫返回,6在3庫offset是3333,因而3在3庫的offset是3331
步驟4、找出全局OFFSET
3是全局offset3332+3333+3331=9996
噹噹噹當,跳過3,3,3,4,因而全局OFFSET 10000 LIMIT 4是[5,5,6,6]
總結:58同城數據庫架構設計思路
(1)可用性,解決思路是冗餘(複製)
(1.1)讀可用性:多個從庫
(1.2)寫可用性:雙主模式 or 雙主當主從用(58的玩法)
(2)讀性能,三種方式擴充讀性能
(2.1)增長索引:主從上的索引能夠不同
(2.2)增長從庫
(2.3)增長緩存:服務+緩存+數據一套(58的玩法)
(3)一致性
(3.1)主從不一致:引入中間層 or 讀寫都走主庫(58的玩法)
(3.2)緩存不一致:雙淘汰來解決緩存不一致問題
(4)擴展性
(4.1)數據擴容:提高從庫,double主庫,秒級擴容
(4.2)字段擴展:追日誌法 or 雙寫法
(4.3)水平切分
(單key)用戶庫如何拆分:, user(uid XXOO)
(1對多)帖子庫如何拆分: tiezi(tid, uid, XXOO)
(多對多)好友庫如何拆分: friend(uid, friend_uid, XXOO)
(多key)訂單庫如何拆分:order(oid, buyer_id, seller_id, XXOO)
(5)SQL玩法
(5.0)不這麼玩:聯合查詢,子查詢,觸發器,自定義函數,事務
(5.1)IN查詢:分發MR or 拼裝成不一樣SQL語句
(5.2)非partition key查詢:定位一個庫 or 分發MR
(5.3)誇庫分頁
(5.3.1)修改sql語句,服務內排序
(5.3.2)引入特殊id,減小返回數量
(5.3.3)業務優化,容許模糊查詢
(5.3.4)查詢改寫,二段查詢
58同城的案例到這兒
另外,咱們回顧一下數據庫之父Codd的12條法則,做爲數據庫設計的指導性方針:
還有一些經驗:
若有想了解更多軟件,系統 IT,企業信息化 資訊,請關注個人微信訂閱號:
做者:Petter Liu
出處:http://www.cnblogs.com/wintersun/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
該文章也同時發佈在個人獨立博客中-Petter Liu Blog。