【PG高可用】Repmgr源码分析之主库监控

repmgr需要在集群中每个节点上以扩展的形式安装插件,运行在每个节点上的repmgrd可以监控复制以及执行故障转移或切换等操作增强 PostgreSQL 的内置复制功能。

如何可靠快速的监控主节点故障一般是所有数据库高可用程序中都会有的环节,本篇内容主要介绍不同角色的节点如何监控主节点是否存活。

函数入口

通过命令 repmgrd -f repmgr.conf 启动repmgrd后台监控程序时,进入程序的主函数 main(repmgrd.c文件)函数中,

在main 函数主要是处理命令行参数,获取本地节点的信息

	/* Retrieve record for this node from the local database */
	record_status = get_node_record(local_conn, config_file_options.node_id, &local_node_info);

通过SQL查询repmgr数据库中 nodes表 获取本地节点信息 ,查询SQL如下

	appendPQExpBuffer(&query,
					  "SELECT " REPMGR_NODES_COLUMNS
					  "  FROM repmgr.nodes n "
					  " WHERE n.node_id = %i",
					  node_id);

通过函数 start_monitoring(void) 启动对本地节点的监控,

首先会判断本地节点的角色,根据不同的节点角色进入到不同的监控函数中:

primary节点 对应 monitor_streaming_primary()

standby节点 对应 monitor_streaming_standby()

witness节点 对应 monitor_streaming_witness()

static void
start_monitoring(void)
{
	log_notice(_("starting monitoring of node \"%s\" (ID: %i)"),
			   local_node_info.node_name,
			   local_node_info.node_id);

	log_info(_("\"connection_check_type\" set to \"%s\""), print_connection_check_type(config_file_options.connection_check_type));
    // 进入一个无限循环
	while (true)
	{
	    // 根据节点的不同角色 ,进入到不同的监控处理分支
		switch (local_node_info.type)
		{
		    // 主节点
			case PRIMARY:
				monitor_streaming_primary();
				break;
			// 备节点
			case STANDBY:
				monitor_streaming_standby();
				break;
			// 见证节点
			case WITNESS:
				monitor_streaming_witness();
				break;
			case UNKNOWN:
				/* should never happen */
				break;
		}
	}
}

monitor_streaming_primary

如果是节点角色是PRIMARY,会进入到 monitor_streaming_primary 分支进行监控本地的主节点,以下是主要流程

1 重置节点的投票信息,设置节点当前不参与投票 VS_NO_VOTE

执行函数 reset_node_voting_status();

reset_node_voting_status();

该函数会调用执行数据库的函数 repmgr.reset_voting_status()

SELECT repmgr.reset_voting_status()

在数据库中查看 函数 reset_voting_status 定义,它告诉PostgreSQL查找‘libdir/repmgr动态链接库(DLL 或 so 文件),并在其中查找名为repmgr_reset_voting_status` 的函数。

repmgr=#\sf reset_voting_status
CREATE OR REPLACE FUNCTION repmgr.reset_voting_status()
 RETURNS void
 LANGUAGE c
 STRICT
AS '$libdir/repmgr', $function$repmgr_reset_voting_status$function$


函数解释

  • CREATE OR REPLACE FUNCTION: 如果函数已存在,则替换它;如果不存在,则创建它。

  • repmgr.reset_voting_status(): 函数名,表明这个函数属于 repmgr schema,并且函数名为 reset_voting_status

  • RETURNS void: 函数不返回任何值。

  • LANGUAGE c: 函数是用 C 语言编写的。

  • STRICT: 如果任何输入参数为 NULL,则函数不会执行并立即返回 NULL。

  • **AS 'libdir/repmgr′,functionrepmgrr​esetv​otings​tatusfunction∗∗:指定了函数的实现。这里,它告诉PostgreSQL查找‘libdir/repmgr动态链接库(DLL 或 so 文件),并在其中查找名为repmgr_reset_voting_status` 的函数。

查看 repmgr 源码中的函数 repmgr_reset_voting_status

