全国最完整的Repmgr高可用Postgresql

标题应该没有违反广告法

本教程为全国最完整的PGSQL高可用集群方案,以下的每一个步骤都事关重要。

安装环境

Centos7.8

Posgresql10

Repmgr10

两台服务器:

192.168.126.143

192.168.126.144

服务器名称:

hostnamectl set-hostname post1

hostnamectl set-hostname post2

数据库集群架构图

全国最完整的Repmgr高可用Postgresql

整体设计思路

1.先两台机器都安装Posgresql

2.两台机器都安装Repmgr

3.两台机器都设置postgres用户的相互信任

4.其中一台机器设置好postgresql的配置文件,并且初始化数据库

5.另一台机器克隆数据库

6.修改repmgr的配置

7.将两台机器加入集群节点

8.测试两台都关机

9.测试关机一台

10.安装keepalived配置VIP

11.配置高可用

安装步骤

1.分别安装Postgresql和Repmgr


两节点同时操作

yum install -y gcc bison gcc-c++ readline readline-devel zlib zlib-devel perl perl-devel telnet openssl openssl-devel

systemctl stop firewalld

安装PGSQL

sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

sudo yum install -y postgresql10-server

sudo /usr/pgsql-10/bin/postgresql-10-setup initdb

sudo systemctl enable postgresql-10

sudo systemctl start postgresql-10

安装REPMGR

curl https://dl.2ndquadrant.com/default/release/get/10/rpm | sudo bash

sudo yum repolist

yum search repmgr

yum --showduplicates list repmgr10

yum install repmgr10-4.4-1.el7

su postgres

ln -s /usr/pgsql-11/bin/repmgr /usr/local/sbin/

exit

安装完成后的路径如下:

可执行目录/usr/pgsql-10/bin/

数据实例的目录/var/lib/pgsql/10/data/

修改主节点的postgesql.conf


别的地方不改,默认改这几个地方

listen_addresses = '*' # what IP address(es) to listen on;

# comma-separated list of addresses;

# defaults to 'localhost'; use '' for all

# (change requires restart)

port = 5432 # (change requires restart)

max_connections = 1000 # (change requires restart)

wal_log_hints=on

archive_mode=on

archive_command='test ! -f /pgarch/%f && cp %p /pgarch/%f'

shared_preload_libraries='repmgr'

修改主节点的pg_hba.conf


注意IP地址根据自己情况来配置

local all all peer

# IPv4 local connections:

host all all 127.0.0.1/32 trust

host all all 172.16.108.54/32 md5

host all all 192.168.126.144/32 trust

host all all 192.168.126.143/32 trust

host all all 0.0.0.0/0 md5

# IPv6 local connections:

host all all ::1/128 ident

# Allow replication connections from localhost, by a user with the

# replication privilege.

local replication repmgr trust

local repmgr repmgr trust

host replication repmgr 127.0.0.1/32 trust

host replication repmgr 192.168.126.143/32 trust

host replication repmgr 192.168.126.144/32 trust

host repmgr repmgr 127.0.0.1/32 trust

host repmgr repmgr 192.168.126.143/32 trust

host repmgr repmgr 192.168.126.144/32 trust

修改主节点的vim /etc/repmgr/10/repmgr.conf


node_id=143

#唯一值,用于标识本服务器

node_name=post1

#连接到本机的信息

conninfo='host=192.168.126.143 user=repmgr dbname=repmgr connect_timeout=2'

##postgresql的data路径

data_directory='/var/lib/pgsql/10/data'

connection_check_type=ping

failover='automatic'

promote_command='repmgr standby promote -f /etc/repmgr/10/repmgr.conf --log-to-file'

follow_command='repmgr standby follow -f /etc/repmgr/10/repmgr.conf --log-to-file --upstream-node-id=%n'

log_file='/var/lib/pgsql/10/repmgrd.log'

monitoring_history=true # (启用监控参数)

monitor_interval_secs=1 #(定义监视数据间隔写入时间参数)

reconnect_attempts=2 #(故障转移之前,尝试重新连接主库次数(默认为6)参数)

reconnect_interval=2 #(每间隔5s尝试重新连接一次参数)

修改备节点的vim /etc/repmgr/10/repmgr.conf


node_id=144

#唯一值,用于标识本服务器

