PostgreSQL的学习心得和知识总结(一百六十四)|深入理解PostgreSQL数据库之在 libpq 中支持负载平衡


注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于PostgreSQL 17.0源码开发而成


深入理解PostgreSQL数据库之在 libpq 中支持负载平衡

  • 文章快速说明索引
  • 功能使用背景说明
    • 使用 psql 检查
    • 使用 pgbench 检查
  • 功能使用源码解析



文章快速说明索引

学习目标:

做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。


学习内容:(详见目录)

1、深入理解PostgreSQL数据库之在 libpq 中支持负载平衡


学习时间:

2024年12月12日 20:51:45


学习产出:

1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习


注:下面我们所有的学习环境是Centos8+PostgreSQL master+Oracle19C+MySQL8.0

postgres=# select version();
                                   version                                    
------------------------------------------------------------------------------
 PostgreSQL 17.0 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.1.0, 64-bit
(1 row)

postgres=#

#-----------------------------------------------------------------------------#

SQL> select * from v$version;          

BANNER        Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
BANNER_FULL	  Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0	
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
CON_ID 0


#-----------------------------------------------------------------------------#

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.06 sec)

mysql>

功能使用背景说明

在连接期间指定多个 PostgreSQL 实例的功能并不新鲜。您可以在连接字符串中的 hosthostaddrport 参数下列出多个副本。客户端将尝试按指定的顺序连接到副本。

psql "host=replica1,replica2,replica3"

但是,如果连接数量很多,列表中的第一个副本将比其他副本承受更多的负载,而最后一个副本可能完全处于空闲状态。为了在副本之间均匀分配连接,您可以在形成连接字符串时在应用程序端对副本列表进行打乱。

或者您可以使用新的连接参数 load_balance_hosts,也就是我们今天要学习的重点 如下:

psql "host=replica1,replica2,replica3 load_balance_hosts=random"

其中load_balance_hosts=random 表示在尝试连接之前将对节点列表进行打乱。


load_balance_hosts 已添加到 PostgreSQL 客户端库 libpq 的连接字符串。它当前支持以下两个值:

说明
disable主机之间没有负载平衡。主机按照提供的顺序进行尝试,地址按照从 DNS 或主机文件接收的顺序进行尝试
random以随机顺序尝试主机或地址。此方法允许在多个 PostgreSQL 服务器之间实现连接负载平衡

注:其默认值为禁用。 PostgreSQL 15 之前的行为也与禁用相同。


提交记录如下:

在这里插入图片描述

提交信息,如下:

* Support connection load balancing in libpq

This adds support for load balancing connections with libpq using a
connection parameter: load_balance_hosts=<string>. When setting the
param to random, hosts and addresses will be connected to in random
order. This then results in load balancing across these addresses and
hosts when multiple clients or frequent connection setups are used.

The randomization employed performs two levels of shuffling:

  1. The given hosts are randomly shuffled, before resolving them
     one-by-one.
  2. Once a host its addresses get resolved, the returned addresses
     are shuffled, before trying to connect to them one-by-one.

Author: Jelte Fennema <postgres@jeltef.nl>
Reviewed-by: Aleksander Alekseev <aleksander@timescale.com>
Reviewed-by: Michael Banck <mbanck@gmx.net>
Reviewed-by: Andrey Borodin <amborodin86@gmail.com>
Discussion: https://postgr.es/m/PR3PR83MB04768E2FF04818EEB2179949F7A69@PR3PR83MB0476.EURPRD83.prod.outlook.

翻译一下,如下:

在 libpq 中支持连接负载平衡

  • 这增加了使用连接参数 load_balance_hosts=<string> 实现 libpq 负载平衡连接的支持。将参数设置为random时,主机和地址将以随机顺序连接。当使用多个客户端或频繁连接设置时,这会使得这些地址和主机之间的负载平衡
  • 所采用的随机化执行两个级别的改组:
    • 给定的主机被随机改组,然后逐一解析它们
    • 一旦主机的地址得到解析,返回的地址将被改组,然后尝试逐一连接它们。

该功能的价值在于:

  • 将此功能添加到 libpq 意味着使用 libpq 的客户端应用程序可以从中受益
  • 标准PostgreSQL包中提供的客户端应用程序使用libpq,因此 无需修改 客户端应用程序端的负载均衡功能即可使用负载均衡功能
  • 尽管可能只有 psql 和 pgbench 是最有用的

接下来,我们看一下该功能的使用案例,如下:

在这里插入图片描述


使用 psql 检查

当使用 psql 连接时,通常会显式指定 --host--port--username 等选项,但实际上可以传递连接字符串作为数据库名称。以下是通过传递连接字符串作为数据库名称进行连接的示例:

[postgres@localhost:~/test/bin]$ ./psql 'port=5432 dbname=postgres host=localhost'
psql (17.0)
Type "help" for help.

