天天看點

使用PostgreSQL邏輯訂閱實作multi-master

postgresql , multi master , 邏輯訂閱

很多業務要求多活,但是多活中最難搞定的實際上是資料庫,大多數業務通過分流,例如将資料根據uid切分到不同的idc,同一個uid的資料永遠隻會寫到一個idc中,然後通過資料複制技術,将對應的資料複制到其他的idc。

這種形态的多活是比較安全的,即同一條記錄不會被多個idc執行dml。如果同一條記錄涉及到多個idc的dml,那麼資料庫的一緻性會受到極大挑戰,比如同一條記錄被多個idc更新,到底以哪個為準?

同時多活還要解決另一個問題,資料的循環問題,例如a寫入一條資料,需要複制到b,b寫入的資料也要複制給a。如果沒有手段防止循環的話,一條記錄就可以把多活資料庫弄趴下。

multi-master的兩個重大課題是解決沖突以及死循環的問題。

postgresql 10引入了邏輯訂閱的功能,可以輕松的實作單向複制,同時為雙向複制(multi-master)提供了天然的支援,本文将介紹如何利用邏輯訂閱實作multi-master。

使用PostgreSQL邏輯訂閱實作multi-master

例如某個表的pk字段為pk, a,b,c三個節點,都更新了同一條pk,到底以誰為準呢?

又或者a,b,c都寫入了同一個pk值的一條記錄,到底以誰的為準呢?

解決沖突的方法很多,例如以某個字段的值來判斷(例如時間字段,以最大或最小為準)。

針對不同的操作,解法不一。

1、insert和update

建議業務層面保證pk不沖突。如果業務層面無法保證不沖突,則可以這樣實作conflict handler:

對需要實作multi-master的表,新增3個字段,分别為:

node id,事務時間、語句時間 。 然後我們就可以實作這些conflict handler方法。

可以通過hook實作,在核心中insert、update階段應用以上規則。

也可以使用觸發器來實作,本文的例子就是使用觸發器來實作的。

2、delete

無沖突

目前pg 10在遇到沖突(比如insert遇到pk異常,或者任何其他異常),會終止wal apply,此時需要人為介入,例如skip對應的wal。

使用本文的沖突handler後,可以規避此問題。否則我們需要這樣來處理沖突:

1. 通過修改訂閱端的資料,解決沖突。例如insert違反了唯一限制時,可以删除訂閱端造成唯一限制沖突的記錄先delete掉。然後使用alter subscription name enable讓訂閱繼續。

如果是雙向複制(multi-master)則不建議使用方法1。

2. 在訂閱端調用pg_replication_origin_advance(node_name text, pos pg_lsn)函數,node_name就是subscription name,pos指重新開始的lsn,進而跳過有沖突的事務。

目前的lsn通過pg_replication_origin_status.remote_lsn檢視。

<a href="https://www.postgresql.org/docs/devel/static/view-pg-replication-origin-status.html">https://www.postgresql.org/docs/devel/static/view-pg-replication-origin-status.html</a>

例如a寫入了一條記錄,産生一筆wal,b通過這筆wal将這條記錄複制到了b,b又會産生一筆wal,這筆wal又會被複制到a,然後a通過這筆wal又會寫入一條記錄,然後又會産生wal,循環往複。

如果不解決死循環的問題,multi-master的某個節點插入一條記錄,這條記錄複制到另一個節點後,還會回流到某節點,無限循環。

解決死循環的問題方法比較簡單,

1、對需要雙向複制的表,添加一個字段(無預設值),表示這條記錄是來自peer節點,還是本地插入、更新、删除的。

為了友善溯源,也可以加一個gtid字段用來分辨記錄是來自哪個節點的。(在觸發器中展現)

2、對需要雙向複制的表,添加一個觸發器函數,觸發器中需要以下邏輯:

3個postgresql 10執行個體。分别對應端口

1、建立測試表

測試表的原始字段如下