node_name=post2

##连接到本机的信息

conninfo='host=192.168.126.144 user=repmgr dbname=repmgr connect_timeout=2'

##postgresql的data路径

data_directory='/var/lib/pgsql/10/data'

connection_check_type=ping

failover='automatic'

promote_command='repmgr standby promote -f /etc/repmgr/10/repmgr.conf --log-to-file'

follow_command='repmgr standby follow -f /etc/repmgr/10/repmgr.conf --log-to-file --upstream-node-id=%n'

log_file='/var/lib/pgsql/10/repmgrd.log'

monitoring_history=true # (启用监控参数)

monitor_interval_secs=1 #(定义监视数据间隔写入时间参数)

reconnect_attempts=2 #(故障转移之前,尝试重新连接主库次数(默认为6)参数)

reconnect_interval=2 #(每间隔5s尝试重新连接一次参数)

修改完成后重启PGSQL

重启数据库服务器有两个方法

方法1

service postgresql-10 restart

方法2

su postgres

cd /usr/pgsql-10/bin/

./pg_ctl -D /var/lib/pgsql/10/data restart

2.SSH互信以及PGSQL互信

两台服务器同时操作

su postgres

psql

ALTER USER postgres WITH PASSWORD '你的数据库密码';

\q

exit

ssh-keygen -t rsa

ssh-copy-id -i /var/lib/pgsql/.ssh/id_rsa.pub postgres@192.168.126.144

restorecon -r -vv /var/lib/pgsql/.ssh

ssh 'postgres@192.168.126.144'

如果不用输入密码就可以进入,则正常

另一台机器也相同的操作,IP不一样,要测试一下 不输入密码才是对的

ssh-keygen -t rsa

ssh-copy-id -i /var/lib/pgsql/.ssh/id_rsa.pub postgres@192.168.126.143

restorecon -r -vv /var/lib/pgsql/.ssh

ssh 'postgres@192.168.126.143'

主操作

su postgres

createuser -s repmgr

createdb repmgr -O repmgr

#进入psql

psql

#在psql中设置repmgr依次默认查找repmgr、同用户名、public三个schema

ALTER USER repmgr SET search_path TO repmgr, "$user", public;

#退出psql

\q

在备机测试是否能连接主机

su postgres

psql 'host=192.168.126.14 user=repmgr dbname=repmgr connect_timeout=2'

重启主服务器并且将主服务器注册为主节点

service postgresql-10 restart

su postgres

repmgr -f /etc/repmgr/10/repmgr.conf primary register

repmgr cluster show

这样就可以看到集群中有一台主节点

3.备机克隆数据库


停止备用数据库

su postgres

cd /usr/pgsql-10/bin/

./pg_ctl -D /var/lib/pgsql/10/data stop

删除备用数据库的数据目录