postgres=# \q
[postgres@localhost:~/test/bin]$ ./psql 'port=5433 dbname=postgres host=localhost'
psql (17.0)
Type "help" for help.

postgres=# \q
[postgres@localhost:~/test/bin]$ ./psql 'port=5434 dbname=postgres host=localhost'
psql (17.0)
Type "help" for help.

postgres=# \q
[postgres@localhost:~/test/bin]$

因此,新增加的负载均衡功能也可以作为连接字符串给出。(因此,即使 psql 端没有负载平衡选项,它也可以工作)

下面是随机连接到验证环境中显示的三个data并使用 SHOW 命令显示连接目的地的 PostgreSQL 参数端口的示例。要指定的连接字符串的内容如下:

关键词说明
hostlocalhost,localhost,localhost这次,所有三个节点都在同一机器上创建,因此为所有节点指定相同的 localhost
port5432,5433,5434这次,三个节点设置为不同的端口。您需要匹配主机数量和逗号列表
load_balance_hostsrandom如果将其指定为随机,则负载将得到平衡
dbnamepostgres还可以指定数据库名称列表,但在这种情况下只能指定一个。
在这种情况下,无论连接到哪个端口,连接的数据库名称都是postgres

测试如下:

在这里插入图片描述

如上,通过连接到数据库集群并输出其端口号设置,可以看到连接目的地是随机选择的。


如果指定主机/端口上的数据库集群已停止,测试如下:

正常情况,如下:

在这里插入图片描述

我们这里把log_connections = on都设置上,如下:

在这里插入图片描述


异常情况,(停止使用 port=5433连接的数据库集群,并尝试使用与之前相同的连接字符串进行连接)如下:

在这里插入图片描述

在这里插入图片描述

在这种情况下,即使选择已停止的数据库集群也不会发生错误。这是因为当指定 load_balance_hosts=random 时,会以随机顺序尝试连接,而不是从列表中随机选择。例如,如果列表中有一个主机/端口已停止的数据库集群,并且首先选择它(如果立即发生连接错误),则将尝试使用下一个列表进行连接。


接下来,我们仅启动data1,进行调试:

在这里插入图片描述

开始,如下:

在这里插入图片描述

第一次尝试,如下:

在这里插入图片描述

在这里插入图片描述

再次尝试,如下:

在这里插入图片描述

同样此时conn->try_next_addr = true;,继续:

在这里插入图片描述

继续尝试,如下(这次端口号就是5432了):

在这里插入图片描述

注1:在我的调试中,有时候一次就成功,有时候则需要2次或3次。
注2:第三次尝试就成功了,虽然上面有errormessage,但是可以忽略不计,如下:

在这里插入图片描述

注:那么,敏锐的人可能已经注意到一个问题:在连接尝试时,如果无法在 TCP/IP 层连接,直到超时才会返回错误,这种情况下需要如何处理呢?因此,在指定 load_balance_hosts 时(此时可能会在 host 和 port 中指定多个主机和端口),最好将 connect_timeout 的设置也包含在连接字符串中。


使用 pgbench 检查

正如 psql 验证中所解释的,任何使用 libpq 的应用程序都可以从此 load_balance_hosts 中受益。由于 pgbench 也使用 libpq,那么是否可以在不使用另一个负载均衡器的情况下将处理分发到多个数据库服务器?

作为PostgreSQL的pgbench本身的一个选项,指定负载均衡和指定多主机的选项并没有显式的写出来,但是pgbench以和psql相同的方式指定数据库名称,所以连接字符的使用方式是不是也可以?

首先,使用pgbench的初始化模式初始化这三个端口的每个数据库集群(所有数据库名称均保持如上):

在这里插入图片描述

让我们为这三个数据库集群运行一个 pgbench 并随机分配处理。pgbench 的执行选项如下:

选项说明
-btpcb-like默认交易。一个条目也被插入到 pgbench_history 中
-C/每笔交易都会建立一个连接
-c2将同时连接数设置为2
-t500每个连接执行 500 个事务。这次,同时连接数 = 2,因此总共将执行 1000 个事务
数据库名称'host=localhost,localhost,localhost port=5432,5433,5434 load_balance_hosts=random dbname=postgres'在此指定 load_balance_hosts

让我们使用此设置运行 pgbench,如下:

[postgres@localhost:~/test/bin]$ ./pgbench -b tpcb-like -C -c 2 -t 500 'host=localhost,localhost,localhost port=5432,5433,5434 load_balance_hosts=random dbname=postgres'
pgbench (17.0)
starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 10
query mode: simple
number of clients: 2
number of threads: 1
maximum number of tries: 1
number of transactions per client: 500
number of transactions actually processed: 1000/1000
number of failed transactions: 0 (0.000%)
latency average = 7.383 ms
average connection time = 2.446 ms
tps = 270.897508 (including reconnection times)
[postgres@localhost:~/test/bin]$

