☰
ProxySQL Cluster 内部机制深度解析:节点监控线程、校验和同步与配置收敛原理
2026/10/8 1:29:07 网站建设 项目流程
  • 后端
  • 数据库
  • 负载均衡

【免费下载链接】proxysql

High-performance proxy for MySQL and PostgreSQL

项目地址:https://gitcode.com/gh_mirrors/pr/proxysql
点击查看免费下载

导读

本文深入解析 ProxySQL 高可用集群(ProxySQL Cluster)的内部工作原理,以本仓库 doc/proxysql_cluster/proxysql_cluster_working.md 为骨架,结合 include/ProxySQL_Cluster.hpp 与 lib/ProxySQL_Cluster.cpp 的源码实现展开。你将掌握:集群节点模型(ProxySQL_Node_Entry/ProxySQL_Node_Address等核心类的职责)、节点列表加载与监控线程生命周期、基于全局校验和 + 模块校验和的配置变化检测机制、以及diffs_before_sync/save_to_disk等关键变量如何驱动配置从"发现差异"到"拉取 → 校验 → 应用到运行时 → 持久化"的完整收敛闭环。

本文面向已了解 ProxySQL Cluster 基本概念(proxysql_servers表、cluster_*变量、Admin 接口互连等)的读者,聚焦"内部如何工作"这一层。

一、ProxySQL Cluster 的核心类模型

Cluster 功能在代码中由ProxySQL_Cluster类体系承载,全部定义于 include/ProxySQL_Cluster.hpp,实现位于 lib/ProxySQL_Cluster.cpp(约 6000 行)。整个体系围绕 5 个核心类组织。

1.1 ProxySQL_Cluster:集群主控