rm -rf /var/lib/pgsql/10/data/*

从机测试克隆

repmgr -h 192.168.126.143 -U repmgr -d repmgr -f /etc/repmgr/10/repmgr.conf standby clone --dry-run

如果没报错就继续

从机执行克隆

repmgr -h 192.168.126.143 -U repmgr -d repmgr -f /etc/repmgr/10/repmgr.conf standby clone

克隆完成后,检查/var/lib/pgsql/10/data/目录是否又有文件了

4.备机加入集群


从机启动PGSQL

su postgres

cd /usr/pgsql-10/bin/

./pg_ctl -D /var/lib/pgsql/10/data start

从机注册子节点

repmgr -f /etc/repmgr/10/repmgr.conf standby register

repmgr cluster show

查看是否两台都加入集群

5.主备切换


测试主备手动一键切换

su postgres

repmgr -f /etc/repmgr/10/repmgr.conf standby switchover -U repmgr --verbose --dry-run

没有报错就执行切换,仅仅在子节点执行切换命令

repmgr -f /etc/repmgr/10/repmgr.conf standby switchover -U repmgr --verbose

执行后,检查主备是否交换

repmgr cluster show

两台节点都启动repmgrd

repmgrd –f /etc/repmgr/10/repmgr.conf --pid-file /tmp/repmgrd.pid

查看进程是否存在

cat/tmp/repmgrd.pid

将repmgrd设置为开机启动

新建文件/rep.sh

内容如下:

sleep 8s

su postgres -c "/usr/pgsql-10/bin/repmgrd –f /etc/repmgr/10/repmgr.conf --pid-file /tmp/repmgrd.pid"

shift+z+z保存

给文件授权

chmod +x rep.sh

编辑文件

vim /etc/rc.local

#!/bin/bash

touch /var/lock/subsys/local

./rep.sh




到这里,集群的主备搭建就完成了,支持水平扩展多个子节点

6.keepalived安装

两台节点同时操作

yum install -y keepalived

cat /etc/keepalived/keepalived.conf

ifconfig查看网卡名称为ens33

vim /etc/keepalived/keepalived.conf

[root@post1 /]# cat /etc/keepalived/keepalived.conf

! Configuration File for keepalived

vrrp_instance VI_1 {

state BACKUP

#网卡名字

interface ens33

#局域网不能有冲突

virtual_router_id 55

#设置为不抢占

nopreempt

priority 99

#4秒检查一次

advert_int 4

authentication {

auth_type PASS

auth_pass 1111

}

virtual_ipaddress {

#集群VIP

192.168.126.150

}

}

设置开机启动与服务启动

systemctl start keepalived.service

systemctl enable keepalived.service

检查VIP被谁拿走了

ip a

安装完成后,测试集群VIP是否生效,然后关机测试VIP是否能继续访问,程序继续往数据库写入。

7.高可用的配置


两台节点执行

sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm

sudo yum install dotnet-sdk-5.0

cd /root

mkdir vip

cd vip

dotnet new console

dotnet add package Npgsql --version 5.0.7

vim Program.cs编辑如下代码

using Npgsql;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
/****
 * 本代码的作用是确保keepalived的VIP是数据库的主节点。本代码仅仅只需要修改VIP和数据库链接密码
 * ***/
namespace vip
{
    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                try
                {
                    //两个服务器的数据库链接字符串,VIP地址,主=False,True是备
                    bool zb =true;
				    zb = Exec("Host=127.0.0.1;Port=5432;Username=postgres;Password=数据库的密码;Database=postgres");//请修改数据库密码
                    string vip = "192.168.126.150";//请修改这里的IP
                    //Console.WriteLine("主备情况=>True是备,Flase是主----》"+zb);
                    //获取IP地址是否包含VIP
                    if (GetVIP(vip) && !zb)   
                    {
                        //正常情况 VIP在本机,并且本机是F主
                        //什么也不做
                    }
                    else if (!GetVIP(vip) && zb)
                    {
                        //VIP不在本机,本机不是主,正常
                        //什么也不做
                    }
                    else if (GetVIP(vip) && zb)
                    {
						Console.WriteLine("本机不是主,但是有VIP,所以重启KEEPALIVED丢弃VIP");
                        //VIP在本机,但是本机不是主,则重启keepalived
                        Bash("service keepalived stop");
                        Thread.Sleep(4000);//睡4秒的作用是确保VIP已经漂移
                        Bash("service keepalived start");
                    }
                    else if (!GetVIP(vip) && !zb )
                    {
                        //VIP不在本机,本机是主,又另外的从机来执行重启keepalived.
                        //什么也不做
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("异常:" + ex.Message);
                    continue;
                }
                Thread.Sleep(100);//防止占用太多资源100ms检查一次
            }
        }
        /// <summary>
        /// 检查本机是否包含VIP
        /// </summary>
        /// <param name="vip"></param>
        /// <returns></returns>
        static bool GetVIP(string vip)
        {
            string ipa=Bash("ip a");
            // Console.WriteLine(ipa);
            if (ipa.Contains(vip))
                return true;
            else return false;
        }
		
		/// <summary>
        /// 执行BASH命令
        /// </summary>
        /// <param name="vip"></param>
        /// <returns></returns>
        public static string Bash(string escapedArgs)
        {
            var process = new Process()
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "/bin/bash",
                    Arguments = $"-c \"{escapedArgs}\"",
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                }
            };
            process.Start();
            string result = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
            return result;
        }
        /// <summary>
        /// 数据库相关操作,读取PGSQL的pg_is_in_recovery表
        /// </summary>
        /// <param name="vip"></param>
        /// <returns></returns>
        public static bool Exec(string connStr)
        {
			bool ct = true;
            try
            {
                using (var conn = new NpgsqlConnection(connStr))
                {
                    conn.Open();
                    using (NpgsqlCommand cmd = new NpgsqlCommand("select pg_is_in_recovery from pg_is_in_recovery()", conn))
                    {

                        NpgsqlDataReader rd = cmd.ExecuteReader();
                        while (rd.Read())
                        {
                            ct = Convert.ToBoolean(rd["pg_is_in_recovery"]);
                            return ct;
                        }
                        conn.Close();
                        return ct;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("SQL异常:" + ex.Message);
                return ct;
            }
	}

    }
}