為了解決沖突和死循環的問題,我們添加了若幹字段

ps,寫入的沖突,業務層是有辦法規避的,例如

使用序列start val錯開pk

2、建立觸發器函數,解決死循環、沖突問題

為了實作不同的conflict handler,我們需要給觸發器函數輸入參數,設計如下

ps,使用pg_stat_activity可以得到logical replication worker的pids.

3、建立觸發器

可以這樣來建立,分别針對不同的conflict handler

以last_xacttime,last,優先2号節點為例

以last_xacttime,first,優先2号節點為例

4、讓觸發器在所有連接配接中,包括replica程序中都生效

5、建立釋出

前面的操作需要在所有執行個體執行。

6、建立訂閱,不同的執行個體操作分别如下

7、壓測方法

為了避免插入沖突導緻複制中斷,使用以下測試方法,3個執行個體插入的資料確定pk值是不一樣的,(實際生産,可以使用序列的start value來錯開)。

更新、删除則覆寫所有的值範圍。

8、三個節點同時壓測

9、驗證

10、測試删除

11、清理戰場

1、ddl無法被複制,建議在多個節點的資料一緻後,鎖定被複制的表,檢查确認資料一緻,再執行ddl。

2、insert conflict handler無法使用觸發器支援,需要使用hook實作,需增加插件。

3、如果有多個觸發器,那麼本例用到的觸發器必須放在最前面。before觸發器,觸發器的取名順序放在最前即可。

觸發器的詳解

<a href="https://github.com/digoal/blog/blob/master/201303/20130311_01.md">《postgresql 觸發器 用法詳解 1》</a>

<a href="https://github.com/digoal/blog/blob/master/201303/20130311_02.md">《postgresql 觸發器 用法詳解 2》</a>

multi-master的要點,避免沖突(導緻流複制中斷,需要介入),避免死循環(節點間不停的産生redo,循環執行)。

本文使用巧妙的方法解決了這兩個問題,實作了任意節點的multi-master。

<a href="https://github.com/digoal/blog/blob/master/201704/20170413_01.md">《postgresql 邏輯訂閱 - 給業務架構帶來了什麼希望?》</a>

<a href="https://github.com/digoal/blog/blob/master/201702/20170227_01.md">《postgresql 10.0 preview 邏輯複制 - 原理與最佳實踐》</a>

<a href="https://github.com/digoal/blog/blob/master/201704/20170421_05.md">《postgresql 10.0 preview 功能增強 - 邏輯訂閱端worker數控制參數》</a>

<a href="https://github.com/digoal/blog/blob/master/201704/20170405_02.md">《postgresql 10.0 preview 變化 - 邏輯複制pg_hba.conf變化,不再使用replication條目》</a>

<a href="https://github.com/digoal/blog/blob/master/201703/20170330_01.md">《postgresql 10.0 preview 功能增強 - 備庫支援邏輯訂閱,訂閱支援主備漂移了》</a>

<a href="https://github.com/digoal/blog/blob/master/201703/20170328_01.md">《postgresql 10.0 preview 功能增強 - 邏輯複制支援并行copy初始化資料》</a>

<a href="https://www.2ndquadrant.com/en/resources/pglogical/">https://www.2ndquadrant.com/en/resources/pglogical/</a>

<a href="https://www.2ndquadrant.com/en/resources/bdr/">https://www.2ndquadrant.com/en/resources/bdr/</a>

<a href="https://www.enterprisedb.com/products-services-training/products-overview/xdb-replication-server-multi-master">https://www.enterprisedb.com/products-services-training/products-overview/xdb-replication-server-multi-master</a>

<a href="https://github.com/postgrespro/postgres_cluster">https://github.com/postgrespro/postgres_cluster</a>

<a href="https://github.com/postgrespro/postgres_cluster/blob/master/contrib/mmts/doc/architecture.md">https://github.com/postgrespro/postgres_cluster/blob/master/contrib/mmts/doc/architecture.md</a>