虽然VACUUM是先执行的,但并不知道这个VACUUM是针对哪个数据库集群的postgres执行的。实际测量时,VACUUM本身必须为每个数据库集群单独执行,最好提前运行它并使用-n, --no-vacuum运行 pgbench 时。

-n, --no-vacuum          do not run VACUUM before tests

现在 pgbench 已完成运行,让我们看看每个数据库集群的 pgbench_history 条目数,如下:

[postgres@localhost:~/test/bin]$ ./psql -t -p 5432 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   300

[postgres@localhost:~/test/bin]$ ./psql -t -p 5433 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   341

[postgres@localhost:~/test/bin]$ ./psql -t -p 5434 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   359

[postgres@localhost:~/test/bin]$

清理一下,再来一遍(2 * 1000):

[postgres@localhost:~/test/bin]$ ./pgbench -p 5432 -i -s 10 --unlogged-table -q postgres
dropping old tables...
creating tables...
generating data (client-side)...
vacuuming...
creating primary keys...
done in 2.22 s (drop tables 0.01 s, create tables 0.00 s, client-side generate 1.24 s, vacuum 0.24 s, primary keys 0.72 s).
[postgres@localhost:~/test/bin]$ ./pgbench -p 5433 -i -s 10 --unlogged-table -q postgres
dropping old tables...
creating tables...
generating data (client-side)...
vacuuming...
creating primary keys...
done in 1.90 s (drop tables 0.01 s, create tables 0.00 s, client-side generate 1.21 s, vacuum 0.24 s, primary keys 0.43 s).
[postgres@localhost:~/test/bin]$ ./pgbench -p 5434 -i -s 10 --unlogged-table -q postgres
dropping old tables...
creating tables...
generating data (client-side)...
vacuuming...
creating primary keys...
done in 1.88 s (drop tables 0.02 s, create tables 0.00 s, client-side generate 1.20 s, vacuum 0.23 s, primary keys 0.44 s).
[postgres@localhost:~/test/bin]$
[postgres@localhost:~/test/bin]$ ./vacuumdb -p 5432 -d postgres
vacuumdb: vacuuming database "postgres"
[postgres@localhost:~/test/bin]$ ./vacuumdb -p 5433 -d postgres
vacuumdb: vacuuming database "postgres"
[postgres@localhost:~/test/bin]$ ./vacuumdb -p 5434 -d postgres
vacuumdb: vacuuming database "postgres"
[postgres@localhost:~/test/bin]$
[postgres@localhost:~/test/bin]$ ./psql -t -p 5432 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
     0

[postgres@localhost:~/test/bin]$ ./psql -t -p 5433 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
     0

[postgres@localhost:~/test/bin]$ ./psql -t -p 5434 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
     0

[postgres@localhost:~/test/bin]$
[postgres@localhost:~/test/bin]$ ./pgbench -n -b tpcb-like -C -c 2 -t 1000 'host=localhost,localhost,localhost port=5432,5433,5434 load_balance_hosts=random dbname=postgres'
pgbench (17.0)
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 10
query mode: simple
number of clients: 2
number of threads: 1
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 2000/2000
number of failed transactions: 0 (0.000%)
latency average = 7.924 ms
average connection time = 2.612 ms
tps = 252.392714 (including reconnection times)
[postgres@localhost:~/test/bin]$
[postgres@localhost:~/test/bin]$ ./psql -t -p 5432 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   662

[postgres@localhost:~/test/bin]$ ./psql -t -p 5433 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   672

[postgres@localhost:~/test/bin]$ ./psql -t -p 5434 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   666

[postgres@localhost:~/test/bin]$

当然还可以 能者多劳,如下:

[postgres@localhost:~/test/bin]$ ./pgbench -n -b tpcb-like -C -c 2 -t 1000 'host=localhost,localhost,localhost,localhost port=5432,5433,5434,5433 load_balance_hosts=random dbname=postgres'
pgbench (17.0)
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 10
query mode: simple
number of clients: 2
number of threads: 1
maximum number of tries: 1
number of transactions per client: 1000
number of transactions actually processed: 2000/2000
number of failed transactions: 0 (0.000%)
latency average = 7.319 ms
average connection time = 2.428 ms
tps = 273.276011 (including reconnection times)
[postgres@localhost:~/test/bin]$
[postgres@localhost:~/test/bin]$
[postgres@localhost:~/test/bin]$ ./psql -t -p 5432 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   525

[postgres@localhost:~/test/bin]$ ./psql -t -p 5433 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   981

[postgres@localhost:~/test/bin]$ ./psql -t -p 5434 -d postgres -c "SELECT COUNT(*) FROM pgbench_history"
   494

[postgres@localhost:~/test/bin]$

功能使用源码解析

官方手册解释,如下:

