postgresql源码学习(31)—— 检查点③ - 入口函数CheckpointerMain

67 篇文章 52 订阅
14 篇文章 4 订阅

一、 函数代码

CheckpointerMain函数是checkpointer进程的主入口函数。

第一部分主要是些准备工作:

  • 初始化变量
  • 定义、重置一些信号量,包括前面提到的SIGINT
  • 创建专用内存上下文 checkpointer_context
  • 一些异常判断和处理
/*
 * Main entry point for checkpointer process,checkpointer进程的主入口函数
 * 由AuxiliaryProcessMain函数调用,其实早已经创建,只是尚未启用。
 */
void
CheckpointerMain(void)
{
	sigjmp_buf	local_sigjmp_buf; // 主要是跟异常处理有关的,不是我们想看的重点,略过
	MemoryContext checkpointer_context; // 内存上下文

	CheckpointerShmem->checkpointer_pid = MyProcPid;  // 检查点进程pid

	/*
	 * Properly accept or ignore signals the postmaster might send us
	 * 自定义 pg接收或忽略的信号量
	 */
	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* reload配置文件 */
	pqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint,这个就是前文提到的SIGINT,用于请求检查点 */
	pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM,SIG_IGN就是SIG ignore */
	/* SIGQUIT handler was already set up by InitPostmasterChild */
	pqsignal(SIGALRM, SIG_IGN);
	pqsignal(SIGPIPE, SIG_IGN);
	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);

	/*
	 * Reset some signals that are accepted by postmaster but not here,重置一些信号量
	 */
	pqsignal(SIGCHLD, SIG_DFL);

/*
	 * Initialize so that first time-driven event happens at the correct time.初始化上次检查点执行时间点、及上次xlog切换的时间点为当前时间(检查点开始时间)
	 */
	last_checkpoint_time = last_xlog_switch_time = (pg_time_t) time(NULL);

	/*
	创建一块专用的内存上下文,我们的工作都会在其中完成。创建的原因是在遇到recovery报错时我们可以reset上下文,避免可能的内存泄漏问题。以前这个工作都是直接在TopMemoryContext中完成,而重置TopMemoryContext真是一个坏主意。
	 */
	checkpointer_context = AllocSetContextCreate(TopMemoryContext,
												 "Checkpointer",
												 ALLOCSET_DEFAULT_SIZES);
	MemoryContextSwitchTo(checkpointer_context);

//下面这段主要是跟异常处理有关的,不是我们想看的重点,略过
/*
	 * If an exception is encountered, processing resumes here.
	 */
if (sigsetjmp(local_sigjmp_buf, 1) != 0)
……

下面这段很重要

      Checkpointer进程的主流程是一个无条件的for循环,在未触发checkpoint时一直在WaitLatchsleep,也就是在epoll_wait中观察list链表,查看是否有事件句柄已经就绪(某个条件触发checkpoint)。如果已经存在就绪事件,则通过SetLatchwrite pipe的方式唤醒,执行checkpoint

https://i-blog.csdnimg.cn/blog_migrate/2ed2eec15fd044ce353d056c098ad7bb.png