ProxySQL_Cluster是集群功能的门面与协调者(include/ProxySQL_Cluster.hpp#L594)。它持有:

  • 与每个模块对应的pthread_mutex_t update_<module>_mutex(如update_mysql_query_rules_mutex、update_runtime_mysql_servers_mutex、update_mysql_servers_v2_mutex),保证同一模块不会并发同步;
  • 集群凭据cluster_username/cluster_password,以及本节点对外广播的admin_mysql_ifaces(在admin_mysql_ifaces_mutex保护下读写);
  • 一组cluster_*_diffs_before_sync原子整数变量与cluster_*_save_to_disk布尔变量,控制每个模块的同步触发阈值与持久化行为;
  • nodes成员(ProxySQL_Cluster_Nodes),即全量节点表的封装;
  • 每个模块的pull_<module>_from_peer()系列方法(如 lib/ProxySQL_Cluster.cpp#L1177 的pull_mysql_query_rules_from_peer),负责实际从远端拉取配置。

此外,类内还定义了一组被 Admin 接口拦截的CLUSTER_QUERY_*宏(见 include/ProxySQL_Cluster.hpp#L71-L239),例如:

-- 运行时 MySQL 服务器列表的校验和查询 PROXY_SELECT hostgroup_id, hostname, port, gtid_port, status, weight, compression, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment FROM runtime_mysql_servers WHERE status<>'OFFLINE_HARD' ORDER BY hostgroup_id, hostname, port

这些查询由ProxySQL_Admin拦截(文件头部注释明确说明),其结果集既用于生成校验和,也用于拉取配置后计算"期望校验和",因此必须保持相同的过滤与排序条件——这是校验和对比能够成立的前提。

1.2 ProxySQL_Node_Entry:单个节点的信息容器

ProxySQL_Node_Entry(include/ProxySQL_Cluster.hpp#L288)代表集群中的一个节点,持有:

  • hostname/port/weight/comment等注册信息,以及解析出的ip_addr;
  • 活跃标志active(在load_servers_list中被批量标记);
  • 一个checksums_values结构体,内含每个模块的ProxySQL_Checksum_Value_2(admin_variables、mysql_variables、ldap_variables、mysql_query_rules、mysql_servers、mysql_users、proxysql_servers、mysql_servers_v2,以及 PostgreSQL 侧的pgsql_query_rules/pgsql_servers/pgsql_users/pgsql_servers_v2/pgsql_variables);
  • 节点级的global_checksum(全局校验和);
  • 一个环形指标缓冲区ProxySQL_Node_Metrics *metrics[](长度PROXYSQL_NODE_METRICS_LEN = 5,include/ProxySQL_Cluster.hpp#L16),用于滚动记录相邻两次采样的节点状态指标。

1.3 ProxySQL_Node_Address:线程间传递的节点地址

ProxySQL_Node_Address(include/ProxySQL_Cluster.hpp#L271)封装hostname、port、uuid、admin_mysql_ifaces与可选ip_addr,并包含线程句柄thrid。它的典型用途是作为监控线程的参数:每次新节点入表时,load_servers_list会构造一个ProxySQL_Node_Address并传给新创建的ProxySQL_Cluster_Monitor_thread。

1.4 ProxySQL_Cluster_Nodes:节点集合管理器

ProxySQL_Cluster_Nodes(include/ProxySQL_Cluster.hpp#L395)是节点总表,核心是std::unordered_map<uint64_t, ProxySQL_Node_Entry *> umap_proxy_nodes——以generate_hash(hostname, port)生成的 64 位哈希为键。它提供:

  • load_servers_list(SQLite3_result *, bool _lock):加载proxysql_servers结果集;
  • Update_Global_Checksum()/Update_Node_Checksums():接收监控线程拉回的校验和结果集并落到对应节点;
  • 一组get_peer_to_sync_<module>():遍历 map,选出"版本大于 1 且 epoch 最大"的节点作为同步源。

值得注意,lib/ProxySQL_Cluster.cpp#L4585 的get_peer_to_sync_variables_module采用数据驱动的统一实现:用一张ModuleConfig表把模块名、对应的diffs_before_sync变量、校验和字段访问器、是否携带双校验和(V2 类模块同时返回mysql_servers_v2与runtime_mysql_servers两份校验和)统一映射起来,取代了历史上每个模块一套独立函数。

1.5 ProxySQL_GlobalVariables 与 ProxySQL_Checksum_Value

  • ProxySQL_GlobalVariables:聚合当前实例各模块的 checksum、epoch、version(代码中即GloVars.uuid所引用的全局对象),并提供对外访问入口;监控线程注册节点时使用的正是ProxySQL_GlobalVariables::uuid。
  • ProxySQL_Checksum_Value及其子类ProxySQL_Checksum_Value_2(include/ProxySQL_Cluster.hpp#L241):除version/epoch/checksum外,_2版本还增加last_updated、last_changed、diff_check三个时间/计数字段——diff_check即"连续检测到差异的次数",是diffs_before_sync阈值机制的核心输入。

二、节点列表加载与监控线程初始化

2.1 触发时机与数据来源

两个时机触发节点列表加载:

  1. ProxySQL 实例启动;
  2. 在 Admin 接口执行LOAD PROXYSQL SERVERS TO RUNTIME。

届时,proxysql_servers表的最新记录(hostname, port, weight, comment,按hostname, port排序)被取出,结果集交给ProxySQL_Cluster::load_servers_list,后者直接转发给ProxySQL_Cluster_Nodes::load_servers_list(include/ProxySQL_Cluster.hpp#L656)。

2.2 load_servers_list 的六步流程

ProxySQL_Cluster_Nodes::load_servers_list的完整实现位于 lib/ProxySQL_Cluster.cpp#L4380,流程与文档描述完全吻合:

  1. 加锁(pthread_mutex_lock(&mutex)),随后调用set_all_inactive()(lib/ProxySQL_Cluster.cpp#L4347)把umap_proxy_nodes中所有节点标记为 inactive;
  2. 遍历结果集:对每一行取fields[0..3]作为 host、port、weight、comment,用generate_hash(h_, p_)计算键;
  3. 查 map:若键存在,则对该节点执行resolve_hostname()、set_active(true),并更新set_weight()/set_comment();
  4. 若键不存在:new ProxySQL_Node_Entry(h_, p_, w_, c_)插入 map 并set_active(true);随后创建ProxySQL_Node_Address并pthread_create一个 detached 的ProxySQL_Cluster_Monitor_thread(lib/ProxySQL_Cluster.cpp#L4404),把节点 host/port 作为参数传入;
  5. 删除 inactive 节点:remove_inactives()(lib/ProxySQL_Cluster.cpp#L4355)把仍处于 inactive 的条目delete并从 maperase;
  6. 解锁。

由此得到一个关键语义:每个集群节点对应一个常驻监控线程,且线程只在节点首次入表时创建;之后该节点每次LOAD PROXYSQL SERVERS TO RUNTIME只会被更新属性,而不会重复开线程。若某节点从proxysql_servers表中被删除,下一次加载时它会被标记 inactive 并连同其监控线程(通过GloProxyCluster->Update_Node_Metrics(..., NULL, 0)的哨兵调用判定节点已不存在,见 lib/ProxySQL_Cluster.cpp#L402)一起被清理。

文档给出的整体流程图(mermaid)可归纳为:

三、监控线程的工作循环

ProxySQL_Cluster_Monitor_thread(入口位于 lib/ProxySQL_Cluster.cpp#L170)是每个节点的专职监控者。它启动后先通过proxysql_startup_gate_wait_for_runtime等待插件生命周期完成,随后进入while (glovars.shutdown == 0 && rc_bool == true)主循环。每一轮循环按以下顺序执行。

3.1 版本核对:SELECT @@version

线程以get_credentials()拿到的cluster_username/cluster_password连接远端(MYSQL_OPT_CONNECT_TIMEOUT = 1秒),然后发送版本查询。在启用PROXYSQL40宏的构建中,实际发送的是SELECT @@version, @@proxysql_plugin_set_hash(宏PROXYSQL_CLUSTER_PEER_IDENTITY_QUERY,见 include/ProxySQL_ClusterPluginHash.h#L37),除了比对版本字符串,还会比对插件集哈希,确保双方插件集合兼容(lib/ProxySQL_Cluster.cpp#L244-L258)。

  • 版本/插件集不一致:打印告警日志,关闭连接,并硬编码sleep30 秒后重试(lib/ProxySQL_Cluster.cpp#L283);
  • 一致:进入注册与同步阶段。

3.2 节点注册:PROXYSQL CLUSTER_NODE_UUID

版本匹配后,线程发送命令:

PROXYSQL CLUSTER_NODE_UUID <ProxySQL_GlobalVariables::uuid> <ProxySQL_Cluster::admin_mysql_ifaces>

其中uuid来自全局对象GloVars.uuid,admin_mysql_ifaces在admin_mysql_ifaces_mutex保护下读取(lib/ProxySQL_Cluster.cpp#L264-L269)。该命令让远端把当前节点登记为集群客户端,从而实现双向识别。

3.3 全局校验和比对:SELECT GLOBAL_CHECKSUM()

线程发送SELECT GLOBAL_CHECKSUM(),结果集交给ProxySQL_Cluster_Nodes::Update_Global_Checksum(lib/ProxySQL_Cluster.cpp#L4442)。该函数在 map 中定位本节点,取出结果集第一行的 64 位校验和v,与node->global_checksum比较:

  • 相同:返回false,本轮只做轻量检查;
  • 不同:覆盖本地保存的node->global_checksum并返回true,表示至少一个模块的配置发生了变化,需要继续拉取模块明细。

3.4 模块校验和明细:SELECT * FROM runtime_checksums_values

当全局校验和变化时,线程执行SELECT * FROM runtime_checksums_values ORDER BY name(query3,lib/ProxySQL_Cluster.cpp#L192),把结果交给Update_Node_Checksums→ProxySQL_Node_Entry::set_checksums。runtime_checksums_values的表结构定义在 include/ProxySQL_Admin_Tables_Definitions.h#L43:

CREATE TABLE runtime_checksums_values ( name VARCHAR NOT NULL, version INT NOT NULL, epoch INT NOT NULL, checksum VARCHAR NOT NULL, PRIMARY KEY (name) );

set_checksums的处理逻辑(对应文档的详细流程图)对每个模块执行:

  1. 若结果集中存在该模块记录:用远端值更新本地version、epoch、last_updated;
  2. 比较本地模块校验和与远端校验和:相同则diff_check += 1(连续差异计数递增);不同则更新校验和、置last_changed为当前时间并把diff_check重置为 1;
  3. 若本地自身模块校验和 == 远端校验和(即自己已经是最新),则把diff_check清零;
  4. 若结果集中不存在某模块记录(resultset == NULL分支):对每个模块置last_updated = now,并按"本地校验和 == 自身版本校验和"决定diff_check置 0 或自增。

3.5 同步判定:diffs_before_sync 阈值

set_checksums之后进入同步判定,条件严格按文档描述串行执行:

  1. cluster_<module>_diffs_before_sync != 0?为 0 则跳过该模块;
  2. 本地模块version > 1?(version 为 1 表示模块配置刚从初始状态加载,通常不参与抢占式同步);
  3. own_version == 1 || local epoch > own_epoch?(即实例刚启动、或远端 epoch 更新);
  4. local diff_check >= cluster_<module>_diffs_before_sync?

只有四个条件全部满足,该模块才进入真正的拉取阶段。这就是文档所述"差值累积到一定次数才同步"的去抖设计:diff_check必须连续超过阈值(默认 3 次),避免瞬时抖动触发频繁的全量同步。

3.6 选择同步源:最大 epoch 节点

同步源选择由ProxySQL_Cluster_Nodes::get_peer_to_sync_<module>(统一走get_peer_to_sync_variables_module,lib/ProxySQL_Cluster.cpp#L4585)完成。算法遍历umap_proxy_nodes:

  • 过滤条件:模块version > 1;
  • 择优条件:epoch 最大的节点(且要求其diff_check >= diff_threshold);
  • 记录该节点的 host/port、IP,以及(对带校验和的模块)其校验和值。

选出的节点即"事实来源"(source of truth)。V2 类模块(mysql_servers_v2)还会额外携带runtime_mysql_servers的次级校验和,用于双表一致性校验。

3.7 拉取配置、校验并应用

选中同步源后,线程用get_credentials()建立新连接,调用ProxySQL_Cluster::pull_<module>_from_peer(如 lib/ProxySQL_Cluster.cpp#L1177 的pull_mysql_query_rules_from_peer)拉取该模块最新配置。关键安全步骤:

  1. 本地对拉回的配置重新计算校验和;
  2. 与同步源返回的校验和比较;
  3. 一致才应用:清空本地模块配置表 → 插入远端数据 → 发出内部LOAD <module> TO RUNTIME;
  4. 不一致则丢弃(防止拉取过程中远端又变更导致数据错位)。

3.8 持久化:save_to_disk

配置成功应用到运行时后,检查cluster_<module>_save_to_disk:

  • true:发出内部SAVE <module> TO DISK,把配置写入磁盘数据库,保证重启后依然生效;
  • false:仅保留在内存运行时,重启后丢失。

3.9 周期性的状态指标采样

每轮循环末尾还有一个独立于校验和逻辑的指标通道(详见 3.10 流程图):cluster_check_status_frequency_count每轮自增,当累计达到cluster_check_status_frequency时清零并执行:

SELECT * FROM stats_mysql_global ORDER BY Variable_Name

结果交给Update_Node_Metrics→ProxySQL_Node_Entry::set_metrics(lib/ProxySQL_Cluster.cpp#L4482),提取Client_Connections_connected、Client_Connections_created、ProxySQL_Uptime、Questions、Servers_table_version五项,写入环形指标缓冲区(metrics_idx在 0..4 间滚动)。这些指标被用于stats_proxysql_servers_metrics统计表以及 Prometheus 指标(p_cluster_nodes_dyn_*,include/ProxySQL_Cluster.hpp#L362-L384)。

3.10 完整循环与节流

每轮循环结束后计算耗时,若小于cluster_check_interval_ms则usleep补齐,保证轮询频率受控(lib/ProxySQL_Cluster.cpp#L371-L379);连接异常时也会按该间隔 sleep 后重连。

文档提供的详细流程图(mermaid)完整描述了上述全部状态,核心链路可压缩为:

四、关键配置变量与默认值

集群行为由 Admin 变量控制,全部可在GLOBAL VARIABLES中查看与修改,在 lib/ProxySQL_Admin.cpp 中解析,默认值在 lib/ProxySQL_Cluster.cpp#L5888-L5917 的构造函数中初始化。

变量默认值含义
cluster_username空字符串监控/同步时用于连接远端节点的用户名;为空则线程不执行任何监控(lib/ProxySQL_Cluster.cpp#L206)
cluster_password空字符串对应密码
cluster_interface/admin_mysql_ifaces空节点注册时向远端广播的 Admin 接口地址
cluster_check_interval_ms1000每轮轮询的最小间隔(毫秒),实际间隔为该值与单轮耗时的较大者
cluster_check_status_frequency10每多少次轮询执行一次节点状态指标(stats)采样
cluster_<module>_diffs_before_sync3各模块(mysql_query_rules、mysql_servers、mysql_users、proxysql_servers、mysql_variables、admin_variables、ldap_variables、pgsql_query_rules、pgsql_servers、pgsql_users、pgsql_variables)连续检测到差异的阈值;0 表示禁用该模块同步
cluster_<module>_save_to_disktrue模块配置应用到运行时后是否SAVE TO DISK持久化
cluster_mysql_servers_sync_algorithm1runtime_mysql_servers与mysql_servers_v2的同步算法选择(详见下节)

4.1 mysql_servers_sync_algorithm 的三种模式

enum class mysql_servers_sync_algorithm(include/ProxySQL_Cluster.hpp#L556)定义了服务器模块的特殊同步策略:

  • runtime_mysql_servers_and_mysql_servers_v2 = 1:同时同步运行时服务器状态与配置,Monitor 产生的运行时变更(如连接池状态变化)也会触发同步;
  • mysql_servers_v2 = 2:只同步mysql_servers_v2静态配置,仅用户显式执行的配置变更触发同步;
  • auto_select = 3:依据是否以-M标志启动决定——未带-M选模式 1,带-M选模式 2。

该模块也因此是唯一携带双校验和(mysql_servers_v2校验和 +runtime_mysql_servers校验和)的同步对象。

五、从源码看设计要点

5.1 校验和是同步的"唯一事实"

整个同步系统不依赖时间戳对账,而是以校验和 + epoch + version 三元组为唯一事实来源。runtime_checksums_values表(见 include/ProxySQL_Admin_Tables_Definitions.h#L43)保存的就是这三个维度。这也解释了为何文档与代码反复强调"拉取配置后必须在本地重新计算校验和并与远端比对"——它是防止同步过程中数据错位、半更新状态污染集群的最后防线。

5.2 去抖与收敛:diff_check 的双重角色

diff_check既在set_checksums中单调递增(连续发现差异),又在节点自身已与远端一致时清零。这构成一个"软状态":只有差异持续存在超过diffs_before_sync轮次才触发拉取,从而容忍单次网络抖动或瞬时不一致,同时保证配置最终收敛到 epoch 最大节点所代表的最新状态。

5.3 并发安全:模块级互斥锁

每个模块在ProxySQL_Cluster中都有独立的update_<module>_mutex,多个节点的监控线程可能同时尝试同步同一模块,锁保证了"同一时刻只有一个拉取+应用操作在执行",避免配置表被并发写入撕裂。

5.4 线程生命周期与故障自愈

监控线程以 detached 方式创建(lib/ProxySQL_Cluster.cpp#L4403),连接失败时解析主机名并 sleep 后重试(lib/ProxySQL_Cluster.cpp#L390-L398);节点从proxysql_servers表消失后,通过Update_Node_Metrics(..., NULL, 0)哨兵调用感知并终止线程(lib/ProxySQL_Cluster.cpp#L402,对应 bug #1323 的修复)。这保证了集群拓扑变化后监控资源能自动回收。

六、验证与观测

集群的健康状态与同步进度可以从以下入口观测(均为仓库中已确认的实现):

  • stats_proxysql_servers_checksums()(include/ProxySQL_Cluster.hpp#L433):各节点的模块版本、epoch、diff_check等校验和维度;
  • stats_proxysql_servers_metrics()(include/ProxySQL_Cluster.hpp#L434):节点连接数、uptime、Questions 等状态指标;
  • Prometheus 指标族proxysql_servers_checksums_*/proxysql_servers_metrics_*(include/ProxySQL_Cluster.hpp#L362-L384),以及pulled_<module>_success/failure系列计数器(include/ProxySQL_Cluster.hpp#L453-L530),可用于监控同步成败;
  • 日志层面,ProxySQL_Cluster_Monitor_thread在连接、版本比对、注册、每轮同步的关键节点都输出proxy_info/proxy_warning/proxy_error(lib/ProxySQL_Cluster.cpp#L186-L391),是排查集群同步问题最直接的手段。

结语

ProxySQL Cluster 的内部机制可以概括为一条清晰的链路:proxysql_servers表驱动节点地图与监控线程 → 每轮以GLOBAL_CHECKSUM()做轻量比对 → 变化时拉取runtime_checksums_values明细 → 以diff_check与diffs_before_sync做去抖判定 → 从最大 epoch 节点拉取配置 → 本地校验和复核 →LOAD TO RUNTIME→SAVE TO DISK。本文所有流程均与 doc/proxysql_cluster/proxysql_cluster_working.md 及 lib/ProxySQL_Cluster.cpp 中的实际实现相互印证,读者可结合这两份材料深入每一处细节,并借助第六节的统计表与日志对生产集群进行观测验证。

  • 后端
  • 数据库
  • 负载均衡

【免费下载链接】proxysql

High-performance proxy for MySQL and PostgreSQL

项目地址:https://gitcode.com/gh_mirrors/pr/proxysql
点击查看免费下载

相关推荐

上一篇:Redis RDB Tools终极兼容性指南:从RDB v5到v9的完整解析
下一篇:IceCubesApp的编译错误快速定位:Xcode日志分析技巧

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询