Datum
repmgr_reset_voting_status(PG_FUNCTION_ARGS)
{   // 如果 shared_state 不为空 ,说明不是第一次启动?
	if (!shared_state)
		PG_RETURN_NULL();
    // 获取共享的锁
	LWLockAcquire(shared_state->lock, LW_SHARED);

	/* only do something if local_node_id is initialised */
	// 如果local_node_id 是初始化的值 ,则做一些操作
	if (shared_state->local_node_id != UNKNOWN_NODE_ID)
	{   // 释放共享锁
		LWLockRelease(shared_state->lock);
		// 获取排他锁
		LWLockAcquire(shared_state->lock, LW_EXCLUSIVE);
        // 将该节点的投票状态设置为 VS_NO_VOTE 不参与投票
		shared_state->voting_status = VS_NO_VOTE;
		// 将后端节点的node id 设置为UNKNOWN_NODE_ID
		shared_state->candidate_node_id = UNKNOWN_NODE_ID;
		// follow_new_primary 设置为 不
		shared_state->follow_new_primary = false;
	}
    // 释放锁
	LWLockRelease(shared_state->lock);

	PG_RETURN_VOID();
}

指针类型,共享内存的结构体

static repmgrdSharedState *shared_state = NULL;

shared_state 结构体

typedef struct repmgrdSharedState
{
	LWLockId	lock;			/* protects search/modification */
	TimestampTz last_updated;
	int			local_node_id;
	int			repmgrd_pid;
	char		repmgrd_pidfile[MAXPGPATH];
	bool		repmgrd_paused;
	/* streaming failover */
	int			upstream_node_id;
	TimestampTz upstream_last_seen;
	NodeVotingStatus voting_status;
	int			current_electoral_term;
	int			candidate_node_id;
	bool		follow_new_primary;
} repmgrdSharedState;

投票状态是一个枚举类型

typedef enum
{
	VS_UNKNOWN = -1,
	VS_NO_VOTE,
	VS_VOTE_REQUEST_RECEIVED,
	VS_VOTE_INITIATED
} NodeVotingStatus;

2 重置主节点的上游节点id 为 ​NO_UPSTREAM_NODE,因为主节点 不需要follow 任何其他节点

repmgrd_set_upstream_node_id(local_conn, NO_UPSTREAM_NODE);

3 根据 startup_event_logged 的值,发送一个 repmgrd_start(如果repmgrd未启动) 或 repmgrd_reload (如果repmgred已启动) 的事件通知

4 获取下游子节点的信息

bool success = get_child_nodes(local_conn, config_file_options.node_id, &db_child_node_records);

查询下游节点的SQL语句

appendPQExpBuffer(&query,
              "    SELECT n.node_id, n.type, n.upstream_node_id, n.node_name, n.conninfo, n.repluser, "
              "           n.slot_name, n.location, n.priority, n.active, n.config_file, "
              "           '' AS upstream_node_name, "
              "           CASE WHEN sr.application_name IS NULL THEN FALSE ELSE TRUE END AS attached "
              "      FROM repmgr.nodes n "
              " LEFT JOIN pg_catalog.pg_stat_replication sr "
              "        ON sr.application_name = n.node_name "
              "     WHERE n.upstream_node_id = %i ",
              node_id);

5 进入到while (true) 循环

51  (void) connection_ping(local_conn);  防止数据库连接过期失效 发出SELECT TRUE

5.2 check_connection(&local_node_info, &local_conn); 会对本地节点进行两次检查

5.3 如果节点状态是连接不正常的(PQstatus(local_conn) != CONNECTION_OK),而且原来的状态也被标记为启动的(NODE_STATUS_UP),会初始化一个本地节点不可用开始时间的变量(local_node_unreachable_start),并且把该变量设置为当前时间,然后发送一个 repmgrd_local_disconnect 事件通知,并将节点状态设置为 NODE_STATUS_UNKNOWN 。

5.4 为了防止网络抖动,会进行连接重试,最多重试多少次,每次重试间隔多长时间可以通过配置文件配置

try_reconnect(&local_conn, &local_node_info);

5.5 如果在上面重试过程中,节点状态又变为可用了(NODE_STATUS_UP) ,则 

  • 计算当前节点不可用时间

  • 当前节点设置为 UNKNOWN_NODE_ID

  • 准备事件通知

  • 记录日志

  • 发送事件通知 repmgrd_local_reconnect

  • 重新设置node_id  和 pid

  • 如果不是在主节点,则函数返回,并进入到loop

