標籤:color tree ott name pos 未來 script 為我 應用
在這一章中,將向您介紹一個全新的技術,成為BDR。雙向複製(BDR),在PostgreSQL的世界裡,它絕對是一顆冉冉升起的新星。在不久的將來,許多新的東西將會被看到,並且人們可以期待一個蓬勃發展的項目。
本章將是關於如下這些主題:
?理解 BDR 複製概念
?安裝 BDR
?設定一個簡單的叢集
?修改叢集和容錯移轉
?瞭解 BDR 的效能
在挖掘所有的技術細節之前,理解 BDR 方面的基本技術是非常重要的。
理解 BDR 複製概念
過去,在9.0被引進之前,人們不得不使用Slony來複製資料。如Slony這樣的解決方案的核心問題是,需要一個更新的觸發器,它實際上將資料寫入兩次。基於觸發器的解決方案管理起來很困難,不能處理DDLs,並且一般操作起來有點棘手。
BDR已經被創造來終結基於觸發器的解決方案,並把PostgreSQL轉變為一個更強壯,更具擴充性,並且方式更簡單的管理解決方案。基於觸發器的複製確實是一件過時的事情,並且不應該在現代的基礎設施中被看到。你可以打賭BDR—它是一個長期的,安全的解決方案。
理解最終一致性
在本書前面的一部分,對CAP理解進行了討論。這是重要的一個組成部分,當一個新的資料庫技術被評價是,它應該時刻被牢記在心。對於BDR,這個系統是具有最終一致性的。這是什麼意思呢?維基百科(http://en.wikipedia.org/wiki/Eventual_consistency)提供了如下定義:
最終一致性是分散式運算使用的一個一致性模型,被用來實現非正式地保證高可用,如果對於給定的資料沒有更新,最終對給定資料的所有訪問將返回最後更新的值。
這個定義其實是如此的好並且簡單,以至於把它放在這裡很有意義。最終一致性的思想是在所有的節點上資料並不會立即相同,但是過一段時間,它實際上會相同的(如果沒有發生什麼事情)。最終一致性也意味著預設情況下,資料將被非同步複製,因此,不是所有的節點什麼時候都看到相同的資料。你必須期待看到略有不同的資料,這取決於您串連到的主機。
[BDR也支援同步複製。然而,它不像經典的兩階段交易認可事務那樣安全。]
處理衝突
考慮到一致性模型,一個重要的話題出現了:衝突了怎麼辦?一般情況下,使用最終一致性的所有系統需要需要某種衝突解決。這用於適用於BDR。
BDR的妙處在於衝突管理是非常有彈性的。預設情況下,BDR提供了一個明確的衝突管理演算法,它定義了最後的更新總是贏。
然而,更多的是,在BDR中,你自己寫衝突解決方案演算法也是可能的。一個伺服器端的預存程序可以用來定義發生衝突是必須做的事情。這個機制給使用者提供了最大的靈活性,並協助使用者實現更加複雜的目標。
BDR的另外一個優勢是衝突修改可以被記錄在一個表中。換句話說,如果發生衝突,逆轉系統中正在發生的工程仍然是可能的。資料不會被默默地遺忘,但會為以後的調查被儲存。
當談到衝突,BDR的使用必須要牢記;BDR被設計成一個(地理地)分散式資料庫系統,它允許您處理大量的資料。從一致性的觀點來看,有一件事情必須要牢記:一個在紐約的人和一個在羅馬的人在它們的節點上同時更改同一行的可能性有多大?如果這是在表中情況下的衝突,BDR真的不是合適的解決方案。然而,如果衝突幾乎不發生(這對99%的應用程式的情況下來說),BDR是真正需要選擇的方案。請記住,只要同一行同時被許多人改變,或者如果違反了一個主鍵,就會發生衝突。如果兩個人更改完全相互獨立的兩行,衝突時不會發生的。因此,對於有些場合來說,最終一致性是一個合理的選擇。
分布序列
序列是潛在的衝突來源。想象一下,數以百計的使用者同時往一個相同的表中添加資料。任何自動成長列都會立即變成 衝突的真正來源,因為執行個體傾向於重複賦值相同或者相似的數字。
因此,BDR提供了分布式序列。每個節點不是被賦為一個值而一系列值,然後,這些值可以被使用,直到下一個系列值被分配。分布式序列的可用性大大減少了潛在的衝突的數量,並協助您的系統比其它方式更順利地運行。
處理DDLs
資料結構絕不是固定不變的。一旦在一段時間內,結構更改就會發生。可能一張表要被添加資料,或者一列必須被刪除,等等。
BDR可以很好地處理這些操作。大多數DDLs只是被複製,因為它們只是被發送到所有節點並執行。然而,也有被禁止的命令。這裡有兩個比較突出的:
ALTER TABLE ... ALTER COLUMN ... USING();
ALTER TABLE ... ADD COLUMN ... DEFAULT;
請記住,衝突很可能是並發的,所以沒有這些命令並非太多的問題。在大多數情況下,這些限制並不重要。然而,它們必須被牢記。
在許多情況下,有一個為那些命令變通的方法,例如,設定明確的值,和其它的方法。
BDR的使用情境
對BDR來說,既有好的使用情境也有不好的使用情境。當然,這條規則適用於每一個軟體。然而,資料庫系統有點不同,在作出決定之前仔細思考是必要的。
好的BDR使用情境
一般來說,如果一個特定的資料集只在一個節點上修改,BDR工作的最好。這大大減少了衝突的可能性,並協助您享受一個平穩的過程。
在一個節點上修改實際意味著什麼呢?我們假設有三個地點:維也納,柏林,倫敦。與工作在柏林的德國人而言,在維也納工作的修改奧地利的資料的可能性更大。德國操作員通常修改德國客戶的資料而不是奧地利或者英國人的資料。
奧地利操作員極有可能更改奧地利的資料。每個操作員在每個國家都應該看到公司所有的資料。然而,更可能的是,資料在哪裡被建立,就在哪裡被改變。從商業的角度來看,這基本上是不可能的,兩個人同一時間在不同的國家更改相同的資料—衝突時不可能的。
除此之外,如果工作負載主要有寫操作組成,而不是UPDATE 或者DELETE 操作。插入式不太可能引起衝突的,因此,對BDR來說是一個好的工作負載。
壞的BDR 使用情境
然而,也有一些一般來說,對BDR是沒有益處的工作負載,如果一致性是您的首要目標,而且是您的應用程式的關鍵,那麼,使用BDR當然不是正確的選擇。由於產品的非同步特性,衝突和一致性可以相互抵消。
對BDR來說,另外一個不好的情境是,如果所有的節點需要在同一時間看到完全相同的資料,BDR是很困難的。
BDR可以有效地寫資料。然而,它不能無限制地擴充寫操作,因為在這一點,所有的寫操作仍然結束於每台伺服器。請記住,只有通過實際拆分資料,才能擴充寫操作。所以,如果您正在找一個可以較好地擴充寫操作的方案,PL/Proxy可能是一個更好的選擇。
邏輯解碼的戲法
BDR背後的主要概念是邏輯解碼。正如本書已經提到的,邏輯解碼已經被發明來剖析交易記錄流和把二進位日誌轉換到一個更可讀的格式,如SQL。和二進位流相比,邏輯流的優勢是複製可以更容易地在版本邊界發生。
另外一個重要的優點是不需要同步物理XLOG位置。正如本書在前面章節所展示的,XLOG地址是非常重要的,要使事情工作,不可以更改。因此,基於XLOG的複製總是單master和多slave複製。根本沒有辦法把兩個二進位XLOG流統一到一個改變的流。邏輯解碼以一個優雅的方式解決了那個問題,因為留下了整個XLOG同步的問題。通過SQL格式複製真實物理的更改,獲得了很多的靈活性並提供了對為未來的改進的新的操作。
整個XLOG解碼事情基本上在幕後進行;終端使用者不會注意到它。
安裝BDR
安裝BDR很容易。該軟體作為一個來源程式包是可用的,並且它可以直接使用二進位包來部署。當然,從原始碼來安裝也可以的。然而,隨著越來越多的更改轉移到PostgreSQL核心,這一過程可能會改變。因此,我決定跳過原始碼安裝。
安裝二進位包
在前面的章節中,您已經學習了如何使用先行編譯的二進位軟體包在Linux系統上安裝BDR。選擇的顯示安裝工作是如果進行的Linux發行版是CentOS 7(要找到關於其它包的資訊,請檢查http://bdr-project.org/docs/stable/installation.html)
安裝過程本身是簡單的、首先安裝repo:
yum install http://packages.2ndquadrant.com/postgresql-bdr94-2ndquadrant/ yum-repo-rpms/postgresql-bdr94-2ndquadrant-redhat-1.0-2.noarch.rpm
接下來的步驟中,可以部署BDR:
yum install postgresql-bdr94-bdr
一旦在所有的節點上都安裝了DBR,系統就準備好運行了。
[請記住,BDR仍處於相當早期的發展狀態,因此,隨後的過程可能可能會隨時間而改變。]
設定一個簡單的叢集
一旦安裝完成,是時間開始並實際地設定一個簡單的叢集了。在這個情境,將建立一個由三個節點群組成的叢集。
請注意,為了讓初學者更容易使用,所有的資料節點都將安裝在相同的物理伺服器上。
安排儲存
管理員要做的第一件事情是為PostgreSQL建立一些空間。在這個簡單的例子中,只建立三個目錄:
[[email protected] ~]# mkdir /data
[[email protected] ~]# mkdir /data/node1 /data/node2 /data/node3
確保這些目錄屬於postgres(如果使用postgres使用者運行PostgreSQL,這通常是一個好主意):
[[email protected] ~]# cd /data/
[[email protected] data]# chown postgres.postgres node*
一旦建立了這些目錄,就準備好了一個成功的設定所需要的一切:
[[email protected] data]# ls -l
total 0
drwxr-xr-x 2 postgres postgres 6 Apr 11 05:51 node1
drwxr-xr-x 2 postgres postgres 6 Apr 11 05:51 node2
drwxr-xr-x 2 postgres postgres 6 Apr 11 05:51 node3
建立資料庫執行個體
為我們的實驗建立了一些空間之後,對交叉檢查我們的系統路徑是有意義的。確保正確的PostgreSQL版本在您的路徑中。一些使用者報告了在安裝過程中因為意外地,一些作業系統提供的PostgreSQL版本在那個路徑中中的問題。因此,只需檢查路徑並做相應的設定是有意義的,如果需要:
export PATH=/usr/pgsql-9.4/bin:$PATH
然後,可以建立三個資料庫執行個體。initdb命令可以用在任何通常的情況:
[[email protected] ~]$ initdb -D /data/node1/ -A trust
[[email protected] ~]$ initdb -D /data/node2/ -A trust
[[email protected] ~]$ initdb -D /data/node3/ -A trust
為了使安裝過程更簡單,trust將被用作驗證方法。當然,使用者身分識別驗證是可能的,但它不是這章主題的核心,所以最好儘可能地簡化這部分。
既然已經建立了三個資料庫執行個體,可以調整postgresql.conf了。以下參數是需要的:
shared_preload_libraries = ‘bdr‘
wal_level = ‘logical‘
track_commit_timestamp = on
max_connections = 100
max_wal_senders = 10
max_replication_slots = 10
max_worker_processes = 10
要做的第一件事是吧BDR模組載入到PostgreSQL中。它包含了複製的重要基礎設施。下一步,必須啟用邏輯解碼。它將是整個基礎設施的支柱。
要讓BDR工作,就必須把track_commit_timestamp 開啟。在標準PostgreSQL9.4中,這個設定是不存在的。它將最有可能和BDR一起出現在未來的PostgreSQL版本中。知道了提交的時間戳記對BDR的內部衝突解決演算法(最後勝利)是必不可少的。
然後,max_wal_senders必須和 replication slots一起設定。流複製也需要這些設定,也不應是一個大的驚喜。
最後,有一個max_worker_processes。只要PostgreSQL被啟動,BDR就在後台發起一些用戶端背景工作處理序。這些背景工作處理序是基於標準後台工作API的,並且在複製過程中被處理資料轉送所需要。確保有足夠可用的進程是必不可少的。
最後,還有一些衝突相關的設定可以被使用:
# Handling conflicts
#bdr.default_apply_delay=2000 # milliseconds
#bdr.log_conflicts_to_table=on
既然postgresql.conf已經配置好了,是時候把注意力集中在pg_hba.conf上了。在最簡單的情況下,簡單的複製規則必須被建立:
local replication postgres trust
host replication postgres 127.0.0.1/32 trust
host replication postgres ::1/128 trust
請注意,在一個真實的,高效的設定中,一個明智的管理者會配置一個特殊的複製使用者並設定一個 密碼,或者使用其它認證方法。為了簡單起見,這個過程已經被排除在這裡了。
使資料庫開始工作就像普通PostgreSQL一樣:
pg_ctl -D /data/node1/ start
pg_ctl -D /data/node2/ start
pg_ctl -D /data/node3/ start
對於我們的測試,需要一個資料庫一個執行個體:
createdb test -p 5432
createdb test -p 5433
createdb test -p 5434
載入模組並啟動叢集
到目前為止,非常好!為了確保BDR可以做它的工作,它必須被載入到資料庫中。需要兩個擴充,即btree_gist和bdr:
[[email protected] node1]$ psql test -p 5432
test=# CREATE EXTENSION btree_gist;
CREATE EXTENSION
test=# CREATE EXTENSION bdr;
CREATE EXTENSION
這些擴充必須被載入到之前建立的三個資料庫中。僅僅將它們載入到一個組件是不夠的。把它們載入到所有的資料庫中是至關重要的。
最後,我們的資料庫節點必須都被加入到一個BDR組中。到目前為止,只有三個獨立的資料庫執行個體,其中正好包含謝謝模組。在下一步中,這些節點將彼此串連。
首先要做的是建立一個BDR組:
test=# SELECT bdr.bdr_group_create(
local_node_name := ‘node1‘,
node_external_dsn := ‘port=5432 dbname=test‘
);
bdr_group_create
------------------
(1 row)
基本上,需要兩個參數:本地名稱和一個從遠程主機串連到節點的資料庫連接。要定義local_node_name,最簡單的做法是,給節點一個簡單名字。
要檢查節點是否為BDR做好了準備,調用下面的函數。如果答案和下面的一樣,就意味著配置沒有問題:
test=# SELECT bdr.bdr_node_join_wait_for_ready();
bdr_node_join_wait_for_ready
------------------------------
(1 row)
現在是將其它節點添加到複製系統的時候了:
test=# SELECT bdr.bdr_group_join(
local_node_name := ‘node2‘,
node_external_dsn := ‘port=5433 dbname=test‘,
join_using_dsn := ‘port=5432 dbname=test‘
);
bdr_group_join
----------------
(1 row)
再次,一個NULL值是一個很好的標誌。首先,第二個節點被添加到了BDR。然後,第三個節點一個可以加入:
test=# SELECT bdr.bdr_group_join(
local_node_name := ‘node3‘,
node_external_dsn := ‘port=5434 dbname=test‘,
join_using_dsn := ‘port=5432 dbname=test‘
);
bdr_group_join
----------------
(1 row)
一旦所有的節點都被添加了,管理員可以檢查是否所有的節點都準備好了:
[[email protected] node2]$ psql test -p 5433
test=# SELECT bdr.bdr_node_join_wait_for_ready();
bdr_node_join_wait_for_ready
------------------------------
(1 row)
[[email protected] node2]$ psql test -p 5434
test# SELECT bdr.bdr_node_join_wait_for_ready();
bdr_node_join_wait_for_ready
------------------------------
(1 row)
如果兩個查詢都返回NULL,就意味著系統運行良好。
檢查您的設定
這個簡單過程之後,BDR啟動並運行了。為了檢查是否所有的工作都如預期的那樣,檢查相關複製進程是有意義的:
[[email protected] ~]$ ps ax | grep bdr
31296 ? Ss 0:00 postgres: bgworker: bdr supervisor
31396 ? Ss 0:00 postgres: bgworker: bdr db: test
31533 ? Ss 0:00 postgres: bgworker: bdr supervisor
31545 ? Ss 0:00 postgres: bgworker: bdr supervisor
31553 ? Ss 0:00 postgres: bgworker: bdr db: test
31593 ? Ss 0:00 postgres: bgworker: bdr db: test
31610 ? Ss 0:00 postgres: bgworker: bdr (6136360420896274864,1,16385,)->bdr (6136360353631754624,1,
...
31616 ? Ss 0:00 postgres: bgworker: bdr (6136360353631754624,1,16385,)->bdr (6136360420896274864,1,
正如你所看到的,每個執行個體都會有至少三個BDR進程。如果這些進程都在,這通常是一個好的標誌,並且複製應該像預期的那樣工作。
一個簡單的測試可以揭示系統是否工作:
test=# CREATE TABLE t_test (id int, t timestamp DEFAULT now() );
CREATE TABLE
表建立之後,該結構應該看起來像這樣:
test=# \d t_test
Table "public.t_test"
Column | Type | Modifiers
--------+-----------------------------+---------------
id | integer |
t | timestamp without time zone | default now()
Triggers:
truncate_trigger AFTER TRUNCATE ON t_test FOR EACH STATEMENT EXECUTE PROCEDURE bdr.queue_truncate()
這張表看起來像預期的那樣。只有一個例外:一個TRUNCATE觸發器被自動建立。請記住,replication slots能夠流傳輸INSERT, UPDATE, 以及 DELETE 語句。DDLs和TRUNCATE現在是行層級資訊,因此,這些語句還不再流中。觸發器被需要來捕獲TRUNCATE並把它複製成純文字。不要嘗試更改或者刪除觸發器。
要測試複製,一個簡單的INSERT語句就可以工作:
test=# INSERT INTO t_test VALUES (1);
INSERT 0 1
test=# TABLE t_test;
id | t
----+----------------------------
1 | 2015-04-11 08:48:46.637675
(1 row)
在這個例子中,該值已經被添加到監聽5432的執行個體。一個快速的檢查顯示,資料已經很好地被複製到監聽5433和5434的執行個體中:
[[email protected] ~]$ psql test -p 5434
test=# TABLE t_test;
id | t
----+----------------------------
1 | 2015-04-11 08:48:46.637675
(1 row)
處理衝突
正如本章前面所屬,當與BDR一起工作時,衝突是一件重要的事情。請記住,BDR被設計成一個分布式的系統,所以,當衝突不太可能時使用它才有意義。然而,瞭解衝突事件中發生了什麼事情是很重要的。
要顯示發生了什麼,這裡有一個簡單的表:
test=# CREATE TABLE t_counter (id int PRIMARY KEY);
CREATE TABLE
然後,添加一行:
test=# INSERT INTO t_counter VALUES (1);
INSERT 0 1
要運行測試,一個簡單的SQL查詢是必要的。在這個例子中,使用了10000條UPDATE語句:
[[email protected] ~]$ head -n 3 /tmp/script.sql
UPDATE t_counter SET id = id + 1;
UPDATE t_counter SET id = id + 1;
UPDATE t_counter SET id = id + 1;
現在這個指令碼被執行三次,一個節點上執行一次:
[[email protected] ~]$ cat run.sh
#!/bin/sh
psql test -p 5432 < /tmp/script.sql > /dev/null &
psql test -p 5433 < /tmp/script.sql > /dev/null &
psql test -p 5434 < /tmp/script.sql > /dev/null &
由於同一行一遍又一遍地衝擊,衝突的數量預計將猛增。
[請注意,這不是BDR最初建立的的目的。它只是一個示範,以顯示衝突事件中發生了什麼。]
一旦這三個指令碼完成了,就可以檢查出衝突方面發生了什麼:
test=# \x
Expanded display is on.
test=# TABLE bdr.bdr_conflict_history LIMIT 1;
-[ RECORD 1 ]------------+------------------------------
conflict_id | 1
local_node_sysid | 6136360318181427544
local_conflict_xid | 0
local_conflict_lsn | 0/19AAE00
local_conflict_time | 2015-04-11 09:01:23.367467+02
object_schema | public
object_name | t_counter
remote_node_sysid | 6136360353631754624
remote_txid | 1974
remote_commit_time | 2015-04-11 09:01:21.364068+02
remote_commit_lsn | 0/1986900
conflict_type | update_delete
conflict_resolution | skip_change
local_tuple |
remote_tuple | {"id":2}
local_tuple_xmin |
local_tuple_origin_sysid |
error_message |
error_sqlstate |
error_querystring |
error_cursorpos |
error_detail |
error_hint |
error_context |
error_columnname |
error_typename |
error_constraintname |
error_filename |
error_lineno |
error_funcname |
BDR提供了一個簡單並且非常容易的方式來讀取包含所有衝突行的表。在頂部的LSN,事務ID以及更多衝突相關資訊的顯示。在這個例子中,BDR已經做了一個skip_change解決方案。記得每個更改的行都會命中相同的行,因為我們是非同步多主。這對BDR來說是非常討厭的。在這個例子中
UPDATE語句確實被跳過了;理解這一點是非常重要的。BDR可以跳過衝突或者並發事件在您的叢集中的更改。
理解集合
到目前為止,已經使用了整個叢集。每個人都能夠複製資料到其他人。在許多情況下,這是不需要的。BDR在這方面比較有彈性。
單向複製
BDR不僅能夠進行雙向複製,也可以進行雙向複製。在某些情況下,這是非常方便的。考慮一個系統只提供讀服務。一個簡單的單向slave可能是您所需要的。
BDR提供了一個簡單的函數來註冊一個節點作為一個單向的slave:
bdr.bdr_subscribe(local_node_name,
subscribe_to_dsn,
node_local_dsn,
apply_delay integer DEFAULT NULL,
replication_sets text[] DEFAULT ARRAY[‘default‘],
synchronize bdr_sync_type DEFAULT ‘full‘)
當然,也有可能從單向複製中刪除一個節點:
bdr.bdr_unsubscribe(local_node_name)
安裝過程非常簡單,適合BDR的基本設計原則。
處理資料表
BDR的妙處在於不需要複製整個執行個體到一個叢集。複製可以是非常細粒度的,並且管理員可以決定什麼資料複製到哪裡。兩個函數可以用於表複製集合:
bdr.table_set_replication_sets(p_relation regclass, p_sets text[])
這設定了一個表的複製集合。最初的分配將被覆蓋。
如果您想要知道一個表屬於哪個複製集合,可以調用下面的函數:
bdr.table_get_replication_sets(relation regclass) text[]
我們會在部分複製地區隨著BDR的發展看到更多的功能。它將允許您根據需要靈活地調度資料。
控制複製
由於維護的原因,可能保持和一次又一次地恢複復制是有必要的。只要考慮一個主要軟體更新。它可能會對您的資料結構做一些討厭的事情。您絕對不想有錯誤的東西被複製到您的系統。因此,它可以方便地停止複製並重新啟動它,一旦它被證明是正常的。
兩個函數可以用於這個工作:
SELECT bdr.bdr_apply_pause()
要再次重新啟動,可以使用下面的函數:
SELECT bdr.bdr_apply_resume()
串連到遠程節點(或者節點)被保持,但是不能從它們那裡讀取資料。暫停請求的效果不是長久的,所以,如果PostgreSQL被重新啟動或者postmaster在後台故障之後重新恢複,重放將重新恢複。中止個人後台使用pg_terminate_backend將不會引起重放來恢複,或者將重裝postmaster,而不需要完全重新啟動。沒有選擇從唯一一個對等節點來暫停一個重放
總結
BDR在PostgreSQL的複製世界中是一個後起之秀。目前,它仍然處於開發中,我們可以在不遠的將來期待更多(也許在您手中拿著這本書的時候)。
BDR是一個非同步多主並且允許人們運行地理分散式資料庫。記住複製衝突率很低的時候BDR是特別有用的是非常重要的。
PostgreSQL Replication之擴充與BDR