dotnet build

执行测试一下功能

dotnet run

将本程序设置为两个节点开机启动

vim rep.sh

sleep 8s

su postgres -c "/usr/pgsql-10/bin/repmgrd –f /etc/repmgr/10/repmgr.conf --pid-file /tmp/repmgrd.pid"

nohup dotnet /root/vip/bin/Debug/net5.0/vip.dll&

保存退出,确保程序开机执行!

8.测试用例

以下测试过程保持测试工具一直数据插入和读取状体,保持不丢失数据,保持数据一致性。

初始条件

执行

影响范围

预期状态

其他

测试结果

A主,B备

关闭B

不影响业务

一切正常

B再开机也正常

通过

A主,B备

关闭A

业务10秒内恢复

B为备,VIP在B机

稳妥起见,A机要人工介入检查,执行/rejoin.sh

通过

A主,B备

同时关闭AB

影响业务

链接失败

重新开机业务自动恢复正常

通过

A主,B备

主备人工切换

业务10秒内恢复

B主,A从

数据库主备在3秒左右切换

vip在7秒左右切换

通过

A主,B备

停止B的PGSQL

无影响

无影响

B在启动也无影响

通过

A主,B备

停止A的PGSQL

业务10秒内恢复

B会变成主

repmgrd会自动切换B为主

VIP守护进程链接不上本机数据库,丢弃VIP

A机要人工介入检查,执行/rejoin.sh

通过

A主,B备

手动关闭A的keepalived

业务异常,不可读写

B有VIP,但是B发现自己的数据库是备机,会重启丢弃VIP,导致大家都没有VIP

VIP守护发现本机有VIP,但是不是主节点

会循环重启keepalived,直到A机恢复正常。

通过

A主,B备

手动关闭B的keepalived

业务正常

一切正常

再启动keepalived也正常

通过

A主,B备

断开两个节点链接

业务异常

ip地址冲突

网内无法访问

通过

测试工具以及代码

https://github.com/cagy520/MYSQL-CLUSTER-TEST.git

全国最完整的Repmgr高可用Postgresql

9.总结

repmgr做主备搭建相对PGPOOL要容易一些,而且也不像PGPOOL那样有性能损耗。

用keepalived的目的是为了主备虚拟一个集群IP地址出来,避免单节点故障。

使用VIP进程守护的目的是为了避免VIP漂移到备用节点上。

不使用shell脚本处理VIP保持主节点的原因是不够灵活,逻辑不易理解,会给后续部署带来麻烦。

尽量避免使用编译安装的方式安装repmgr,会遇到很多问题。

为什么主节点挂了之后,主节点漂移,原来主节点需要手动添加到集群,当遇到这种情况的时候,建议人工介入检查一下,避免数据不一致。

本教程的每一个细节步骤都很重要。

10.其他命令

测试主备切换,仅仅在备机执行

repmgr -f /etc/repmgr/10/repmgr.conf standby switchover -U repmgr --verbose --dry-run

repmgr -f /etc/repmgr/10/repmgr.conf standby switchover -U repmgr --verbose

原来故障的主节点从新作为备节点加入集群,也就是/rejoin.sh

repmgr node rejoin -d 'host=192.168.126.143 dbname=repmgr user=repmgr' --force-rewind --config-files=postgresql.conf,postgresql.auto.conf --verbose --dry-run

repmgr node rejoin -d 'host=192.168.126.143 dbname=repmgr user=repmgr' --force-rewind --config-files=postgresql.conf,postgresql.auto.conf --verbose

从集群中剔除节点

repmgr primary unregister --force --node-id post1

上一篇:记录一次Postgresql的repmgr高可用集群切换故障


下一篇:repmgr切换以及故障转移恢复