load_balance_hosts:控制客户端尝试连接到可用主机和地址的顺序。连接尝试成功后,将不再尝试其他主机和地址。此参数通常与多个主机名或返回多个 IP 的 DNS 记录结合使用。此参数可以与 target_session_attrs 结合使用,例如,仅在备用服务器上进行负载平衡。成功连接后,返回连接的后续查询将全部发送到同一服务器。目前有两种模式:

  • disable(默认):不执行跨主机的负载平衡。主机按提供顺序尝试,地址按从 DNS 或主机文件接收的顺序尝试。
  • random:主机和地址按随机顺序尝试。此值主要用于同时打开多个连接(可能来自不同的计算机)。这样,连接可以在多个 PostgreSQL 服务器之间进行负载平衡。虽然随机负载平衡由于其随机性,几乎永远不会导致完全均匀的分布,但从统计上看,它非常接近。这里的一个重要方面是,该算法使用两级随机选择(下面我们会详细解释):首先,主机将以随机顺序解析。其次,在解析下一个主机之前,将以随机顺序尝试当前主机的所有已解析地址。在某些情况下,这种行为可能会极大地扭曲每个节点获得的连接数量,例如当某些主机解析到比其他主机更多的地址时。但这种偏差也可以故意使用,例如通过在主机字符串中多次提供主机名来增加大型服务器获得的连接数量。使用此值时,建议还为 connect_timeout 配置一个合理的值。因为这样,如果用于负载平衡的节点之一没有响应,就会尝试一个新节点。

该功能的源码非常简单,下面重点看一下核心部分:

// src/interfaces/libpq/libpq-int.h

/* Target server type (decoded value of load_balance_hosts) */
typedef enum
{
	LOAD_BALANCE_DISABLE = 0,	/* Use the existing host order (default) */
	LOAD_BALANCE_RANDOM,		/* Randomly shuffle the hosts */
} PGLoadBalanceType;

如上,两种选择 核心在于random,如下:

// src/interfaces/libpq/fe-connect.c

/*
 *		pqConnectOptions2
 *
 * Compute derived connection options after absorbing all user-supplied info.
 * 吸收所有用户提供的信息后计算派生的连接选项
 *
 * Returns true if OK, false if trouble (in which case errorMessage is set
 * and so is conn->status).
 * 如果成功则返回 true,如果出现问题则返回 false(这种情况下会设置 errorMessage 并且 conn->status 也会设置)。
 */
bool
pqConnectOptions2(PGconn *conn)
{
	...
	if (conn->load_balance_type == LOAD_BALANCE_RANDOM)
	{
		libpq_prng_init(conn);

		/*
		 * This is the "inside-out" variant of the Fisher-Yates shuffle
		 * algorithm. Notionally, we append each new value to the array and
		 * then swap it with a randomly-chosen array element (possibly
		 * including itself, else we fail to generate permutations with the
		 * last integer last).  The swap step can be optimized by combining it
		 * with the insertion.
		 * 这是 Fisher-Yates 洗牌算法的“由内而外”变体。
		 * 理论上,我们将每个新值附加到数组中,然后将其与随机选择的数组元素交换(可能包括其自身,否则我们无法生成最后一个整数的排列)。
		 * 交换步骤可以通过将其与插入相结合来优化。
		 */
		for (i = 1; i < conn->nconnhost; i++)
		{
			int			j = pg_prng_uint64_range(&conn->prng_state, 0, i);
			pg_conn_host temp = conn->connhost[j];

			conn->connhost[j] = conn->connhost[i];
			conn->connhost[i] = temp;
		}
	}
	...
}
// src/interfaces/libpq/fe-connect.c

/* ----------------
 *		PQconnectPoll
 *
 * Poll an asynchronous connection.
 * 轮询异步连接
 *
 * Returns a PostgresPollingStatusType.
 * Before calling this function, use select(2) to determine when data
 * has arrived..
 * 在调用此函数之前,使用 select(2) 来确定数据何时到达。
 *
 * You must call PQfinish whether or not this fails.
 *
 * This function and PQconnectStart are intended to allow connections to be
 * made without blocking the execution of your program on remote I/O. However,
 * there are a number of caveats:
 * 此函数和 PQconnectStart 旨在允许建立连接而不阻塞程序在远程 I/O 上的执行。
 * 但是,有许多注意事项:
 *
 *	 o	If you call PQtrace, ensure that the stream object into which you trace
 *		will not block.
 *	    如果调用 PQtrace,请确保您跟踪的流对象不会阻塞。
 *	 o	If you do not supply an IP address for the remote host (i.e. you
 *		supply a host name instead) then PQconnectStart will block on
 *		getaddrinfo.  You will be fine if using Unix sockets (i.e. by
 *		supplying neither a host name nor a host address).
 *		如果您不提供远程主机的 IP 地址(即您提供的是主机名),那么 PQconnectStart 将在 getaddrinfo 上阻塞。
 *		如果使用 Unix 套接字(即不提供主机名和主机地址),则不会有问题。
 *	 o	If your backend wants to use Kerberos authentication then you must
 *		supply both a host name and a host address, otherwise this function
 *		may block on gethostname.
 *		如果您的后端想要使用 Kerberos 身份验证,那么您必须提供主机名和主机地址,否则此功能可能会在 gethostname 上阻止。
 *
 * ----------------
 */
