数据库性能优化—主从分离
文章出自:阿里巴巴十億級(jí)并發(fā)系統(tǒng)設(shè)計(jì)(2021版)
鏈接:https://pan.baidu.com/s/1lbqQhDWjdZe1CBU-6U4jhA?提取碼:8888?
上節(jié)課,我們用池化技術(shù)解決了數(shù)據(jù)庫(kù)連接復(fù)用的問(wèn)題,這時(shí),你的垂直電商系統(tǒng)雖然整體架構(gòu)上沒(méi)有變化,但是和數(shù)據(jù)庫(kù)交互的過(guò)程有了變化,在你的 Web 工程和數(shù)據(jù)庫(kù)之間增加了數(shù)據(jù)庫(kù)連接池,減少了頻繁創(chuàng)建連接的成本,從上節(jié)課的測(cè)試來(lái)看性能上可以提升 80%。現(xiàn)在的架構(gòu)圖如下所示:
此時(shí),你的數(shù)據(jù)庫(kù)還是單機(jī)部署,依據(jù)一些云廠商的 Benchmark 的結(jié)果,在 4 核 8G 的 機(jī)器上運(yùn) MySQL 5.7 時(shí),大概可以支撐 500 的 TPS 和 10000 的 QPS。這時(shí),運(yùn)營(yíng)負(fù)責(zé) 人說(shuō)正在準(zhǔn)備雙十一活動(dòng),并且公司層面會(huì)繼續(xù)投入資金在全渠道進(jìn)行推廣,這無(wú)疑會(huì)引發(fā)查詢(xún)量驟然增加的問(wèn)題。那么今天,我們就一起來(lái)看看當(dāng)查詢(xún)請(qǐng)求增加時(shí),應(yīng)該如何做主從 分離來(lái)解決問(wèn)題。
主從讀寫(xiě)分離
其實(shí),大部分系統(tǒng)的訪問(wèn)模型是讀多寫(xiě)少,讀寫(xiě)請(qǐng)求量的差距可能達(dá)到幾個(gè)數(shù)量級(jí)。 這很好理解,刷朋友圈的請(qǐng)求量肯定比發(fā)朋友圈的量大,淘寶上一個(gè)商品的瀏覽量也肯定遠(yuǎn) 大于它的下單量。因此,我們優(yōu)先考慮數(shù)據(jù)庫(kù)如何抗住更高的查詢(xún)請(qǐng)求,那么首先你需要把讀寫(xiě)流量區(qū)分開(kāi),因?yàn)檫@樣才方便針對(duì)讀流量做單獨(dú)的擴(kuò)展,這就是我們所說(shuō)的主從讀寫(xiě)分離。
它其實(shí)是個(gè)流量分離的問(wèn)題,就好比道路交通管制一樣,一個(gè)四車(chē)道的大馬路劃出三個(gè)車(chē)道 給領(lǐng)導(dǎo)外賓通過(guò),另外一個(gè)車(chē)道給我們使用,優(yōu)先保證領(lǐng)導(dǎo)先行,就是這個(gè)道理。 這個(gè)方法本身是一種常規(guī)的做法,即使在一個(gè)大的項(xiàng)目中,它也是一個(gè)應(yīng)對(duì)數(shù)據(jù)庫(kù)突發(fā)讀流量的有效方法。
我目前的項(xiàng)目中就曾出現(xiàn)過(guò)前端流量突增導(dǎo)致從庫(kù)負(fù)載過(guò)高的問(wèn)題,DBA 兄弟會(huì)優(yōu)先做一 個(gè)從庫(kù)擴(kuò)容上去,這樣對(duì)數(shù)據(jù)庫(kù)的讀流量就會(huì)落入到多個(gè)從庫(kù)上,從庫(kù)的負(fù)載就降了下來(lái), 然后研發(fā)同學(xué)再考慮使用什么樣的方案將流量擋在數(shù)據(jù)庫(kù)層之上。
主從讀寫(xiě)的兩個(gè)技術(shù)關(guān)鍵點(diǎn)
一般來(lái)說(shuō)在主從讀寫(xiě)分離機(jī)制中,我們將一個(gè)數(shù)據(jù)庫(kù)的數(shù)據(jù)拷貝為一份或者多份,并且寫(xiě)入 到其它的數(shù)據(jù)庫(kù)服務(wù)器中,原始的數(shù)據(jù)庫(kù)我們稱(chēng)為主庫(kù),主要負(fù)責(zé)數(shù)據(jù)的寫(xiě)入,拷貝的目標(biāo) 數(shù)據(jù)庫(kù)稱(chēng)為從庫(kù),主要負(fù)責(zé)支持?jǐn)?shù)據(jù)查詢(xún)。可以看到,主從讀寫(xiě)分離有兩個(gè)技術(shù)上的關(guān)鍵點(diǎn):
接下來(lái),我們分別來(lái)看一看。
1.?主從復(fù)制
我先以 MySQL 為例介紹一下主從復(fù)制。
MySQL 的主從復(fù)制是依賴(lài)于 binlog 的,也就是記錄 MySQL 上的所有變化并以二進(jìn)制形式保存在磁盤(pán)上二進(jìn)制日志文件。主從復(fù)制就是將 binlog 中的數(shù)據(jù)從主庫(kù)傳輸?shù)綇膸?kù)上, 一般這個(gè)過(guò)程是異步的,即主庫(kù)上的操作不會(huì)等待 binlog 同步的完成。主從復(fù)制的過(guò)程是這樣的:首先從庫(kù)在連接到主節(jié)點(diǎn)時(shí)會(huì)創(chuàng)建一個(gè) IO 線程,用以請(qǐng)求主庫(kù)更新的 binlog,并且把接收到的 binlog 信息寫(xiě)入一個(gè)叫做 relay log 的日志文件中,而主庫(kù)也會(huì)創(chuàng)建一個(gè) log dump 線程來(lái)發(fā)送 binlog 給從庫(kù);同時(shí),從庫(kù)還會(huì)創(chuàng)建一個(gè) SQL 線程讀取 relay log 中的內(nèi)容,并且在從庫(kù)中做回放,最終實(shí)現(xiàn)主從的一致性。這是一種比較常見(jiàn)的主從復(fù)制方式。在這個(gè)方案中,使用獨(dú)立的 log dump 線程是一種異步的方式,可以避免對(duì)主庫(kù)的主體更新流程產(chǎn)生影響,而從庫(kù)在接收到信息后并不是寫(xiě)入從庫(kù)的存儲(chǔ)中,是寫(xiě)入一個(gè) relay log,是避免寫(xiě)入從庫(kù)實(shí)際存儲(chǔ)會(huì)比較耗時(shí),最終造成從庫(kù)和主庫(kù)延遲變長(zhǎng)。
你會(huì)發(fā)現(xiàn),基于性能的考慮,主庫(kù)的寫(xiě)入流程并沒(méi)有等待主從同步完成就會(huì)返回結(jié)果,那么在極端的情況下,比如說(shuō)主庫(kù)上 binlog 還沒(méi)有來(lái)得及刷新到磁盤(pán)上就出現(xiàn)了磁盤(pán)損壞或者機(jī)器掉電,就會(huì)導(dǎo)致 binlog 的丟失,最終造成主從數(shù)據(jù)的不一致。不過(guò),這種情況出現(xiàn)的概率很低,對(duì)于互聯(lián)網(wǎng)的項(xiàng)目來(lái)說(shuō)是可以容忍的。
做了主從復(fù)制之后,我們就可以在寫(xiě)入時(shí)只寫(xiě)主庫(kù),在讀數(shù)據(jù)時(shí)只讀從庫(kù),這樣即使寫(xiě)請(qǐng)求會(huì)鎖表或者鎖記錄,也不會(huì)影響到讀請(qǐng)求的執(zhí)行。同時(shí)呢,在讀流量比較大的情況下,我們可以部署多個(gè)從庫(kù)共同承擔(dān)讀流量,這就是所說(shuō)的“一主多從”部署方式,在你的垂直電商項(xiàng)目中就可以通過(guò)這種方式來(lái)抵御較高的并發(fā)讀流量。另外,從庫(kù)也可以當(dāng)成一個(gè)備庫(kù)來(lái)使用,以避免主庫(kù)故障導(dǎo)致數(shù)據(jù)丟失。
那么你可能會(huì)說(shuō),是不是我無(wú)限制地增加從庫(kù)的數(shù)量就可以抵抗大量的并發(fā)呢?實(shí)際上并不是的。因?yàn)殡S著從庫(kù)數(shù)量增加,從庫(kù)連接上來(lái)的 IO 線程比較多,主庫(kù)也需要?jiǎng)?chuàng)建同樣多的 log dump 線程來(lái)處理復(fù)制的請(qǐng)求,對(duì)于主庫(kù)資源消耗比較高,同時(shí)受限于主庫(kù)的網(wǎng)絡(luò)帶寬,所以在實(shí)際使用中,一般一個(gè)主庫(kù)最多掛 3~5 個(gè)從庫(kù)。
當(dāng)然,主從復(fù)制也有一些缺陷,除了帶來(lái)了部署上的復(fù)雜度,還有就是會(huì)帶來(lái)一定的主從同步的延遲,這種延遲有時(shí)候會(huì)對(duì)業(yè)務(wù)產(chǎn)生一定的影響,我舉個(gè)例子你就明白了。
在發(fā)微博的過(guò)程中會(huì)有些同步的操作,像是更新數(shù)據(jù)庫(kù)的操作,也有一些異步的操作,比如 說(shuō)將微博的信息同步給審核系統(tǒng),所以我們?cè)诟峦曛鲙?kù)之后,會(huì)將微博的 ID 寫(xiě)入消息隊(duì)列,再由隊(duì)列處理機(jī)依據(jù)ID 在從庫(kù)中獲取微博信息再發(fā)送給審核系統(tǒng)。此時(shí)如果主從數(shù)據(jù)庫(kù)存在延遲,會(huì)導(dǎo)致在從庫(kù)中獲取不到微博信息,整個(gè)流程會(huì)出現(xiàn)異常。
這個(gè)問(wèn)題解決的思路有很多,核心思想就是盡量不去從庫(kù)中查詢(xún)信息,純粹以上面的例子來(lái) 說(shuō),我就有三種解決方案:
- 第一種方案是數(shù)據(jù)的冗余。你可以在發(fā)送消息隊(duì)列時(shí)不僅僅發(fā)送微博 ID,而是發(fā)送隊(duì)列處理機(jī)需要的所有微博信息,借此避免從數(shù)據(jù)庫(kù)中重新查詢(xún)數(shù)據(jù)。
- 第二種方案是使用緩存。我可以在同步寫(xiě)數(shù)據(jù)庫(kù)的同時(shí),也把微博的數(shù)據(jù)寫(xiě)入到 Memcached 緩存里面,這樣隊(duì)列處理機(jī)在獲取微博信息的時(shí)候會(huì)優(yōu)先查詢(xún)緩存,這樣也可以保證數(shù)據(jù)的一致性。
- 最后一種方案是查詢(xún)主庫(kù)。我可以在隊(duì)列處理機(jī)中不查詢(xún)從庫(kù)而改為查詢(xún)主庫(kù)。不過(guò),這種方式使用起來(lái)要慎重,要明確查詢(xún)的量級(jí)不會(huì)很大,是在主庫(kù)的可承受范圍之內(nèi),否則會(huì)對(duì) 主庫(kù)造成比較大的壓力。
我會(huì)優(yōu)先考慮第一種方案,因?yàn)檫@種方式足夠簡(jiǎn)單,不過(guò)可能造成單條消息比較大,從而增加了消息發(fā)送的帶寬和時(shí)間。
緩存的方案比較適合新增數(shù)據(jù)的場(chǎng)景,在更新數(shù)據(jù)的場(chǎng)景下,先更新緩存可能會(huì)造成數(shù)據(jù)的不一致,比方說(shuō)兩個(gè)線程同時(shí)更新數(shù)據(jù),線程 A 把緩存中的數(shù)據(jù)更新為 1,此時(shí)另一個(gè)線程 B 把緩存中的數(shù)據(jù)更新為 2,然后線程 B 又更新數(shù)據(jù)庫(kù)中的數(shù)據(jù)為 2,此時(shí)線程 A 更新 數(shù)據(jù)庫(kù)中的數(shù)據(jù)為 1,這樣數(shù)據(jù)庫(kù)中的值(1)和緩存中的值(2)就不一致了。
最后,若非萬(wàn)不得已的情況下,我不會(huì)使用第三種方案。原因是這種方案要提供一個(gè)查詢(xún)主 庫(kù)的接口,在團(tuán)隊(duì)開(kāi)發(fā)的過(guò)程中,你很難保證其他同學(xué)不會(huì)濫用這個(gè)方法,而一旦主庫(kù)承擔(dān) 了大量的讀請(qǐng)求導(dǎo)致崩潰,那么對(duì)于整體系統(tǒng)的影響是極大的。
所以對(duì)這三種方案來(lái)說(shuō),你要有所取舍,根據(jù)實(shí)際項(xiàng)目情況做好選擇。
另外,主從同步的延遲,是我們排查問(wèn)題時(shí)很容易忽略的一個(gè)問(wèn)題。有時(shí)候我們遇到從數(shù)據(jù)庫(kù)中獲取不到信息的詭異問(wèn)題時(shí),會(huì)糾結(jié)于代碼中是否有一些邏輯會(huì)把之前寫(xiě)入的內(nèi)容刪 除,但是你又會(huì)發(fā)現(xiàn),過(guò)了一段時(shí)間再去查詢(xún)時(shí)又可以讀到數(shù)據(jù)了,這基本上就是主從延遲 在作怪。所以,一般我們會(huì)把從庫(kù)落后的時(shí)間作為一個(gè)重點(diǎn)的數(shù)據(jù)庫(kù)指標(biāo)做監(jiān)控和報(bào)警,正 常的時(shí)間是在毫秒級(jí)別,一旦落后的時(shí)間達(dá)到了秒級(jí)別就需要告警了。
2.?如何訪問(wèn)數(shù)據(jù)庫(kù)
我們已經(jīng)使用主從復(fù)制的技術(shù)將數(shù)據(jù)復(fù)制到了多個(gè)節(jié)點(diǎn),也實(shí)現(xiàn)了數(shù)據(jù)庫(kù)讀寫(xiě)的分離,這時(shí),對(duì)于數(shù)據(jù)庫(kù)的使用方式發(fā)生了變化。以前只需要使用一個(gè)數(shù)據(jù)庫(kù)地址就好了,現(xiàn)在需要使用一個(gè)主庫(kù)地址和多個(gè)從庫(kù)地址,并且需要區(qū)分寫(xiě)入操作和查詢(xún)操作,如果結(jié)合下一節(jié)課 中要講解的內(nèi)容“分庫(kù)分表”,復(fù)雜度會(huì)提升更多。為了降低實(shí)現(xiàn)的復(fù)雜度,業(yè)界涌現(xiàn)了很多數(shù)據(jù)庫(kù)中間件來(lái)解決數(shù)據(jù)庫(kù)的訪問(wèn)問(wèn)題,這些中間件可以分為兩類(lèi)。
第一類(lèi)以淘寶的 TDDL( Taobao Distributed Data Layer)為代表,以代碼形式內(nèi)嵌運(yùn)行在應(yīng)用程序內(nèi)部。你可以把它看成是一種數(shù)據(jù)源的代理,它的配置管理著多個(gè)數(shù)據(jù)源,每個(gè)數(shù)據(jù)源對(duì)應(yīng)一個(gè)數(shù)據(jù)庫(kù),可能是主庫(kù),可能是從庫(kù)。當(dāng)有一個(gè)數(shù)據(jù)庫(kù)請(qǐng)求時(shí),中間件將 SQL 語(yǔ)句發(fā)給某一個(gè)指定的數(shù)據(jù)源來(lái)處理,然后將處理結(jié)果返回。這一類(lèi)中間件的優(yōu)點(diǎn)是簡(jiǎn)單易用,沒(méi)有多余的部署成本,因?yàn)樗侵踩氲綉?yīng)用程序內(nèi)部,與應(yīng)用程序一同運(yùn)行的,所以比較適合運(yùn)維能力較弱的小團(tuán)隊(duì)使用;缺點(diǎn)是缺乏多語(yǔ)言的支持,目前業(yè)界這一類(lèi)的主流方案除了 TDDL,還有早期的網(wǎng)易 DDB,它們都是 Java 語(yǔ)言開(kāi) 發(fā)的,無(wú)法支持其他的語(yǔ)言。另外,版本升級(jí)也依賴(lài)使用方更新,比較困難。
另一類(lèi)是單獨(dú)部署的代理層方案,這一類(lèi)方案代表比較多,如早期阿里巴巴開(kāi)源的 Cobar,基于 Cobar 開(kāi)發(fā)出來(lái)的 Mycat,360 開(kāi)源的 Atlas,美團(tuán)開(kāi)源的基于 Atlas 開(kāi)發(fā) 的 DBProxy 等等。這一類(lèi)中間件部署在獨(dú)立的服務(wù)器上,業(yè)務(wù)代碼如同在使用單一數(shù)據(jù)庫(kù)一樣使用它,實(shí)際上它內(nèi)部管理著很多的數(shù)據(jù)源,當(dāng)有數(shù)據(jù)庫(kù)請(qǐng)求時(shí),它會(huì)對(duì) SQL 語(yǔ)句做必要的改寫(xiě),然后發(fā) 往指定的數(shù)據(jù)源。它一般使用標(biāo)準(zhǔn)的 MySQL 通信協(xié)議,所以可以很好地支持多語(yǔ)言。由于它是獨(dú)立部署 的,所以也比較方便進(jìn)行維護(hù)升級(jí),比較適合有一定運(yùn)維能力的大中型團(tuán)隊(duì)使用。它的缺陷是所有的 SQL 語(yǔ)句都需要跨兩次網(wǎng)絡(luò):從應(yīng)用到代理層和從代理層到數(shù)據(jù)源,所以在性能上會(huì)有一些損耗。
這些中間件,對(duì)你而言,可能并不陌生,但是我想讓你注意到是,在使用任何中間件的時(shí)候一定要保證對(duì)于中間件有足夠深入的了解,否則一旦出了問(wèn)題沒(méi)法快速地解決就悲劇了。我之前的一個(gè)項(xiàng)目中,一直使用自研的一個(gè)組件來(lái)實(shí)現(xiàn)分庫(kù)分表,后來(lái)發(fā)現(xiàn)這套組件有一定幾率會(huì)產(chǎn)生對(duì)數(shù)據(jù)庫(kù)多余的連接,于是團(tuán)隊(duì)討論后決定替換成 Sharding-JDBC。原本以為是一次簡(jiǎn)單的組件切換,結(jié)果上線后發(fā)現(xiàn)兩個(gè)問(wèn)題:一是因?yàn)槭褂米藙?shì)不對(duì),會(huì)偶發(fā)地出現(xiàn)分庫(kù)分表不生效導(dǎo)致掃描所有庫(kù)表的情況,二是偶發(fā)地出現(xiàn)查詢(xún)延時(shí)達(dá)到秒級(jí)別。由于缺少對(duì)于 Sharding-JDBC 足夠的了解,這兩個(gè)問(wèn)題我們都沒(méi)有很快解決,后來(lái)不得已只能切回原來(lái)的組件,在找到問(wèn)題之后再進(jìn)行切換。
課程小結(jié)
本節(jié)課,我?guī)懔私饬瞬樵?xún)量增加時(shí),我們?nèi)绾瓮ㄟ^(guò)主從分離和一主多從部署抵抗增加的數(shù)據(jù)庫(kù)流量的,你除了掌握主從復(fù)制的技術(shù)之外,還需要了解主從分離會(huì)帶來(lái)什么問(wèn)題以及它 們的解決辦法。這里我想讓你明確的要點(diǎn)主要有:
其實(shí),我們可以把主從復(fù)制引申為存儲(chǔ)節(jié)點(diǎn)之間互相復(fù)制存儲(chǔ)數(shù)據(jù)的技術(shù),它可以實(shí)現(xiàn)數(shù)據(jù)的冗余,以達(dá)到備份和提升橫向擴(kuò)展能力的作用。在使用主從復(fù)制這個(gè)技術(shù)點(diǎn)時(shí),你一般會(huì) 考慮兩個(gè)問(wèn)題:
我們采用的很多組件都會(huì)使用到這個(gè)技術(shù),比如,Redis 也是通過(guò)主從復(fù)制實(shí)現(xiàn)讀寫(xiě)分離; Elasticsearch 中存儲(chǔ)的索引分片也可以被復(fù)制到多個(gè)節(jié)點(diǎn)中;寫(xiě)入到 HDFS 中文件也會(huì)被 復(fù)制到多個(gè) DataNode 中。只是不同的組件對(duì)于復(fù)制的一致性、延遲要求不同,采用的方案也不同。但是這種設(shè)計(jì)的思想是通用的,是你需要了解的,這樣你在學(xué)習(xí)其他存儲(chǔ)組件的 時(shí)候就能夠觸類(lèi)旁通了。
總結(jié)
以上是生活随笔為你收集整理的数据库性能优化—主从分离的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 数据库性能优化—数据库连接池
- 下一篇: Redis—主从复制