PostgreSQL Keep-Alive 内置实现方案简介

PostgreSQL Keep-Alive 内置实现方案简介

文一

2024-09-16 发布50 浏览 · 0 点赞 · 0 收藏

PostgreSQL Keep-Alive 内置实现方案简介

我们只有将点点滴滴的工作扎实做好,才可以真正成为一流的数据库内核工程师,我们期待更多的人参与到“数据库内核一周一审”的工作之中,各自贡献扎实的技术材料,真正繁荣起中国数据库内核研发的生态来。

Keep-Alive 是什么?

keep-alive

Keep-Alive 实际上就是一种确保连接通畅的机制(这就像我们在很多特定的节日会去祝贺我们的朋友,老师乃至于同事一样,实际上就是一种对于彼此关系的确认方式,区别在于,节日的时间间隔是不固定的,而 Keep-Alive 数据包的发送间隔则往往一致)。

而在 PostgreSQL 中,因为客户端与服务端的连接要么架构于 Unix-domain socket 上面(这是一种供本地进程通信使用的机制,可以理解为 PostgreSQL 本地版所使用),要么架构于 TCP/IP 协议上面,而 Keep-Alive 往往并不需要在 Unix-domain socket 上面使用(因为本机之间的自家进程通信,除非操作系统或者应用程序自身出现故障,否则一般是不会断开联系的),因此我们只需要关系 TCP/IP 下面的 Keep-Alive 即可。

好的,在建立了这种理解的基础之上,让我们做一个简单的思考,参考下图:

network

TCP 协议是一种传输层协议,架构于网络层协议之上,它在封装原始数据包的同时,提供了按序传输机制、重传机制等能力,具体的实现思路便是在每一款数据包中加入序号(代表发送的顺序),乃至于时间等内容,做一个简单的思考:

  1. 因为我们了解了数据包在整体数据中的所处位置,因此我们可以按照顺序要求将其放置于正确的地方上,这就是实现了有序的传输
  2. 因为我们了解了数据包所发送的时间有关信息,因此我们可以用此估算网络的传输速度,控制流量,并且设置一个合理的重传时间(重传的原理也非常简单,当我们在某个时间限制内没有接收到一个预期中的数据包之后,我们将会要求对方重新传输数据包,因为序号的存在,所以即使我们接收到了序号一样的内容,也不会出现很大的问题。而 CRC32 校验的存在,也为同序列号不同内容,加上了一道保险杠)

而 Keep-Alive 的本质,正如我们之前所说,是为了确认双方连接的顺畅性,而设立的一个根据固定的时间间隔,定期相互发送数据包的机制,而结合起 TCP/IP 的这种特点,它的实现实际上也变得简单:我们可以直接借助 TCP/IP 来加以实现(PostgreSQL 就是这么做的)。

PostgreSQL 依托 TCP/IP 协议 以实现 Keep-Alive 的办法

在文档 https://www.postgresql.org/docs/current/runtime-config-connection.html 中,PostgreSQL 详细阐述了自身连接所牵涉到的内容,而与 Keep-Alive 有关的部分,则参考为如下:

  • tcp_keepalives_idle 如果在时间间隔期间,PostgreSQL 服务端与客户端存在通信的话,那么证明连接就是畅通的,在这种情况下,实际上并不存在发送“心跳检测包”(Keep-Alive)用以验证连接可用的必要性。 因此这个参数的意义便是,在服务端与客户端相互之间没有发送数据超过这个时间限制之后,PostgreSQL 将会发送一个“心跳检测包”(Keep-Alive),用以验证连接的可用。
  • tcp_keepalives_interval 网路传输并不可靠,因此存在连接畅通但是丢失数据包的可能性,这种时候,TCP 为我们设计了重传机制,即我们可以在一个时间期限到达之后,重新传输没有得到反馈的数据包(TCP 协议要求当对方接收到数据后,就要向我们反馈接收到了数据,这就像我们网购的确认,当我们接收不到包裹,商家就要重新发放商品,一个道理)。 因此这个参数约定了重传的触发时间期限。
  • tcp_keepalives_count 如果很不幸的,重传也没能将数据包送达,但是连接又是畅通的(这种时候往往是因为网络的传输速度很慢,就像我们玩游戏时网卡导致的很多“诡异”情况一样),我们可以适当地容忍一些数据的丢失。 因此这个参数规定了我们能够容忍的数据丢失量。

在建立了这种基本的了解之后,让我们把目光聚焦于具体的代码实现之上,请参考如下的内容:

guc-keep-alive

