提語: 就複製功能來說,從遠不能勝任,到功能完備種種包含在內,PG雖然腳步略遲,但很快地走完了這些路徑,的確當得起一個“功能最強大的開源資料庫”的稱呼。
原本我準備的下一個話題,是PostgreSQL的Redo的討論,但就PG的實現看,對運維來說,redo的機制很少需要特別關註,所以就把redo話題下的複製主題,單獨拉出來整理了一下,其中第一部分,就是PG的複製這個特性的歷史來由。
複製,曾經是PGer心中永遠揮之不去的傷口,在PG 9.0之前的版本,如果想要做一個PG的資料庫主從,只能人工(PG內建了歸檔檔案的shell排程操作)不斷地複製wal日誌(並且只能複製到當前在寫的wal的前一個WAL日誌)到從庫(姑且這麼叫它),這個從庫需要設定為恢復樣式,不可對外提供服務。
從我個人的看法而言,PG在功能性上,的確是強於MySQL的。但是為什麼在網際網路業務早期不被看好並選用,主要原因之一,就是其無法進行讀能力的擴充套件,而在網際網路業務之外,對事務,資料安全講究的人來說,PG對比Oracle,其差距也是肉眼可見的。
MySQL還是一個跑在檔案的SQL執行器(其衍生出來的MyISAM真的最多隻能說是一個帶B樹的檔案訪問器)的時候,就已經做出來複制這個關鍵特性,並用這個機制,終於等到InnoDB的引入,成為了一個“真正的資料庫”。對於早年的網際網路業務,MySQL這種快速擴容從庫,擴充套件讀能力的機制,對網際網路業務這種先天讀遠多於寫的形態,簡直是天作之合。
而PG,在那個時代而言,的確是比較被動的,但終於隨著時間推移,逐步實現了主從複製的種種特性。
時間來到2010年(第一波網際網路大潮早已過去,第二波網際網路大潮也將要過去,大創業時代即將來臨),PG釋出了版本9.0,其中最重要的特性之一,就是流複製機制的實現,解決了兩個問題:一個是透過網路連線,從庫直接去拉redo日誌(redo複製,這個也是MySQLer心中痛點啊),而非從主庫走作業系統命令複製過來,避免了對這個複製機制本身維護的複雜性;第二個,就是改造出來Hot Standby機制,也就是在從庫應用redo的同時,也允許從庫提供只讀的select服務。
這裡實現的複製,當然還只是非同步複製,而且機制我認為很怪異:首先,把從主庫走作業系統命令複製wal日誌(不包含最新的正在被寫入的wal檔案)全部執行recovery之後,再發起一個網路連線到主庫讀最新的wal記錄,主庫的連線資訊從recovery.conf檔案中讀取(MySQL是change master命令之後,直接存在masterinfo檔案裡面,或者每次start slave的時候,手工指定)。
而如果從庫想要中斷主從複製,不是執行一個stop slave(MySQL)命令,而是在作業系統設定一個trigger檔案。
2011年,pg 9.1釋出,其中引入的新複製機制,就是同步複製這個大殺器了。
在MySQL中,有個被稱為“半同步複製”的機制,就是說當主庫收到客戶端的commit發出來之後,直到從庫接受完成這個事務所有的event,並且確認flush到relay日誌之後,這個commit才會傳回給客戶端,客戶端收到commit的時候,就代表即便主庫宕機,資料也必然已經存在於從庫,也就是“沒有資料丟失”。
簡單概括來說,PG在9.1中實現的同步複製,也是這麼一回事,把關鍵字event替換成redo,relay日誌替換為從庫wal日誌就可以。
註:下圖僅為相關流程的簡化圖,僅保留了與複製相關的邏輯,對於WAL子系統沒有詳細展開,後續其他圖片一樣,都是簡化圖.
但實現的細節上,還是有很多區別的(MySQL的討論基礎版本一律為MySQL 8.0——好吧,我知道這個有些不公平)。
比如允許在回話或者單個事務級別控制,區分“重要”事務與“不重要”的事務,MySQL中,半同步會在遇到從庫響應超時的時候,進行自動的全庫降級(rpl_semi_sync_master_wait_no_slave控制)。
比如其設定哪些從庫是半同步複製的時候,是透過逗號切割的方式指定一個或者多個(但只支援其中的某一個作為同步從庫,首先選取第一個作為同步節點,如果第一個出問題,則選取第二個,以此類推),而MySQL的半同步複製,則是保障“只要有rpl_semi_sync_master_wait_for_slave_count個從庫接收到”這個邊界點。
比如由於rollback實現機制問題,PG的rollback不會受到同步複製阻塞,而MySQL在特定情況(可以參考https://my.oschina.net/llzx373/blog/282768 這篇文中,討論的在rollback情況下,也會產生binlog的討論)下,即便是rollback,也需要等待半同步響應。
比如事務可見性上,PG是直接透過事務id控制,而MySQL在5.7引入rpl_semi_sync_master_wait_point來處理(MySQL 半同步複製在5.7之前,主庫的其他事務可以看到在等待半同步複製傳回的事務的資料,即便這個事務尚未對客戶端傳回commit)
順帶一提,pg_basebackup這個命令也是這個版本中引入,主要用來搞從庫和資料庫備份。
PG的從庫也可以透過報告查詢所需要的最老事務點給主庫的方式,避免主庫的資料清理(vacuum)清理掉從庫查詢所需要的資料,當然,從庫的長時間查詢也會導致主庫檔案放大,具體使用決策上,值得考量。
而在控制複製啟停的方式上,也提供了sql函式呼叫,而非只是純粹依賴觸發檔案。(pg_wal_replay_pause(),pg_wal_replay_resume())
級聯複製,也就是A->B->C這種形態的複製,其最大的意義,是降低主庫的複製負載壓力。
複製負載這個問題的來源,是類似網際網路業務中,寫少讀多,一個主庫,可能要承擔幾十個從庫(記得有次春節時候,我們給一個主庫擴出幾十個從庫)的日誌傳送行為,無論從磁碟壓力還是網路頻寬來說,級聯複製都是有必要引入的。
當然,級聯複製的C,就必然是非同步複製了。
pg_basebackup也可以在從庫執行備份了,可以避開備份對主庫的效能影響。
而在同步複製上,新增了一個remote_write 級別,意思是,只要從庫接收到wal日誌就可以,不需要保障必須flush到磁碟的情況下,就可以確認commit成功了。
這個版本沒有本質性的特性變更,但在易用性和效能上,做了相當大的改進。
比如當多個從庫中的某一個從庫被提升為主庫之後,其他從庫可以直接切換過去,而在之前,必須重新同步。
比如pg_basebackup可以直接生成一個recovery.conf檔案。
這個版本開始,xlog(wal日誌)支援邏輯解析方式的匯出,用於邏輯複製,或者跨資料庫型別複製這種操作,甚至邏輯複製連同步複製都可以支援,唯一的限制,是邏輯複製只能作用於單庫而非全域性。
而replication slot的概念,也是這個版本開始引入。
之前提到過,為了避免主庫清理掉從庫尚需使用的資料,從庫需要給主庫報告所需要的事務點資訊,在9.4開始,這個機制被單獨提出來,稱為replication slot,其主要的作用,是為物理複製,以及邏輯複製,提供維持事務點資訊的檢視,避免從庫連線斷開等原因導致的資料清理。
而在複製應用上,這個版本開始,PG增加了延遲複製這個特性,對於誤刪除操作等諸多問題,這個特性可以讓資料恢復時間盡可能地縮短了。
這個版本沒有大的變更,主要是以下一些內容:
允許WAL日誌以壓縮形態傳輸到從庫,以主從庫CPU換取較低的網路消耗。
recovery.conf的主庫連線資訊,可以以URI(postgres://)的形式來寫。
新增了wal_retrieve_retry_interval 引數來控制從庫失敗後的重試。
說個題外話,PG在9.6開始,支援了並行查詢,併在隨後的版本中做到了很大的增強,這點也是我認為PG對比MySQL上,有絕對優勢的一個特性。而PG的主要槽點之一vacuum凍結,也是在這個版本引入不再重覆處理已經完全凍結的資料塊這個重要特性的。
前文中提到,MySQL有個引數rpl_semi_sync_master_wait_for_slave_count控制同步的從庫數量,而PG則是從設定的串列中選取某一個,在9.6開始,這個設計被變更為,可以等到wal被確認寫入多個從庫之後,再傳回commit。
synchronous_standby_names引數也不僅僅是逗號切割的串列,變成了 n(s1,s2,s3)這種形態,讓前n個資料庫達到wal條件後確認commit。
在前面同步複製的討論中,提到PG的同步複製,與MySQL半同步複製實現機理基本類似,但在9.6開始,新增了remote_apply 這個同步點,也就是,直到從庫應用了對應的wal日誌,主庫才能傳回commit成功,可以做到主庫提交從庫立即可見的效果。我相信不止我一個人被開發問到,說我主庫寫入的陳述句,到從庫去查,為啥查不著的問題,而在當代的大型專案中,可能上層應用直接呼叫讀寫的假設,就是寫入立即可讀,下層如果貿然採用了傳統的讀寫分離手段,可能就會導致上層應用無法馬上看到資料的問題了,這個問題再摻和上主從延遲的種種糾結,是我在傳統行業客戶中,遇到最多的問題之一。
邏輯複製在這個版本得到了極大的增強。
首先,是支援了邏輯複製這個特性本身。雖然9.4開始,xlog就已經可以解析並提供給邏輯複製使用,但PG在當時,並沒有內建邏輯複製的相關元件,在10版本開始,支援了到表級別的邏輯複製,並且支援跨大版本,跨作業系統,跨機器架構之間的邏輯複製,靈活性上遠勝物理複製(MySQL的binlog複製就是如此)。
synchronous_standby_names再次變更了語法,包括first和any兩個語意,first指定的串列的話,會按照順序優先順序確認傳回的從庫響應達到指定數量(比如first 1(s1,s2)就類似舊的實現,選取第一個作為同步點,如果第一個s1失敗了,再選取第二個s2作為同步點),而any語意的話,舉個例子,類似any 2(s1,s2,s3)這種,則是允許三個從庫中,任意兩個從庫只要傳回同步成功,就可以確認commit了。any來說,更類似MySQL中半同步的確認語意。
recovery樣式的恢復標的點,除了timestamp,事務id之外,也支援了LSN號,恢復的時候更加靈活了。
另外估計是由於slot維持wal,導致一些為臨時會話維護的slot導致wal積累(比如pg_basebackup,每次備份都需要建立slot,備份完成後,slot還需要處理掉),這個版本開始,slot支援僅為當前會話提供的臨時slot避免這個問題。
11大版本的主要功能變化,是分割槽表終於可以用了,而不是必須得採用第三方外掛,包括hash分割槽,分割槽表上的主鍵,外來鍵,全域性索引,觸發器這些,都終於支援了。
而在複製特性上,這個版本基本上沒有什麼大的變化,都是些邊邊角角的修補。
比方在做備份的時候,增加對資料塊的校驗。
比如pg_stat_wal_receiver 檢視,增加了機器與埠資訊。
乏陳可述,但就PG目前在複製上已經做的事情來看,的確也沒有更強的需求來驅動這方面的進一步增強。
從遠不能勝任,到功能完備種種包含在內,PG雖然腳步略遲,但很快地走完了這些路徑,就功能性而言,的確當得起一個“功能最強大的開源資料庫”的稱呼。
佛門講修行,其中一道障礙稱為“知見障”,是說對一個東西認知越多,看其他東西的時候,成見也就越多,也就越難有一個清晰的認知(註:這個是知見障的一種解釋,而且應該是佛教禪宗本土化後的頓宗解釋,原始佛教中,這個詞應該是類似六根不凈的一個概念,不是我這裡要表達的意思)。
的確,我自己的感覺也好,和其他人聊資料庫時候的感覺也罷,每個人都有以自己熟悉的,認知的“資料庫”來去看其他資料庫的習慣。
以我自己來說。
比如我對pg的vacuum這麼糾結,是因為這個問題上,在MySQL,Oracle這倆我熟知的資料庫中,都是(相對)很好地解決了這個問題的,而PG非得別彆扭扭地不處理——是的,是可以有很多人工策略可以搞,是有很多引數設定合適可以避開,但為什麼非得我去管?它自個安安靜靜地處理好不就得了?哪怕是最低限度的單表上的並行vacuum也可以啊(MySQL 5.6之前單執行緒purge也是一個大坑,後來就改多執行緒了)。
比如對於DB2,這個是我學校學習資料庫時候的教材資料庫,我的諸多知識都是從這個資料庫上學會的,當我之後不久去看Oracle的時候,第一個反應是,隔離級別到哪裡去了?——Oracle的隔離級別,除了RC,Serializable之外,其他隔離級別都是透過變通方式實現的,而DB2,是有很齊全的4個隔離級別的(雖然各個隔離級別名字和SQL標準的名字不是很一致)——後來我給別人講隔離級別課程的時候,都是用DB2講課,而不是拉一個其他資料庫出來(MySQL InnoDB在RR處理了幻讀,那Serializable級別和RR的區別,解釋起來就很費工夫,而PG的幾個隔離級中,讀未提交讀不到臟讀,可重覆讀讀不到幻讀就更不用說了),我不知道有多少人拿著Oracle這種講隔離級別,但就我個人的感覺來說,哪怕到現在,依然感覺非常彆扭。
如果說要打破成見,莫過於瞭解下其他的東西,切切實實地瞭解其優缺點,做到在“應該用的地方去用”,在技術成長上,相信會有更好地進步。
另外,由於不同資料庫之間,雖然以外部視角看,都是SQL語言控制的資料庫,但內部實現的種種細節卻都是全然不同的,作為DBA來說,如果不想要讓自己的職業生命,被迫維繫在某一個資料庫上的話(DB2前車之鑒,我當初差點入走了這條路),那麼開啟思路,去切實瞭解一下其他資料庫的種種特點,也不失為一個很好的防禦措施。
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複製對事務的處理