5.6 如果节点状态还是不可用的,则将节点监控状态设置为降级监控模式 MS_DEGRADED,并初始化降级监控的开始时间为当前时间。

注意

1 主节点不参与投票?

2 主节点上的后台进程不会进行切换操作,可能考虑到 ,主节点服务器宕机,提升命令都需要在本地执行

3 主节点上的repmgrd 会获取下游节点信息,并检查下游节点的状态

4 主节点loop 部分逻辑

  • 检查是否还是主节点 ,角色改变则函数返回return,重新判断节点

  • 每隔log_status_interval秒记录日志

  • reload repmgr配置

  • 监控休眠monitor_interval_secs秒

monitor_streaming_standby

忽略初始化部分 ,进入到 while(true)环节

1 执行检查上游节点连通性的函数 check_upstream_connection ,如果连接正常,更新内存中结构体 shared_state 中最新一次检查的时间

	shared_state->upstream_last_seen = GetCurrentTimestamp();
	shared_state->upstream_node_id = upstream_node_id;

2 如果上游节点连接不正常 ,会尝试 reconnect_attempts 次 重新连接到上游节点

primary_node_id = try_primary_reconnect(&upstream_conn, local_conn, &upstream_node_info);

3 如果主节点的node_id 为 ELECTION_RERUN_NOTIFICATION ,则进行 主库切换 do_primary_failover ,函数返回

4如果主节点的node_id 不是 UNKNOWN_NODE_ID 且 不是 ELECTION_RERUN_NOTIFICATIO,则进行跟随新的主库 ,函数返回

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Patroni是一种开源的工具,用于管理PostgreSQL集群的高可用性。它是一个容器化的解决方案,可以实现自动化的集群管理和故障转移。以下是使用Patroni实现PG数据库高可用的步骤: 1. 安装Patroni 可以使用pip命令安装Patroni: ``` pip install patroni ``` 2. 配置Patroni Patroni的配置文件是YAML格式的,可以根据需要进行修改。以下是一个简单的示例: ``` scope: postgres namespace: /db/ name: pg-cluster restapi: listen: 0.0.0.0:8008 connect_address: $NODE1_IP:8008 etcd: host: $ETCD_IP:2379 bootstrap: dcs: ttl: 30 loop_wait: 10 retry_timeout: 10 maximum_lag_on_failover: 1048576 postgresql: use_pg_rewind: true parameters: max_wal_senders: 10 wal_keep_segments: 10 pg_hba: - host replication replicator 0.0.0.0/0 md5 - host all all 0.0.0.0/0 md5 synchronous_mode: off synchronous_commit: off archive_mode: off archive_command: false recovery_conf: restore_command: cp /var/lib/postgresql/backup/%f %p recovery_target_timeline: latest pgpass: /tmp/pgpass pgpassfile_mode: 600 bin_dir: /usr/lib/postgresql/9.6/bin pg_ctl: /usr/lib/postgresql/9.6/bin/pg_ctl use_slots: true create_replica_methods: - basebackup - pg_rewind ``` 在这个示例中,我们使用etcd作为DCS(分布式协调服务)来管理集群状态。我们还配置了一些PostgreSQL参数,如max_wal_senders和wal_keep_segments。这些参数都可以根据需要进行修改。 3. 启动Patroni 可以使用以下命令启动Patroni: ``` patroni postgres.yml ``` 这将启动一个PostgreSQL集群,并将其注册到etcd中。您可以使用以下命令检查集群状态: ``` curl http://$NODE1_IP:8008/patroni ``` 这将返回一个JSON格式的响应,其中包含有关集群状态的信息。 4. 测试故障转移 为了测试故障转移,您可以杀死主节点上的PostgreSQL进程。Patroni将检测到主节点已经下线,并自动将一个从节点提升为新的主节点。 您可以使用以下命令检查新主节点的状态: ``` curl http://$NODE2_IP:8008/patroni ``` 这将返回有关新主节点的信息。 总的来说,使用Patroni实现PostgreSQL集群的高可用性相对简单。它可以自动管理故障转移,并提供一些其他有用的功能,如DCS和可插拔的备份存储后端。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DBA之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值