postgresql源码学习(30)—— 检查点② - 手动创建检查点函数RequestCheckpoint

       检查点进程的主入口函数是CheckpointerMain,其中CreateCheckPoint这个函数是创建检查点最主要的函数。这两个函数都很复杂,我们留到后面再看。

       本篇先来看一个相对简单的RequestCheckpoint函数。在手动执行checkpoint命令时,调用的就是RequestCheckpoint函数。


一、 RequestCheckpoint函数

       首先判断是否运行在单用户模式下:

  • 如果是,则直接调用CreateCheckPoint函数
  • 不是,则获取自旋锁,保存 CheckpointerShmem中 ckpt_failed,ckpt_started 计数器的当前值(类似于打快照),修改标记位,然后释放自旋锁
/*
 * RequestCheckpoint Called in backend processes to request a checkpoint
 */
void
RequestCheckpoint(int flags)
{
	int			ntries;
	int			old_failed,
				old_started;

	/*
	 * 如果是单用户模式运行,直接调用CreateCheckPoint函数创建检查点
	 */
	if (!IsPostmasterEnvironment)
	{
		CreateCheckPoint(flags | CHECKPOINT_IMMEDIATE);

		/*
		 * 创建检查点后,关闭PG存储介质管理器(smgr)
		 */
		smgrcloseall();

		return;
	}

     // 非单用户模式运行
	/*
	 * 原子级地设置请求标记并给计数器打快照,当看到ckpt_started > old_started时,我们知道设置在这里的标签已经被checkpointer进程看到了。
	 */

// 拿自旋锁,以下为原子级操作
	SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
// 上次checkpoint的失败信息
	old_failed = CheckpointerShmem->ckpt_failed;
// 上次checkpoint的开始位置
	old_started = CheckpointerShmem->ckpt_started;
// 设置checkpoint标记位
	CheckpointerShmem->ckpt_flags |= (flags | CHECKPOINT_REQUESTED);
// 释放自旋锁
	SpinLockRelease(&CheckpointerShmem->ckpt_lck);

       判断检查点进程是否启动。此时检查点进程可能后面启动,也可能正在重启,因此如果有需要的话我们会重试几次(最多重试600次)。在检查点进程启动时,它将会看到这个请求(无论有没有获得信号)。

       如果检查点进程启动了,通过kill函数发送SIGINT信号,判断进程能不能接收。

       SIGINT信号的定义在主函数CheckpointerMain,内容为 pqsignal(SIGINT,ReqCheckpointHandler);

       这里SIGINT信号对应的处理函数是ReqCheckpointHandler函数,它通过SetLatch函数唤醒当前检查点进程(检查点进程正在等待触发消息),并设置checkpoint_requested标记为true,表明本次检查点由RequestCheckpoint函数触发。

#define MAX_SIGNAL_TRIES 600	/* max wait 60.0 sec,600次,每次等0.1秒 */
	for (ntries = 0;; ntries++)
	{
		if (CheckpointerShmem->checkpointer_pid == 0) /* checkpointer进程没启动 */

		{
			if (ntries >= MAX_SIGNAL_TRIES || !(flags & CHECKPOINT_WAIT)) /* 等待超时 */
			{
				elog((flags & CHECKPOINT_WAIT) ? ERROR : LOG,
					 "could not signal for checkpoint: checkpointer is not running");
				break;
			}
		}

/* request checkpoint */
		else if (kill(CheckpointerShmem->checkpointer_pid, SIGINT) != 0)
		{
			if (ntries >= MAX_SIGNAL_TRIES || !(flags & CHECKPOINT_WAIT))
			{
				elog((flags & CHECKPOINT_WAIT) ? ERROR : LOG,
					 "could not signal for checkpoint: %m");
				break;
			}
		}
		else // 检查点进程已启动,且信号发送成功,退出重试循环,进行下一步
			break;				/* signal sent successfully */

		CHECK_FOR_INTERRUPTS();
		pg_usleep(100000L);		/* wait 0.1 sec, then retry,等0.1秒,进入下一次循环 */
	}

       检查点进程已启动,且信号发送成功,则开始等待检查点创建。

       下面这段检查的算法都是一样的:获取自旋锁,对比新旧计数器的值。

  • new_started != old_started,说明新检查点已经开始,计数器值已经增加,退出循环。
  • new_done - new_started >= 0,同理,说明检查点已经完成
  • new_failed != old_failed,同理,说明检查点创建失败
	/*
	 * 检查点进程已启动,且信号发送成功,继续后续操作
	 */
	if (flags & CHECKPOINT_WAIT)
	{
		int			new_started,
					new_failed;

		/* Wait for a new checkpoint to start.等待新检查点开始 */
		ConditionVariablePrepareToSleep(&CheckpointerShmem->start_cv);
		for (;;)
		{
			SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
			new_started = CheckpointerShmem->ckpt_started; // 新检查点开始,计数器+1
			SpinLockRelease(&CheckpointerShmem->ckpt_lck);

			if (new_started != old_started) // 当新旧计数器的值不相等,说明新检查点已经开始,计数器值已经增加,退出循环。如果值相等,则说明新检查点还未开始,继续循环等待。
				break;

			ConditionVariableSleep(&CheckpointerShmem->start_cv,
								   WAIT_EVENT_CHECKPOINT_START);
		}
		ConditionVariableCancelSleep();

		/*
		 * We are waiting for ckpt_done >= new_started. 等待ckpt_done >= new_started,即检查点完成,这个检查算法跟前面是一样的
		 */
		ConditionVariablePrepareToSleep(&CheckpointerShmem->done_cv);
		for (;;)
		{
			int			new_done;

			SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
			new_done = CheckpointerShmem->ckpt_done;
			new_failed = CheckpointerShmem->ckpt_failed;
			SpinLockRelease(&CheckpointerShmem->ckpt_lck);

			if (new_done - new_started >= 0) //检查点完成,退出循环
				break;

			ConditionVariableSleep(&CheckpointerShmem->done_cv,
								   WAIT_EVENT_CHECKPOINT_DONE);
		}
		ConditionVariableCancelSleep();

		if (new_failed != old_failed) //如果fail计数器的值增加了,说明检查点创建失败,记录报错信息
			ereport(ERROR,
					(errmsg("checkpoint request failed"),
					 errhint("Consult recent messages in the server log for details.")));
	}
}

参考

  《PostgreSQL技术内幕:事务处理深度探索》第4章

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hehuyi_In

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

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

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

打赏作者

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

抵扣说明:

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

余额充值