PL/Proxy可以在CONNECT模式或CLUSTER模式下使用。
在“連接”模式下,PL/Proxy充當到另一個數據庫的傳遞代理。每個PL/Proxy函數都包含一個libpq連接字符串,用于連接到將要代理請求的數據庫。
PL/Proxy也可以在CLUSTER模式下使用,它提供了對基于聚類功能的多個數據庫的數據分區支持。
在CONNECT模式下使用PL/Proxy時,不需要特殊配置。但是,在CLUSTER模式下使用PL/Proxy需要通過群集配置API或SQL/MED定義群集配置。
pg_plproxy install --通過群集配置API
plproxy 參考網址
plproxy 參考網址2
plproxy 參考網址3
plproxy 參考網址4
創建三個容器,每一個容器都安裝了postgresql數據庫軟件,并且每一個數據庫都可以遠程連接。其中一個數據庫作為proxy節點 install plproxy,稱為P1,剩下的兩個數據庫作為數據節點,稱為D1,D1。每一個容器都有一個testduliyan的數據庫。每一個數據庫都要創建plpgsql過程語言--createlang plpgsql testduliyan, 這個過程是相對于物理機而言的。
配置總結
pg_cluster0 ip 172.17.0.11 P1 testduliyan install plproxy
pg_cluster1 ip 172.17.0.12 D1 testduliyan
pg_cluster2 ip 172.17.0.13 D2 testduliyan
plproxy的配置是通過三個函數(過程)實現的。get_cluster_version()函數在每個請求上被調用,它應該返回特定集群當前配置的版本號。
如果這個函數返回的版本號高于一個plproxy緩存的版本號,那么配置和分區信息將通過調用get_cluster_config()和get_cluster_partitions()函數被重載。如果以上三個函數配置完成了,基本的配置就結束了。之后就是根據需要在proxy節點或者數據節點創建函數。
特性;
plproxy是使用長連接的, 會話會復用連接。 如果修改了server, 那么這些連接會斷開, 重新連接,所以不需要擔心修改server帶來的連接cache問題。如果業務為短連接的形式, 那么需要1層連接池, 在應用程序和plproxy數據庫之間,而不是plproxy和數據節點之間。在應用程序和plproxy之間加連接池后, 其實對于plproxy來說就是長連接了, 所以在plproxy和數據節點之間也就不需要連接池了。
注意事項:
- proxy節點不需要存在數據。
- 數據節點的數量必須是2的冪。
- 需要創建一個plproxy的schema -- create schema plproxy ,因為配置的三個函數使用這個schema。
- 數據節點可以是一個物理機,也可以是一個物理機中的一個數據庫。可以通過get_cluster_partitions動態的添加和刪除數據節點,但是需要重啟數據庫。
- proxy節點和數據節點具備相同的schema,或者使用 target func_name(schema.數據節點的函數名)。
- 在proxy節點執行函數時,所有數據節點必須具有與proxy節點同名的函數,或者使用 target func_name(數據節點的函數名) 。
- 修改后需要重啟數據庫
- 數據節點的函數僅僅需要同名或者使用target指向,與使用什么語言沒有關系(plpgsql和sql都可以)
已經配置的proxy集群如何添加和刪除節點
- 創建一個新的物理機或者在已經的物理機創建一個新的數據庫都可以。 如果在一個原有的物理機上添加一個數據庫,沒有創建plpgsql過程語言--createlang plpgsql xxx的過程。
- 新添加的節點和原來的舊節點的總和必須是2的冪,2,4,8……。不然會報錯,顯示invalida part count.
- 然后修改proxy節點的get_cluster_partitions 添加或者移除數據節點。
##################以下配置都是在P1上做的配置,D2,D1不需要。
install postgresql
復制 plproxy-2.8.tar.gz到容器內部
- gunzip plproxy-2.8.tar.gz
- tar xf plproxy-2.8.tar
- cd plproxy-2.8
- find / -name pg_config-- /usr/pgsql-9.6/bin/pg_config
對于bash: make: command not found錯誤
yum -y install gcc automake autoconf libtool make
export PATH=/usr/pgsql-9.6/bin:$PATH
對于Makefile:66: /usr/pgsql-9.6/lib/pgxs/src/makefiles/pgxs.mk: No such file or directory錯誤
- yum install postgresql96-devel
對于/usr/bin/flex -osrc/scanner.c --header-file=src/scanner.h src/scanner.l
make: /usr/bin/flex: Command not found
make: *** [src/scanner.c] Error 127
- yum install flex
對于/usr/bin/flex -osrc/scanner.c --header-file=src/scanner.h src/scanner.l
/usr/bin/bison -b src/parser -d src/parser.y
make: /usr/bin/bison: Command not found
make: *** [src/parser.tab.c] Error 127
- yum install bison
- make USE_PGXS=1
- make USE_PGXS=1 install
對于psql: FATAL: role "root" does not exist
command failed: "/usr/pgsql-9.6/bin/psql" -X -c "DROP DATABASE IF EXISTS "regression"" "postgres"
- 進入數據庫create user root with superuser password '123456';
對于ERROR: PL/Proxy function public.get_random_number_from_each_server(0): [p1] PQconnectPoll: fe_sendauth: no password supplied
- 修改host all all 127.0.0.1/32 peer or md5 -->trust
- localhost all all peer or md5 -->trust
ERROR: PL/Proxy function public.test_connect1(0): [test_part] PQconnectPoll:
FATAL: Peer authentication failed for user "root"
- make installcheck-- 全部通過
- find / -name plproxy.sql -- /var/plproxy-2.8/sql/plproxy.sql
創建plproxy - su postgres
- psql -h 127.0.0.1 -U postgres -f /var/plproxy-2.8/sql/plproxy.sql testduliyan;
為proxy節點的數據庫創建plpgsql過程語言 - su postgres
- createlang plpgsql testduliyan;--select * from pg_language;
在proxy節點上創建名字為plproxy的schema
- su postgres
- psql
- \c testduliyan
- create schema plproxy;
plproxy的配置是通過三個函數(過程)實現的,這三個函數的標準模版如下:
這個函數是讓plproxy可以找到對應的集群
CREATE OR REPLACE FUNCTION plproxy.get_cluster_partitions(cluster_name text)
RETURNS SETOF text AS $$
BEGIN
IF cluster_name ='testcluster' THEN
RETURN NEXT 'dbname=testduliyan host=172.17.0.12';
RETURN NEXT 'dbname=testduliyan host=172.17.0.13';
RETURN;
END IF;
RAISE EXCEPTION 'Unknown cluster';
END;
$$ LANGUAGE plpgsql;
這個函數是plproxy用于判斷是否給前端返回已經cache過的結果用的
CREATE OR REPLACE FUNCTION plproxy.get_cluster_version(cluster_name text)
RETURNS int4 AS $$
BEGIN
IF cluster_name = 'testcluster' THEN
RETURN 1;
END IF;
RAISE EXCEPTION 'Unknown cluster';
END;
$$ LANGUAGE plpgsql;
這個函數是獲取不同的集群的配置
create or replace function plproxy.get_cluster_config(cluster_name text, out key text, out val text)
returns setof record as $$
begin
key := 'statement_timeout';
val := 60;
return next;
return;
end;
$$ language plpgsql;
在D1,D2 節點數據庫創建plpgsql過程語言
- su postgres
- createlang plpgsql testduliyan;--select * from pg_language;
---------------------配置結束,以下是測試過程
在P1proxy代理上執行
CREATE OR REPLACE FUNCTION public.dqlexec(query text) RETURNS setof
record AS $$
CLUSTER 'testcluster';
RUN ON ALL;
TARGET dig.dqlexec2;---- 如果數據節點函數與proxy節點的函數不同名,使用target標注
$$ LANGUAGE plproxy;
CREATE OR REPLACE FUNCTION public.dqlexec(query text) RETURNS setof
record AS $$
CLUSTER 'testcluster';
RUN ON ALL;
$$ LANGUAGE plproxy;
CREATE OR REPLACE FUNCTION public.ddlexec(query text) RETURNS setof integer
AS $$
CLUSTER 'testcluster';
RUN ON ALL;
$$ LANGUAGE plproxy;
CREATE OR REPLACE FUNCTION public.dmlexec(query text) RETURNS setof integer
AS $$
CLUSTER 'testcluster';
RUN ON ANY;
$$ LANGUAGE plproxy;
在D1,D2上執行
CREATE OR REPLACE FUNCTION public.ddlexec(query text)
RETURNS integer AS
$BODY$
declare
ret integer;
begin
execute query;
return 1;
end;
$BODY$
LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION public.dmlexec(query text)
RETURNS integer AS
$BODY$
declare
ret integer;
begin
execute query;
return 1;
end;
$BODY$
LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION public.dqlexec(query text)
RETURNS SETOF record AS
$BODY$
declare
ret record;
begin
for ret in execute query loop
return next ret;
end loop;
return;
end;
$BODY$
LANGUAGE 'plpgsql';
測試在P1節點
select ddlexec('create table usertable(id integer)');
select dmlexec('insert into usertable values(0)');
select dmlexec('insert into usertable values(1)');
select dmlexec('insert into usertable values(2)');
select dmlexec('insert into usertable values(3)');
select dmlexec('insert into usertable values(4)');
select dmlexec('insert into usertable values(5)');
select dmlexec('insert into usertable values(6)');
select dmlexec('insert into usertable values(7)');
select dmlexec('insert into usertable values(8)');
select dmlexec('insert into usertable values(9)');
select dmlexec('insert into usertable values(10)');
select * from dqlexec('select * from usertable') as (id integer);
在 plproxy 節點上創建一個同名的插入函數,用于進行集群檢索
CREATE OR REPLACE FUNCTION insert_user(i_username text, i_emailaddress text)
RETURNS integer AS $$
CLUSTER 'testcluster';
RUN ON hashtext(i_username);
$$ LANGUAGE plproxy;
在 plproxy 節點上創建一個查詢函數,用于進行集群檢索
CREATE OR REPLACE FUNCTION get_user_email(i_username text)
RETURNS text AS $$
CLUSTER 'testcluster';
RUN ON hashtext(i_username) ;
SELECT email FROM users WHERE username = i_username;
$$ LANGUAGE plproxy;
plproxy代理數據庫,可以在代理數據庫上執行這個腳本,他可以查看遠程的數據據節點。
CREATE FUNCTION get_user_email(i_username text)
RETURNS SETOF text AS $$
CONNECT 'dbname=part00';
SELECT email FROM users WHERE username = $1;
$$ LANGUAGE plproxy;
SELECT * from get_user_email($1);
D1,D2 創建表
CREATE TABLE users (
username text,
email text
);
CREATE OR REPLACE FUNCTION insert_user(i_username text, i_emailaddress text)
RETURNS integer AS $$
INSERT INTO users (username, email) VALUES ($1,$2);
SELECT 1;
$$ LANGUAGE SQL;
P1數據庫上沒有數據,但是可以通過p1查看其他D1,D2 的數據,以下語句在P1執行,但是P1沒有users 表
SELECT insert_user('Sven','sven@somewhere.com');
SELECT insert_user('Marko', 'marko@somewhere.com');
SELECT insert_user('Steve','steve@somewhere.cm');
SELECT get_user_email('Sven');
SELECT get_user_email('Marko');
SELECT get_user_email('Steve');
在proxy運行后,繼續在此基礎上添加新的數據節點,不過要求必須2的冪。在兩個數據節點的基礎上又添加了兩個數據節點。
D3,D4數據節點配置:
創建plpgsql過程語言
- su postgres
- createlang plpgsql testduliyan;--select * from pg_language;
要添加和proxy的調用的同名的function.
- create table usertable(id integer);
CREATE OR REPLACE FUNCTION ddlexec(query text)
RETURNS integer AS
$BODY$
declare
ret integer;
begin
execute query;
return 1;
end;
$BODY$
LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION dmlexec(query text)
RETURNS integer AS
$BODY$
declare
ret integer;
begin
execute query;
return 1;
end;
$BODY$
LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION dqlexec(query text)
RETURNS SETOF record AS
$BODY$
declare
ret record;
begin
for ret in execute query loop
return next ret;
end loop;
return;
end;
$BODY$
LANGUAGE 'plpgsql';
proxy節點的修正,修正函數添加了兩個數據節點,然后重啟數據庫。
CREATE OR REPLACE FUNCTION plproxy.get_cluster_partitions(cluster_name text)
RETURNS SETOF text AS $$
BEGIN
IF cluster_name ='testcluster' THEN
RETURN NEXT 'dbname=testduliyan host=172.17.0.12';
RETURN NEXT 'dbname=testduliyan host=172.17.0.13';
RETURN NEXT 'dbname=testduliyan host=172.17.0.14';
RETURN NEXT 'dbname=testduliyan host=172.17.0.15';
RETURN;
END IF;
RAISE EXCEPTION 'Unknown cluster';
END;
$$ LANGUAGE plpgsql;
在此基礎上做測試,再次插入10條記錄,發現原來插入的數據不會改變,新插入的10條記錄被4個數據節點均分了。
CREATE OR REPLACE FUNCTION plproxy.get_cluster_partitions(cluster_name text)
RETURNS SETOF text AS $$
BEGIN
IF cluster_name ='testcluster' THEN
RETURN NEXT 'dbname=testduliyan host=172.17.0.12';
RETURN NEXT 'dbname=testduliyan host=172.17.0.13';
RETURN NEXT 'dbname=testduliyan host=172.17.0.14';
RETURN NEXT 'dbname=testduliyan_db host=172.17.0.11';
RETURN;
END IF;
RAISE EXCEPTION 'Unknown cluster';
END;
$$ LANGUAGE plpgsql;