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;