PostgresPollingStatusType
PQconnectPoll(PGconn *conn)
{
	...
	/* Time to advance to next address, or next host if no more addresses? */
	// 是否需要前进到下一个地址,或者如果没有更多地址则前进到下一个主机?
	if (conn->try_next_addr)
	{
		if (conn->whichaddr < conn->naddr)
		{
			conn->whichaddr++;
			reset_connection_state_machine = true;
		}
		else
			conn->try_next_host = true;
		conn->try_next_addr = false;
	}

	/* Time to advance to next connhost[] entry? */
	// 是时候前进到下一个 connhost[] 条目了吗?
	if (conn->try_next_host)
	{
		...
		/*
		 * If random load balancing is enabled we shuffle the addresses.
		 * 如果启用了随机负载平衡,我们就会打乱地址。
		 */
		if (conn->load_balance_type == LOAD_BALANCE_RANDOM)
		{
			/*
			 * This is the "inside-out" variant of the Fisher-Yates shuffle
			 * algorithm. Notionally, we append each new value to the array
			 * and then swap it with a randomly-chosen array element (possibly
			 * including itself, else we fail to generate permutations with
			 * the last integer last).  The swap step can be optimized by
			 * combining it with the insertion.
			 * 这是 Fisher-Yates 洗牌算法的“由内而外”变体。
			 * 理论上,我们将每个新值附加到数组中,然后将其与随机选择的数组元素交换(可能包括其自身,否则我们无法生成最后一个整数的排列)。
			 * 交换步骤可以通过将其与插入相结合来优化。
			 *
			 * We don't need to initialize conn->prng_state here, because that
			 * already happened in pqConnectOptions2.
			 * 我们不需要在这里初始化 conn->prng_state,因为这已经在 pqConnectOptions2 中发生了。
			 */
			for (int i = 1; i < conn->naddr; i++)
			{
				int			j = pg_prng_uint64_range(&conn->prng_state, 0, i);
				AddrInfo	temp = conn->addr[j];

				conn->addr[j] = conn->addr[i];
				conn->addr[i] = temp;
			}
		}

		reset_connection_state_machine = true;
		conn->try_next_host = false;
		...
	}
	...
}

下面我们来调试一下,(配置还是和之前一样),如下:

        {
            "name": "(gdb) 启动",
            "type": "cppdbg",
            "request": "launch",
            "program": "/home/postgres/test/bin/psql",
            "args": ["host=localhost,localhost,localhost port=5432,5433,5434 load_balance_hosts=random dbname=postgres"],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        }

看一下本机(此次三个data是在一台机器上,区别仅port不同)的host配置,如下:

[postgres@localhost:~/postgres → REL_17_0]$ cat /etc/hosts 
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
[postgres@localhost:~/postgres → REL_17_0]$

如上,说明一下:

  • 第一行是针对 IPv4 环境的主机名绑定,127.0.0.1 是 IPv4 回环地址。
  • 第二行是针对 IPv6 环境的主机名绑定,::1 是 IPv6 回环地址。
  • 通过这两行配置,系统和应用程序可以在 IPv4 和 IPv6 两种网络环境中,均能正确解析 localhost。

开始调试,如下:

在这里插入图片描述

调整顺序之前,host的排序就是初始设置的!

在这里插入图片描述

如上,经过调整之后的host顺序就是5433 5432 5434,此时的函数堆栈,如下:

libpq.so.5!pqConnectOptions2(PGconn * conn) (\home\postgres\postgres\src\interfaces\libpq\fe-connect.c:1840)
libpq.so.5!PQconnectStartParams(const char * const * keywords, const char * const * values, int expand_dbname) (\home\postgres\postgres\src\interfaces\libpq\fe-connect.c:837)
libpq.so.5!PQconnectdbParams(const char * const * keywords, const char * const * values, int expand_dbname) (\home\postgres\postgres\src\interfaces\libpq\fe-connect.c:693)
main(int argc, char ** argv) (\home\postgres\postgres\src\bin\psql\startup.c:272)

然后进入pqConnectDBStart函数,开始相关初始化,如下:

在这里插入图片描述

...
	/*
	 * Set up to try to connect to the first host.  (Setting whichhost = -1 is
	 * a bit of a cheat, but PQconnectPoll will advance it to 0 before
	 * anything else looks at it.)
	 * 设置尝试连接到第一台主机。
	 * 设置 whichhost = -1 有点作弊,但是 PQconnectPoll 会在任何其他东西查看它之前将其推进到 0
	 *
	 * Cancel requests are special though, they should only try one host and
	 * address, and these fields have already been set up in PQcancelCreate,
	 * so leave these fields alone for cancel requests.
	 * 但是取消请求很特殊,它们应该只尝试一个主机和地址,并且这些字段已经在 PQcancelCreate 中设置,因此对于取消请求,请保留这些字段。
	 */
	if (!conn->cancelRequest)
	{
		conn->whichhost = -1;
		conn->try_next_host = true;
		conn->try_next_addr = false;
	}