(三个参数在 GUC 系统中所处的位置,GUC 是 PostgreSQL 参数子系统的代称)

/* src/backend/libpq/pqcomm.c */
/* 负责 PostgreSQL 连接的初始化工作 */
Port *
pq_init(ClientSocket *client_sock)
{
    /* 用于代指端口 */
	Port	   *port;
    /* ... */
		/*
		 * Also apply the current keepalive parameters.  If we fail to set a
		 * parameter, don't error out, because these aren't universally
		 * supported.  (Note: you might think we need to reset the GUC
		 * variables to 0 in such a case, but it's not necessary because the
		 * show hooks for these variables report the truth anyway.)
		 */
         /* 配置有关 Keep-Alive 的参数,最后一个代表 TCP 连接的超时时间  */
		(void) pq_setkeepalivesidle(tcp_keepalives_idle, port);
		(void) pq_setkeepalivesinterval(tcp_keepalives_interval, port);
		(void) pq_setkeepalivescount(tcp_keepalives_count, port);
		(void) pq_settcpusertimeout(tcp_user_timeout, port);
    /* ... */
}

因为 TCP/IP 有关的操作系统 API 实际上都围绕于以“连接”为核心的内容展开,因此只需要我们对其中一个参数的设置方式建立了解,其它的自然而然就触类旁通,在这里,我们选择 pq_setkeepalivesidle 函数,参考下面的内容:

/* 设置 Keep-Alive 下面的在无数据数据传输情况下,发送数据包的间隔时间 */
int
pq_setkeepalivesidle(int idle, Port *port)
{
    /* Unix Sock Domain 为代表的本地通信,不需要这种机制的帮助 */
	if (port == NULL || port->laddr.addr.ss_family == AF_UNIX)
		return STATUS_OK;

/* 判断 Linux/Windows 平台下的 TCP 相关 API 是否支持 Keep-Alive 机制 */
/* check SIO_KEEPALIVE_VALS here, not just WIN32, as some toolchains lack it */
#if defined(PG_TCP_KEEPALIVE_IDLE) || defined(SIO_KEEPALIVE_VALS)
	if (idle == port->keepalives_idle)
		return STATUS_OK;

/* Linux 平台下的实现方案 */
#ifndef WIN32
    /*   */
	if (port->default_keepalives_idle <= 0)
	{
        /* 
            = 0 代表为本地通信或者是已经配置相关设置,但是数据不清楚
            这种时候直接获取数据即可 
        */
		if (pq_getkeepalivesidle(port) < 0)
		{
			if (idle == 0)
				return STATUS_OK;	/* default is set but unknown */
			else                    /* 若依旧 <0,代表工作存在错误    */
				return STATUS_ERROR;
		}
	}

	if (idle == 0)
		idle = port->default_keepalives_idle;

    /* 根据传入参数展开相关设置工作,依托 setsockopt 函数进行 */
	if (setsockopt(port->sock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE,
				   (char *) &idle, sizeof(idle)) < 0)
	{
		ereport(LOG,
				(errmsg("%s(%s) failed: %m", "setsockopt", PG_TCP_KEEPALIVE_IDLE_STR)));
		return STATUS_ERROR;
	}

	port->keepalives_idle = idle; /* 更新 PostgreSQL 系统内部记录 */
#else							/* WIN32 */
	return pq_setkeepaliveswin32(port, idle, port->keepalives_interval);
#endif
#else /* 对应操作系统平台并不支持内置的 Keep-Alive 机制 */
	if (idle != 0)
	{
		ereport(LOG,
				(errmsg("setting the keepalive idle time is not supported")));
		return STATUS_ERROR;
	}
#endif

	return STATUS_OK;
}

因为本质上就是依据操作系统提供的 API 的内置能力展开工作,因此我们只需要对相应的 API 中的一种建立了解即可,参考下面的内容。

setsockopt 函数简介

这个函数是一个 Linux 系统平台的 API 函数,可以用来设置连接的有关内容,在文档 https://linux.die.net/man/3/setsockopt 中,我们可以看到 SO_KEEPALIVE 选项,它可以使得连接启动周期性的数据包确认连接畅通机制(Keep-Alive),同时下面的三个参数,则分别对应 PostgreSQL 上面的三个内容(请参考 https://linux.die.net/man/7/tcp):

tcp-keep-alive

换而言之,这个函数的重点在于了解这些配套的参数,至于使用的办法,可以参考前文中的 PostgreSQL 代码,我们这里不再过多阐述。

写在这里

祝各位一切工作顺利,感谢所有曾经提供给我帮助的人,谢谢你们!