- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
导读
本文深入解析 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 触发时机与数据来源
两个时机触发节点列表加载:
- ProxySQL 实例启动;
- 在 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,流程与文档描述完全吻合:
- 加锁(
pthread_mutex_lock(&mutex)),随后调用set_all_inactive()(lib/ProxySQL_Cluster.cpp#L4347)把umap_proxy_nodes中所有节点标记为 inactive; - 遍历结果集:对每一行取
fields[0..3]作为 host、port、weight、comment,用generate_hash(h_, p_)计算键; - 查 map:若键存在,则对该节点执行
resolve_hostname()、set_active(true),并更新set_weight()/set_comment(); - 若键不存在:
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 作为参数传入; - 删除 inactive 节点:
remove_inactives()(lib/ProxySQL_Cluster.cpp#L4355)把仍处于 inactive 的条目delete并从 maperase; - 解锁。
由此得到一个关键语义:每个集群节点对应一个常驻监控线程,且线程只在节点首次入表时创建;之后该节点每次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的处理逻辑(对应文档的详细流程图)对每个模块执行:
- 若结果集中存在该模块记录:用远端值更新本地
version、epoch、last_updated; - 比较本地模块校验和与远端校验和:相同则
diff_check += 1(连续差异计数递增);不同则更新校验和、置last_changed为当前时间并把diff_check重置为 1; - 若本地自身模块校验和 == 远端校验和(即自己已经是最新),则把
diff_check清零; - 若结果集中不存在某模块记录(
resultset == NULL分支):对每个模块置last_updated = now,并按"本地校验和 == 自身版本校验和"决定diff_check置 0 或自增。
3.5 同步判定:diffs_before_sync 阈值
set_checksums之后进入同步判定,条件严格按文档描述串行执行:
cluster_<module>_diffs_before_sync != 0?为 0 则跳过该模块;- 本地模块
version > 1?(version 为 1 表示模块配置刚从初始状态加载,通常不参与抢占式同步); own_version == 1 || local epoch > own_epoch?(即实例刚启动、或远端 epoch 更新);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)拉取该模块最新配置。关键安全步骤:
- 本地对拉回的配置重新计算校验和;
- 与同步源返回的校验和比较;
- 一致才应用:清空本地模块配置表 → 插入远端数据 → 发出内部
LOAD <module> TO RUNTIME; - 不一致则丢弃(防止拉取过程中远端又变更导致数据错位)。
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_ms | 1000 | 每轮轮询的最小间隔(毫秒),实际间隔为该值与单轮耗时的较大者 |
cluster_check_status_frequency | 10 | 每多少次轮询执行一次节点状态指标(stats)采样 |
cluster_<module>_diffs_before_sync | 3 | 各模块(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_disk | true | 模块配置应用到运行时后是否SAVE TO DISK持久化 |
cluster_mysql_servers_sync_algorithm | 1 | runtime_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
相关推荐
Valdi Renderer 内部机制解析:虚拟节点树、增量 Diffing 与原生同步
Valdi Renderer 内部机制解析:虚拟节点树、增量 Diffing 与原生同步 Valdi 是一个跨平台 UI 框架,其 Renderer 承担着元素
跨平台UI组件前端移动开发鸣潮自动战斗与刷声骸完全指南:ok-wuthering-waves 部署与用法实战
鸣潮自动战斗与刷声骸完全指南:ok wuthering waves 部署与用法实战 ok wuthering waves(简称 ok ww)是一款基于图像识别的
GUI 自动化计算机视觉RPA人工智能零配置向量生成:AnythingLLM原生嵌入器的技术实现路径
零配置向量生成:AnythingLLM原生嵌入器的技术实现路径 在构建本地知识库系统时,向量生成往往成为技术门槛最高的环节。传统方案依赖第三方API服务,不仅带
人工智能AI 应用RAGAI Agent后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考