...

然后进入PQconnectPoll,选择host如下:

在这里插入图片描述

此时conn->whichhost = 0,于是选择的host就是上面的localhost 5433了。

然后就到了IP的重新排序这里,初始状态如下:

在这里插入图片描述

在网络编程中,address family(地址族) 用于指定套接字(socket)的协议族,即网络通信使用的地址类型。AF_INETAF_INET6 是常见的地址族,而它们的数字表示在底层代码中也被用到。以下是 address family 102 的含义:

对比总结

Address Family数字表示含义地址格式应用场景
AF_INET2IPv4 地址族点分十进制(x.x.x.xIPv4 网络通信
AF_INET610IPv6 地址族十六进制(x:x::x:xIPv6 网络通信

扩展信息
其他常见的 address family

  • AF_UNIX (1):本地 Unix 套接字,用于同一台机器上进程间通信。
  • AF_PACKET (17):用于底层网络接口的原始套接字(主要在 Linux 中)。

经过调整之后,顺序没有发生变化,如下:

libpq.so.5!PQconnectPoll(PGconn * conn) (\home\postgres\postgres\src\interfaces\libpq\fe-connect.c:2998)
libpq.so.5!pqConnectDBStart(PGconn * conn) (\home\postgres\postgres\src\interfaces\libpq\fe-connect.c:2446)
libpq.so.5!PQconnectStartParams(const char * const * keywords, const char * const * values, int expand_dbname) (\home\postgres\postgres\src\interfaces\libpq\fe-connect.c:843)
libpq.so.5!PQconnectdbParams(const char * const * keywords, const char * const * values, int expand_dbname) (\home\postgres\postgres\src\interfaces\libpq\fe-connect.c:693)
main(int argc, char ** argv) (\home\postgres\postgres\src\bin\psql\startup.c:272)

在这里插入图片描述

那么这个顺序下,优先选择的conn->connip,如下:

在这里插入图片描述

于是localhost + IPV6 + 5433,这样是无法建立连接的,如下:

在这里插入图片描述

然后再试一下另一个IP,如下:

在这里插入图片描述

在这里插入图片描述

当然我们知道这个也是无法建立连接的,如下:

在这里插入图片描述


然后接下来需要更换主机,也就是localhost 5432了(这里比较幸运,若是5434 那么久的重复一遍上面两次重试),如下:

在这里插入图片描述

此时conn->whichhost = 1,然后又到了对它的IP重排序的地方了,如下:

在这里插入图片描述

其初始的顺序也是10 和 2,这里也非常幸运 排完序之后就是2 和 10。这样我们直接IPV4就连接成功了,如下:

在这里插入图片描述

于是这次直接就成功了,下面是的留存,如下:

// &conn->errorMessage

0x6c7bb0 "
connection to server at \"localhost\" (::1), port 5433 failed: 拒绝连接\n\tIs the server running on that host and accepting TCP/IP connections?\n

connection to server at \"localhost\" (127.0.0.1), port 5433 failed: 拒绝连接\n\tIs the server running on that host and accepting TCP/IP connections?\n

connection to server at \"localhost\" (127.0.0.1), port 5432 failed: "

在这里插入图片描述


最后简单小结一下:

  1. 对主机的排序 发生一次(仅一次),然后后面按照顺序依次选择
  2. 对选择的主机对应的IP再排序,这种发生的次数就是上面选择一个主机 排一次IP的顺序,直到成功建立连接
  3. 若是这样的情况(假设3个host,它们依次对应了1,2,3个IP),最差的情况(我们假定仍然只有一个data存活且是第一个)。主机排序1次,3次+2次的额外尝试将不可避免
  4. 在调试的过程中,可以将connect_timeout也配置到连接字符串中 把该值设置大点,以避免人为因素的影响

最后的最后,我们再看一下上面如何做到随机:

// src/interfaces/libpq/fe-connect.c

/*
 * Initializes the prng_state field of the connection. We want something
 * unpredictable, so if possible, use high-quality random bits for the
 * seed. Otherwise, fall back to a seed based on the connection address,
 * timestamp and PID.
 *  
 * 初始化连接的 prng_state 字段。
 * 我们想要一些不可预测的东西,因此如果可能的话,请使用高质量的随机位作为种子。
 * 否则,请回退到基于连接地址、时间戳和 PID 的种子。
 */
static void
libpq_prng_init(PGconn *conn)
{
	uint64		rseed;
	struct timeval tval = {0};

	if (pg_prng_strong_seed(&conn->prng_state))
		return;

	gettimeofday(&tval, NULL);

	rseed = ((uintptr_t) conn) ^
		((uint64) getpid()) ^
		((uint64) tval.tv_usec) ^
		((uint64) tval.tv_sec);

	pg_prng_seed(&conn->prng_state, rseed);
}

简单解释一下上面的随机数种子函数:

  1. 初始化随机数生成器:该函数的目的是为 PostgreSQL 客户端连接初始化伪随机数生成器(PRNG)。它通过生成一个基于连接地址、进程 ID 和当前时间的种子来初始化 PRNG。
  2. 确保强种子:在初始化之前,函数会检查是否已经有一个强随机种子,如果已经有,就不再进行重新初始化。
  3. 多样性和不可预测性:通过结合连接的内存地址、进程 ID 和当前时间(包括微秒),生成的种子具有较好的随机性和不可预测性。
// src/common/pg_prng.c

/*
 * Select a random uint64 uniformly from the range [rmin, rmax].
 * If the range is empty, rmin is always produced.
 * 从范围 [rmin, rmax] 中均匀选择一个随机 uint64。
 * 如果范围为空,则始终生成 rmin。
 */
uint64
pg_prng_uint64_range(pg_prng_state *state, uint64 rmin, uint64 rmax)
{
	uint64		val;

	if (likely(rmax > rmin))
	{
		/*
		 * Use bitmask rejection method to generate an offset in 0..range.
		 * Each generated val is less than twice "range", so on average we
		 * should not have to iterate more than twice.
		 * 使用位掩码拒绝方法在 0..range 中生成偏移量。
		 * 每个生成的值都小于“range”的两倍,因此平均而言我们不必迭代超过两次。
		 */
		uint64		range = rmax - rmin;
		uint32		rshift = 63 - pg_leftmost_one_pos64(range);

		do
		{
			val = xoroshiro128ss(state) >> rshift;
		} while (val > range);
	}
	else
		val = 0;

	return rmin + val;
}

问题1:因为有小伙伴会问错误信息什么时候清理的,如下:

在这里插入图片描述


问题2:使用两种ip建立的连接分别长什么样,如下:

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/935910.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

批量合并多个Excel到一个文件

工作中&#xff0c;我们经常需要将多个Excel的数据进行合并&#xff0c;很多插件都可以做这个功能。但是今天我们将介绍一个完全免费的独立软件【非插件】&#xff0c;来更加方便的实现这个功能。 准备Excel 这里我们准备了两张待合并的Excel文件 的卢易表 打开的卢易表软件…

shell编程(完结)

shell编程&#xff08;完结&#xff09; 声明&#xff01; 学习视频来自B站up主 ​泷羽sec​​ 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章 笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其…

docker 容器相互访问

目前采用 network 方式 1. 创建自定义网络 docker network create network-group 如下 2. 相互访问的容器更改&#xff08;目前演示redis 以及netcore api 访问redis &#xff09; //redis 原有容器删除 跟之前区别就是加入 --network network-group docker run \ -p 6379:…

nVisual关于钉钉小程序打开项目及调试说明

关于钉钉小程序开发者工具的使用对于没有接触过的人可能比较陌生。如果需要部署钉钉小程序是需要对钉钉小程序开发者工具有一定的了解的&#xff0c;需要在此做部分上线前的测试及在开发者工具中上传项目包&#xff0c;故此做部分工具的解释。 分三部分来进行解释&#xff1a;…

免费下载 | 2024算网融合技术与产业白皮书

《2024算网融合技术与产业白皮书&#xff08;2023年&#xff09;》的核心内容概括如下&#xff1a; 算网融合发展概述&#xff1a; 各国细化算网战略&#xff0c;指引行业应用创新升级。 算网融合市场快速增长&#xff0c;算力互联成为投资新热点。 算网融合产业模式逐渐成型…

基于docker搭建pulsar和使用攻略

pulsar Pulsar是一个由yahoo公司于2016年开源的消息中间件&#xff0c;2018年成为Apache的顶级项目 我们先来看一下架构&#xff0c;从架构来看&#xff0c;和其他的消息中间件差不多&#xff0c;都是有消费者&#xff0c;生产者和broker,唯一一点不同的是pulsar的数据存储是…

[代码随想录16]二叉树的重新构造,路径总和,左下角的值

前言 关于二叉树的题目&#xff0c;我认为主要是把基础的思想掌握了&#xff0c;剩下的还是拼装和组合的题目&#xff0c;我们重要的就是学会一些基本的二叉思路&#xff0c;递归好还是迭代好&#xff0c;怎么递归和怎么迭代&#xff0c;二叉树的题目在面试过程中考的是挺多的&…

数据链路层(Java)(MAC与IP的区别)

以太网协议&#xff1a; "以太⽹" 不是⼀种具体的⽹络, ⽽是⼀种技术标准; 既包含了数据链路层的内容, 也包含了⼀些物理 层的内容. 例如: 规定了⽹络拓扑结构, 访问控制⽅式, 传输速率等; 例如以太⽹中的⽹线必须使⽤双绞线; 传输速率有10M, 100M, 1000M等; 以太…

梳理你的思路(从OOP到架构设计)_基本OOP知识04

目录 1、 主动型 vs.基於被动型 API 1&#xff09;卡榫函数实现API 2&#xff09;API的分类 3&#xff09;回顾历史 4&#xff09;API >控制力 2、 结语&复习&#xff1a; 接口与类 1&#xff09;接口的表示 2&#xff09;Java的接口表示 1、 主动型 vs.基於被动…

题解 - 取数排列

题目描述 取1到N共N个连续的数字&#xff08;1≤N≤9&#xff09;&#xff0c;组成每位数不重复的所有可能的N位数&#xff0c;按从小到大的顺序进行编号。当输入一个编号M时&#xff0c;就能打印出与该编号对应的那个N位数。例如&#xff0c;当N&#xff1d;3时&#xff0c;可…

FPGA实现GTP光口数据回环传输,基于Aurora 8b/10b编解码架构,提供2套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案 3、工程详细设计方案工程设计原理框图用户数据发送模块基于GTP高速接口的数据回环传输架构GTP IP 简介GTP 基本结构GTP 发送和接收…

【Linux】常用Linux命令大全(持续更新)

前言 汇总常用linux命令及用法&#xff0c;方便大家在日常工作中操作linux的便捷性 一、top命令 top 是一个在 Linux 系统上常用的实时系统监控工具。它提供了一个动态的、交互式的实时视图&#xff0c;显示系统的整体性能信息以及正在运行的进程的相关信息    在键入top命令…

美畅物联丨JS播放器录像功能:从技术到应用的全面解析

畅联云平台的JS播放器是一款功能十分强大的视频汇聚平台播放工具&#xff0c;它已经具备众多实用功能&#xff0c;像实时播放、历史录像回放、云台控制、倍速播放、录像记录、音频播放、画面放大、全屏展示、截图捕捉等等。这些功能构建起了一个高效、灵活且用户友好的播放环境…

SQL server学习03-创建数据表

目录 一&#xff0c;SQL server的数据类型 1&#xff0c;基本数据类型 2&#xff0c;自定义数据类型 二&#xff0c;使用T-SQL创建表 1&#xff0c;数据完整性的分类 2&#xff0c;约束的类型 3&#xff0c;创建表时创建约束 4&#xff0c;任务 5&#xff0c;由任务编写…

右玉200MW光伏电站项目 微气象、安全警卫、视频监控系统

一、项目名称 山西右玉200MW光伏电站项目 微气象、安全警卫、视频监控系统 二、项目背景&#xff1a; 山西右玉光伏发电项目位于右玉县境内&#xff0c;总装机容量为200MW&#xff0c;即太阳能电池阵列共由200个1MW多晶硅电池阵列子方阵组成&#xff0c;每个子方阵包含太阳能…

【Linux系统】—— 权限的概念

【Linux系统】—— 权限的概念 1 权限1.1 什么是权限1.2 为什么要有权限1.3 理解权限 2 文件的权限2.1 文件角色2.2 文件权限2.3 修改文件权限2.3.1 修改目标属性2.3.1.1 字符修改法2.3.1.2 8进制修改法 2.3.2 修改角色 3 文件权限补充知识点3.1 只能修改自己的文件权限3.2 没有…

重生之我在异世界学编程之C语言:深入文件操作篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 函数递归与迭代 引言正文一、为什么要用文件二、文…

ctfshow-web 151-170-文件上传

151. 我们首先想到就是上传一句话木马。但是看源代码限制了png。 &#xff08;1&#xff09;改前端代码。 这里是前端限制了上传文件类型&#xff0c;那我们就改一下就好了嘛,改成php。 这里直接修改不行&#xff0c;给大家推荐一篇简短文章&#xff0c;大家就会了&#xff08…

【Flutter_Web】Flutter编译Web第一篇(插件篇):Flutter_web实现上传TOS上传资源,编写web插件

前言 由于Flutter在双端的开发体验几乎接近的情况下&#xff0c;尝试将Flutter代码转Web端进行部署和发布&#xff0c;在其中遇到的所有问题&#xff0c;我都会通过这种方式分享出来。那么第一个要解决的就是上传资源到TOS上&#xff0c;在双端中都是通过插件的方式在各端通过…

成都银泰生物科技有限责任公司简介

成都银泰生物科技有限责任公司成立于2014年&#xff0c;是一家专注于体外诊断产品销售和服务的公司。公司位于中国四川省成都市。其所售产品涵盖了生化、免疫、POCT、凝血、输血、血球、尿液、分子诊断、病理等多个技术平台。 成都银泰生物科技有限责任公司以“科技服务人类健…