/*
	 * Loop forever,以下内容全都在这个大循环里
	 */
	for (;;)
	{
		bool		do_checkpoint = false; //是否执行checkpoint
		int			flags = 0;
		pg_time_t	now;
		int			elapsed_secs; // 自从上次检查点已过去多长时间
		int			cur_timeout;  // 超时时间倒计时(Latch开始等待的时间距离上次检查点超时的剩余时间)

		// 开头这段对理解主要流程用处不大,略
    
       	// 下面这段很重要,判断自从上次检查点后是否已超时,如果超时则需要创建新检查点
		now = (pg_time_t) time(NULL);
		elapsed_secs = now - last_checkpoint_time; // 自从上次检查点已过去的时间
		if (elapsed_secs >= CheckPointTimeout) //如果elapsed_secs超过了检查点超时时间

		{
			if (!do_checkpoint) // 如果没有请求做checkpoint
				BgWriterStats.m_timed_checkpoints++; // ??
			do_checkpoint = true; // 设置标记位
			flags |= CHECKPOINT_CAUSE_TIME;
		}

		/*
		 * Do a checkpoint if requested.如果收到请求,执行checkpoint
		 */
		if (do_checkpoint)
		{
			bool		ckpt_performed = false; // 标记检查点是否创建成功的标记位
			bool		do_restartpoint; // 判断是不是恢复阶段的检查点

			/*
			 * 检查我们是否需要执行checkpoint或restartpoint。可能的影响是,如还未设置TimeLineID,那么RecoveryInProgress()会初始化TimeLineID
			 */
			do_restartpoint = RecoveryInProgress();

			/*
			 * 加锁,修改CheckpointerShmemStruct
结构体内容。获取标记位,以确认需要执行哪种checkpoint;同时增加ckpt_started计数器,以确认已启动了新的checkpoint
			 */
			SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
			flags |= CheckpointerShmem->ckpt_flags;
			CheckpointerShmem->ckpt_flags = 0;
			CheckpointerShmem->ckpt_started++;
			SpinLockRelease(&CheckpointerShmem->ckpt_lck);

			ConditionVariableBroadcast(&CheckpointerShmem->start_cv);

			/*
			 * The end-of-recovery checkpoint is a real checkpoint that's
			 * performed while we're still in recovery.
			 */
			if (flags & CHECKPOINT_END_OF_RECOVERY)
				do_restartpoint = false;

			/*
			 * 如果本次检查点时间离上次太近,或者在上次检查点启动后某个进程设置了CHECKPOINT_CAUSE_XLOG标志,会产生告警。但注意如果CheckPointTimeout < CheckPointWarning,则不会产生告警。
			 */
			if (!do_restartpoint &&
				(flags & CHECKPOINT_CAUSE_XLOG) &&
				elapsed_secs < CheckPointWarning)
				ereport(LOG,
						(errmsg_plural("checkpoints are occurring too frequently (%d second apart)",
									   "checkpoints are occurring too frequently (%d seconds apart)",
									   elapsed_secs,
									   elapsed_secs),
						 errhint("Consider increasing the configuration parameter \"max_wal_size\".")));

			/*
			 * 初始化在创建检查点期间需要用到的私有变量
			 */
			ckpt_active = true;
			if (do_restartpoint) // restartpoint类型
				ckpt_start_recptr = GetXLogReplayRecPtr(NULL); // 获取XLog恢复位置
			else                 // 普通checkpoint
				ckpt_start_recptr = GetInsertRecPtr();// 获取XLog Record插入位置
			ckpt_start_time = now; // checkpoint开始时间
			ckpt_cached_elapsed = 0; // 自上次checkpoint已过多长时间

			/*
			 * Do the checkpoint.
			 */
			if (!do_restartpoint) // 普通checkpoint
			{
				CreateCheckPoint(flags);
				ckpt_performed = true;
			}
			else  // restartpoint类型
				ckpt_performed = CreateRestartPoint(flags);

			/*
			 * 在完成检查点后,关闭smgr(存储介质管理器)
			 */
			smgrcloseall();

			/*
			 * 告诉其他在等待的进程,本次检查点执行完毕
			 */
			SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
			CheckpointerShmem->ckpt_done = CheckpointerShmem->ckpt_started;
			SpinLockRelease(&CheckpointerShmem->ckpt_lck);

			ConditionVariableBroadcast(&CheckpointerShmem->done_cv);

			if (ckpt_performed) //检查点创建成功
			{
				/*
				 * 还记得在函数的最开始我们把last_checkpoint_time设置成了检查点开始时间而不是结束时间,因此这里我们要修正一下,改为当前时间(检查点结束时间)
				 */
				last_checkpoint_time = now;
			}
			else //检查点创建失败
			{
				/*
				 * 如果这个值为false,说明restartpoint没有创建成功(可能是遇到报错)。最有可能是因为我们没有收到自上次restartpoint以来的WAL记录,在15秒后进行重试
				 */
				last_checkpoint_time = now - CheckPointTimeout + 15; //赋值为15秒后
			}

			ckpt_active = false;
		}

		/* 检查是否超过 archive_timeout,是否需要切换xlog日志 */
		CheckArchiveTimeout();

		/*
		 * 发送活动统计信息到统计信息收集器(这里复用bgwriter-related代码的原因是bgwriter 和 checkpointer以前就是一个进程,不值得将这部分代码拆分成两个独立的统计信息类型)
		 */
		pgstat_send_bgwriter();

		/* 发送WAL统计信息到统计信息收集器 */
		pgstat_send_wal(true);

		/*
		 * 如果设置了任何检查点标记位,重新进行下一次循环去处理检查点而不是sleeping
		 */
		if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->ckpt_flags)
			continue;

		/*
		 * 否则,sleep直到收到对应信号量、或者到达超时时间、或者xlog切换,再进行下一次检查点创建
		 */
		now = (pg_time_t) time(NULL);
		elapsed_secs = now - last_checkpoint_time;
		if (elapsed_secs >= CheckPointTimeout) /* 到达超时时间 */
			continue;			/* no sleep for us ...进入下一次循环,创建检查点 */
		cur_timeout = CheckPointTimeout - elapsed_secs; /* 如果没超时,记录剩余时间 */
		if (XLogArchiveTimeout > 0 && !RecoveryInProgress())/* 根据归档日志的超时参数判断是否需要创建新检查点 */
		{
			elapsed_secs = now - last_xlog_switch_time;
			if (elapsed_secs >= XLogArchiveTimeout) /* 到达日志切换超时时间 */
				continue;		/* no sleep for us ... */
			cur_timeout = Min(cur_timeout, XLogArchiveTimeout - elapsed_secs);
		}

         /* 进行sleep的Latch */
		(void) WaitLatch(MyLatch,
						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
						 cur_timeout * 1000L /* convert to ms */ ,
						 WAIT_EVENT_CHECKPOINTER_MAIN);   /* 休眠… */
	}

