PostgreSQL復(fù)制特性歷史漫談
就復(fù)制功能來說,從遠(yuǎn)不能勝任,到功能完備種種包含在內(nèi),PG雖然腳步略遲,但很快地走完了這些路徑,的確當(dāng)?shù)闷鹨粋€“功能***大的開源數(shù)據(jù)庫”的稱呼。
原本我準(zhǔn)備的下一個話題,是PostgreSQL的Redo的討論,但就PG的實(shí)現(xiàn)看,對運(yùn)維來說,redo的機(jī)制很少需要特別關(guān)注,所以就把redo話題下的復(fù)制主題,單獨(dú)拉出來整理了一下,其中***部分,就是PG的復(fù)制這個特性的歷史來由。
復(fù)制,曾經(jīng)是PGer心中永遠(yuǎn)揮之不去的傷口,在PG 9.0之前的版本,如果想要做一個PG的數(shù)據(jù)庫主從,只能人工(PG內(nèi)置了歸檔文件的shell調(diào)度操作)不斷地復(fù)制wal日志(并且只能復(fù)制到當(dāng)前在寫的wal的前一個WAL日志)到從庫(姑且這么叫它),這個從庫需要設(shè)置為恢復(fù)模式,不可對外提供服務(wù)。
從我個人的看法而言,PG在功能性上,的確是強(qiáng)于MySQL的。但是為什么在互聯(lián)網(wǎng)業(yè)務(wù)早期不被看好并選用,主要原因之一,就是其無法進(jìn)行讀能力的擴(kuò)展,而在互聯(lián)網(wǎng)業(yè)務(wù)之外,對事務(wù),數(shù)據(jù)安全講究的人來說,PG對比Oracle,其差距也是肉眼可見的。
MySQL還是一個跑在文件的SQL執(zhí)行器(其衍生出來的MyISAM真的最多只能說是一個帶B樹的文件訪問器)的時候,就已經(jīng)做出來復(fù)制這個關(guān)鍵特性,并用這個機(jī)制,終于等到InnoDB的引入,成為了一個“真正的數(shù)據(jù)庫”。對于早年的互聯(lián)網(wǎng)業(yè)務(wù),MySQL這種快速擴(kuò)容從庫,擴(kuò)展讀能力的機(jī)制,對互聯(lián)網(wǎng)業(yè)務(wù)這種先天讀遠(yuǎn)多于寫的形態(tài),簡直是天作之合。
而PG,在那個時代而言,的確是比較被動的,但終于隨著時間推移,逐步實(shí)現(xiàn)了主從復(fù)制的種種特性。
9.0的異步redo復(fù)制實(shí)現(xiàn)
時間來到2010年(***波互聯(lián)網(wǎng)大潮早已過去,第二波互聯(lián)網(wǎng)大潮也將要過去,大創(chuàng)業(yè)時代即將來臨),PG發(fā)布了版本9.0,其中最重要的特性之一,就是流復(fù)制機(jī)制的實(shí)現(xiàn),解決了兩個問題:一個是通過網(wǎng)絡(luò)連接,從庫直接去拉redo日志(redo復(fù)制,這個也是MySQLer心中痛點(diǎn)啊),而非從主庫走操作系統(tǒng)命令復(fù)制過來,避免了對這個復(fù)制機(jī)制本身維護(hù)的復(fù)雜性;第二個,就是改造出來Hot Standby機(jī)制,也就是在從庫應(yīng)用redo的同時,也允許從庫提供只讀的select服務(wù)。
這里實(shí)現(xiàn)的復(fù)制,當(dāng)然還只是異步復(fù)制,而且機(jī)制我認(rèn)為很怪異:首先,把從主庫走操作系統(tǒng)命令復(fù)制wal日志(不包含***的正在被寫入的wal文件)全部執(zhí)行recovery之后,再發(fā)起一個網(wǎng)絡(luò)連接到主庫讀***的wal記錄,主庫的連接信息從recovery.conf文件中讀取(MySQL是change master命令之后,直接存在masterinfo文件里面,或者每次start slave的時候,手工指定)。
而如果從庫想要中斷主從復(fù)制,不是執(zhí)行一個stop slave(MySQL)命令,而是在操作系統(tǒng)設(shè)置一個trigger文件。
9.1的(半)同步復(fù)制實(shí)現(xiàn)
2011年,pg 9.1發(fā)布,其中引入的新復(fù)制機(jī)制,就是同步復(fù)制這個大殺器了。
在MySQL中,有個被稱為“半同步復(fù)制”的機(jī)制,就是說當(dāng)主庫收到客戶端的commit發(fā)出來之后,直到從庫接受完成這個事務(wù)所有的event,并且確認(rèn)flush到relay日志之后,這個commit才會返回給客戶端,客戶端收到commit的時候,就代表即便主庫宕機(jī),數(shù)據(jù)也必然已經(jīng)存在于從庫,也就是“沒有數(shù)據(jù)丟失”。
簡單概括來說,PG在9.1中實(shí)現(xiàn)的同步復(fù)制,也是這么一回事,把關(guān)鍵字event替換成redo,relay日志替換為從庫wal日志就可以。
注:下圖僅為相關(guān)流程的簡化圖,僅保留了與復(fù)制相關(guān)的邏輯,對于WAL子系統(tǒng)沒有詳細(xì)展開,后續(xù)其他圖片一樣,都是簡化圖.
但實(shí)現(xiàn)的細(xì)節(jié)上,還是有很多區(qū)別的(MySQL的討論基礎(chǔ)版本一律為MySQL 8.0——好吧,我知道這個有些不公平)。
比如允許在回話或者單個事務(wù)級別控制,區(qū)分“重要”事務(wù)與“不重要”的事務(wù),MySQL中,半同步會在遇到從庫響應(yīng)超時的時候,進(jìn)行自動的全庫降級(rpl_semi_sync_master_wait_no_slave控制)。
比如其設(shè)置哪些從庫是半同步復(fù)制的時候,是通過逗號切割的方式指定一個或者多個(但只支持其中的某一個作為同步從庫,首先選取***個作為同步節(jié)點(diǎn),如果***個出問題,則選取第二個,以此類推),而MySQL的半同步復(fù)制,則是保障“只要有rpl_semi_sync_master_wait_for_slave_count個從庫接收到”這個邊界點(diǎn)。
比如由于rollback實(shí)現(xiàn)機(jī)制問題,PG的rollback不會受到同步復(fù)制阻塞,而MySQL在特定情況(可以參考https://my.oschina.net/llzx373/blog/282768 這篇文中,討論的在rollback情況下,也會產(chǎn)生binlog的討論)下,即便是rollback,也需要等待半同步響應(yīng)。
比如事務(wù)可見性上,PG是直接通過事務(wù)id控制,而MySQL在5.7引入rpl_semi_sync_master_wait_point來處理(MySQL 半同步復(fù)制在5.7之前,主庫的其他事務(wù)可以看到在等待半同步復(fù)制返回的事務(wù)的數(shù)據(jù),即便這個事務(wù)尚未對客戶端返回commit)
順帶一提,pg_basebackup這個命令也是這個版本中引入,主要用來搞從庫和數(shù)據(jù)庫備份。
PG的從庫也可以通過報告查詢所需要的最老事務(wù)點(diǎn)給主庫的方式,避免主庫的數(shù)據(jù)清理(vacuum)清理掉從庫查詢所需要的數(shù)據(jù),當(dāng)然,從庫的長時間查詢也會導(dǎo)致主庫文件放大,具體使用決策上,值得考量。
而在控制復(fù)制啟停的方式上,也提供了sql函數(shù)調(diào)用,而非只是純粹依賴觸發(fā)文件。(pg_wal_replay_pause(),pg_wal_replay_resume())
9.2的級聯(lián)復(fù)制
級聯(lián)復(fù)制,也就是A->B->C這種形態(tài)的復(fù)制,其***的意義,是降低主庫的復(fù)制負(fù)載壓力。
復(fù)制負(fù)載這個問題的來源,是類似互聯(lián)網(wǎng)業(yè)務(wù)中,寫少讀多,一個主庫,可能要承擔(dān)幾十個從庫(記得有次春節(jié)時候,我們給一個主庫擴(kuò)出幾十個從庫)的日志發(fā)送行為,無論從磁盤壓力還是網(wǎng)絡(luò)帶寬來說,級聯(lián)復(fù)制都是有必要引入的。
當(dāng)然,級聯(lián)復(fù)制的C,就必然是異步復(fù)制了。
pg_basebackup也可以在從庫執(zhí)行備份了,可以避開備份對主庫的性能影響。
而在同步復(fù)制上,新增了一個remote_write 級別,意思是,只要從庫接收到wal日志就可以,不需要保障必須flush到磁盤的情況下,就可以確認(rèn)commit成功了。
9.3的性能與易用性
這個版本沒有本質(zhì)性的特性變更,但在易用性和性能上,做了相當(dāng)大的改進(jìn)。
比如當(dāng)多個從庫中的某一個從庫被提升為主庫之后,其他從庫可以直接切換過去,而在之前,必須重新同步。
比如pg_basebackup可以直接生成一個recovery.conf文件。
9.4邏輯日志導(dǎo)出與延遲備份
這個版本開始,xlog(wal日志)支持邏輯解析方式的導(dǎo)出,用于邏輯復(fù)制,或者跨數(shù)據(jù)庫類型復(fù)制這種操作,甚至邏輯復(fù)制連同步復(fù)制都可以支持,唯一的限制,是邏輯復(fù)制只能作用于單庫而非全局。
而replication slot的概念,也是這個版本開始引入。
之前提到過,為了避免主庫清理掉從庫尚需使用的數(shù)據(jù),從庫需要給主庫報告所需要的事務(wù)點(diǎn)信息,在9.4開始,這個機(jī)制被單獨(dú)提出來,稱為replication slot,其主要的作用,是為物理復(fù)制,以及邏輯復(fù)制,提供維持事務(wù)點(diǎn)信息的視圖,避免從庫連接斷開等原因?qū)е碌臄?shù)據(jù)清理。
而在復(fù)制應(yīng)用上,這個版本開始,PG增加了延遲復(fù)制這個特性,對于誤刪除操作等諸多問題,這個特性可以讓數(shù)據(jù)恢復(fù)時間盡可能地縮短了。
9.5性能與易用性
這個版本沒有大的變更,主要是以下一些內(nèi)容:
允許WAL日志以壓縮形態(tài)傳輸?shù)綇膸欤灾鲝膸霤PU換取較低的網(wǎng)絡(luò)消耗。
recovery.conf的主庫連接信息,可以以URI(postgres://)的形式來寫。
新增了wal_retrieve_retry_interval 參數(shù)來控制從庫失敗后的重試。
9.6多個同步復(fù)制從庫以及真-同步復(fù)制
說個題外話,PG在9.6開始,支持了并行查詢,并在隨后的版本中做到了很大的增強(qiáng),這點(diǎn)也是我認(rèn)為PG對比MySQL上,有絕對優(yōu)勢的一個特性。而PG的主要槽點(diǎn)之一vacuum凍結(jié),也是在這個版本引入不再重復(fù)處理已經(jīng)完全凍結(jié)的數(shù)據(jù)塊這個重要特性的。
前文中提到,MySQL有個參數(shù)rpl_semi_sync_master_wait_for_slave_count控制同步的從庫數(shù)量,而PG則是從設(shè)定的列表中選取某一個,在9.6開始,這個設(shè)計被變更為,可以等到wal被確認(rèn)寫入多個從庫之后,再返回commit。
synchronous_standby_names參數(shù)也不僅僅是逗號切割的列表,變成了 n(s1,s2,s3)這種形態(tài),讓前n個數(shù)據(jù)庫達(dá)到wal條件后確認(rèn)commit。
在前面同步復(fù)制的討論中,提到PG的同步復(fù)制,與MySQL半同步復(fù)制實(shí)現(xiàn)機(jī)理基本類似,但在9.6開始,新增了remote_apply 這個同步點(diǎn),也就是,直到從庫應(yīng)用了對應(yīng)的wal日志,主庫才能返回commit成功,可以做到主庫提交從庫立即可見的效果。我相信不止我一個人被開發(fā)問到,說我主庫寫入的語句,到從庫去查,為啥查不著的問題,而在當(dāng)代的大型項目中,可能上層應(yīng)用直接調(diào)用讀寫的假設(shè),就是寫入立即可讀,下層如果貿(mào)然采用了傳統(tǒng)的讀寫分離手段,可能就會導(dǎo)致上層應(yīng)用無法馬上看到數(shù)據(jù)的問題了,這個問題再摻和上主從延遲的種種糾結(jié),是我在傳統(tǒng)行業(yè)客戶中,遇到最多的問題之一。
10 發(fā)布訂閱式邏輯復(fù)制
邏輯復(fù)制在這個版本得到了極大的增強(qiáng)。
首先,是支持了邏輯復(fù)制這個特性本身。雖然9.4開始,xlog就已經(jīng)可以解析并提供給邏輯復(fù)制使用,但PG在當(dāng)時,并沒有內(nèi)置邏輯復(fù)制的相關(guān)組件,在10版本開始,支持了到表級別的邏輯復(fù)制,并且支持跨大版本,跨操作系統(tǒng),跨機(jī)器架構(gòu)之間的邏輯復(fù)制,靈活性上遠(yuǎn)勝物理復(fù)制(MySQL的binlog復(fù)制就是如此)。
synchronous_standby_names再次變更了語法,包括first和any兩個語義,first指定的列表的話,會按照順序優(yōu)先級確認(rèn)返回的從庫響應(yīng)達(dá)到指定數(shù)量(比如first 1(s1,s2)就類似舊的實(shí)現(xiàn),選取***個作為同步點(diǎn),如果***個s1失敗了,再選取第二個s2作為同步點(diǎn)),而any語義的話,舉個例子,類似any 2(s1,s2,s3)這種,則是允許三個從庫中,任意兩個從庫只要返回同步成功,就可以確認(rèn)commit了。any來說,更類似MySQL中半同步的確認(rèn)語義。
recovery模式的恢復(fù)目標(biāo)點(diǎn),除了timestamp,事務(wù)id之外,也支持了LSN號,恢復(fù)的時候更加靈活了。
另外估計是由于slot維持wal,導(dǎo)致一些為臨時會話維護(hù)的slot導(dǎo)致wal積累(比如pg_basebackup,每次備份都需要創(chuàng)建slot,備份完成后,slot還需要處理掉),這個版本開始,slot支持僅為當(dāng)前會話提供的臨時slot避免這個問題。
11 邊角修補(bǔ)的復(fù)制特性
11大版本的主要功能變化,是分區(qū)表終于可以用了,而不是必須得采用第三方插件,包括hash分區(qū),分區(qū)表上的主鍵,外鍵,全局索引,觸發(fā)器這些,都終于支持了。
而在復(fù)制特性上,這個版本基本上沒有什么大的變化,都是些邊邊角角的修補(bǔ)。
比方在做備份的時候,增加對數(shù)據(jù)塊的校驗。
比如pg_stat_wal_receiver 視圖,增加了機(jī)器與端口信息。
乏陳可述,但就PG目前在復(fù)制上已經(jīng)做的事情來看,的確也沒有更強(qiáng)的需求來驅(qū)動這方面的進(jìn)一步增強(qiáng)。
結(jié)語以及個人思考
從遠(yuǎn)不能勝任,到功能完備種種包含在內(nèi),PG雖然腳步略遲,但很快地走完了這些路徑,就功能性而言,的確當(dāng)?shù)闷鹨粋€“功能***大的開源數(shù)據(jù)庫”的稱呼。
佛門講修行,其中一道障礙稱為“知見障”,是說對一個東西認(rèn)知越多,看其他東西的時候,成見也就越多,也就越難有一個清晰的認(rèn)知(注:這個是知見障的一種解釋,而且應(yīng)該是佛教禪宗本土化后的頓宗解釋,原始佛教中,這個詞應(yīng)該是類似六根不凈的一個概念,不是我這里要表達(dá)的意思)。
的確,我自己的感覺也好,和其他人聊數(shù)據(jù)庫時候的感覺也罷,每個人都有以自己熟悉的,認(rèn)知的“數(shù)據(jù)庫”來去看其他數(shù)據(jù)庫的習(xí)慣。
以我自己來說。
比如我對pg的vacuum這么糾結(jié),是因為這個問題上,在MySQL,Oracle這倆我熟知的數(shù)據(jù)庫中,都是(相對)很好地解決了這個問題的,而PG非得別別扭扭地不處理——是的,是可以有很多人工策略可以搞,是有很多參數(shù)設(shè)置合適可以避開,但為什么非得我去管?它自個安安靜靜地處理好不就得了?哪怕是***限度的單表上的并行vacuum也可以啊(MySQL 5.6之前單線程purge也是一個大坑,后來就改多線程了)。
比如對于DB2,這個是我學(xué)校學(xué)習(xí)數(shù)據(jù)庫時候的教材數(shù)據(jù)庫,我的諸多知識都是從這個數(shù)據(jù)庫上學(xué)會的,當(dāng)我之后不久去看Oracle的時候,***個反應(yīng)是,隔離級別到哪里去了?——Oracle的隔離級別,除了RC,Serializable之外,其他隔離級別都是通過變通方式實(shí)現(xiàn)的,而DB2,是有很齊全的4個隔離級別的(雖然各個隔離級別名字和SQL標(biāo)準(zhǔn)的名字不是很一致)——后來我給別人講隔離級別課程的時候,都是用DB2講課,而不是拉一個其他數(shù)據(jù)庫出來(MySQL InnoDB在RR處理了幻讀,那Serializable級別和RR的區(qū)別,解釋起來就很費(fèi)工夫,而PG的幾個隔離級中,讀未提交讀不到臟讀,可重復(fù)讀讀不到幻讀就更不用說了),我不知道有多少人拿著Oracle這種講隔離級別,但就我個人的感覺來說,哪怕到現(xiàn)在,依然感覺非常別扭。
如果說要打破成見,莫過于了解下其他的東西,切切實(shí)實(shí)地了解其優(yōu)缺點(diǎn),做到在“應(yīng)該用的地方去用”,在技術(shù)成長上,相信會有更好地進(jìn)步。
另外,由于不同數(shù)據(jù)庫之間,雖然以外部視角看,都是SQL語言控制的數(shù)據(jù)庫,但內(nèi)部實(shí)現(xiàn)的種種細(xì)節(jié)卻都是全然不同的,作為DBA來說,如果不想要讓自己的職業(yè)生命,被迫維系在某一個數(shù)據(jù)庫上的話(DB2前車之鑒,我當(dāng)初差點(diǎn)入走了這條路),那么打開思路,去切實(shí)了解一下其他數(shù)據(jù)庫的種種特點(diǎn),也不失為一個很好的防御措施。
作者:劉偉,云和恩墨軟件開發(fā)部研究院研究員;前微博DBA,主要研究方向為開源數(shù)據(jù)庫,分布式數(shù)據(jù)庫,擅長自動化運(yùn)維以及數(shù)據(jù)庫內(nèi)核研究。
參考
http://mysql.taobao.org/monthly/2015/12/05/
https://www.postgresql.org/docs/11/release.html
https://dev.mysql.com/doc/refman/8.0/en/replication-semisync.html
https://my.oschina.net/llzx373/blog/282768 mysql復(fù)制對事務(wù)的處理