二、 代码调试

1. 调试方法

① vscode 设置断点函数 CheckpointerMain

② 跟踪checkpointer进程

ps -ef|grep checkpointer

vscode跟踪5513进程

③ psql执行checkpoint命令

刚进来会在休眠函数位置

④ 找到for循环部分再打一个断点,这部分是函数的重点

⑤ 点击继续按钮,会直接跳到这步,避免在其他函数里一直打转。

2. 调试过程

      判断是不是恢复阶段的检查点

       加锁,修改CheckpointerShmemStruct结构体内容。获取标记位,以确认需要执行哪种checkpoint;同时增加ckpt_started计数器,以确认已启动了新的checkpoint

          点击CheckpointerShmem结构体可以看到其中变量的值,例如ckpt_started:12。

         如果本次检查点时间离上次太近,或者在上次检查点启动后某个进程设置了CHECKPOINT_CAUSE_XLOG标志,会产生告警。这里CheckPointWarning的值为30,所以不符合这个if条件。

       初始化在创建检查点期间需要用到的私有变量,包括checkpoint开始位置、开始时间、自上次checkpoint已过多长时间(这里因为刚开始,所以是0)。

       判断restartpoint类型,若为恢复检查点,则checkpoint开始位置设置为XLog恢复位置;若为普通检查点,则设为XLog Record插入位置。

       若为普通检查点,则通过下一节要介绍的核心函数CreateCheckPoint创建;若为恢复检查点,则通过CreateRestartPoint函数创建。

         ckpt_done加一,表示当前检查点执行完成。

       判断检查点是否创建成功:如果成功,则将last_checkpoint_time设置为当前时间(检查点结束时间);如果失败,等待15秒后重试。

       然后,将ckpt_active设置为false。

  • 检查是否超过 archive_timeout,是否需要切换xlog日志
  • 发送活动统计信息到统计信息收集器
  • 发送WAL统计信息到统计信息收集器

          如果设置了任何检查点标记位,重新进行下一次循环去处理检查点而不是sleep。

          否则,sleep直到收到对应信号量、或者到达超时时间、或者xlog切换,再进行下一次检查点创建。

参考

postgresql checkpoint原理浅析-杨向博.pdf - 墨天轮文档

http://blog.itpub.net/6906/viewspace-2374762/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hehuyi_In

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

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

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

打赏作者

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

抵扣说明:

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

余额充值