ProxySQL v1.3.2 版本解析:Monitor 崩溃修复、USE 路由支持与 Query Digest 内存优化实战 后端数据库负载均衡【免费下载链接】proxysqlHigh-performance proxy for MySQL and PostgreSQL项目地址https://gitcode.com/gh_mirrors/pr/proxysql点击查看免费下载ProxySQL 是一款面向 MySQL 与 PostgreSQL 的高性能代理High-performance proxy for MySQL and PostgreSQL本仓库即为其开源代码库。本文围绕 ProxySQL_v1.3.2.md 这份 2016-12-29 发布的 v1.3.2 稳定版发布说明展开逐条拆解该版本修复的 Bug、新增的 MySQL 协议能力以及为降低内存占用而引入的两个新变量并结合当前仓库源码lib/MySQL_Monitor.cpp、lib/MySQL_Session.cpp、lib/MySQL_Thread.cpp、lib/MySQLFFTO.cpp、tools/proxysql_galera_checker.sh 等说明其底层实现与实战配置方法。读完本文你将掌握 v1.3.2 关键修复的原理、Galera 高可用检查脚本的用法以及如何通过调整 query digest 相关变量来精确控制 ProxySQL 的内存占用。v1.3.2 版本概览v1.3.2 是 ProxySQL 在 2016-12-29 发布的稳定版本Stable release。与 v1.3.1 相比该版本主要包含以下几类变更Bug 修复Monitor 模块在purge_idle_connection中的崩溃与内存泄漏修复贡献来自 efirs查询路由中 session 变量与transaction_persistenttrue组合时错误禁用查询路由的问题。功能增强proxysql_galera_checker.sh调度工具改进贡献来自 grypyrgMySQL 协议支持将COM_QUERY USE视为COM_INIT_DB加速stats_mysql_query_digest与stats_mysql_query_digest_text两张表的填充速度。新变量引入mysql-query_digests_max_digest_length与mysql-query_digests_max_query_length主要用于降低包含大量唯一查询的工作负载下的内存占用对应 issue #766。下文按功能模块逐一深入。Monitor 崩溃修复purge_idle_connections 的内存泄漏问题背景在 v1.3.1 及更早版本中Monitor 模块的purge_idle_connections实现存在两处缺陷崩溃crashing bug清理空闲连接时对连接列表的迭代与删除操作存在竞态与指针失效风险内存泄漏memory leak清理过程中连接对象与状态数据未能正确释放。在当前的仓库代码中这段旧的purge_idle_connections实现已作为注释保留在 lib/MySQL_Monitor.cpp#L552-L578MySQL_Monitor_Connection_Pool::purge_idle_connections可以看到其原实现逻辑遍历连接池对空闲时间超过mysql_thread___monitor_ping_interval * 1000 * 3即 3 个 ping 周期的连接执行MYSQL *my对象回收并加入GloMyMon-queue队列再通过std::swappop_back/erase的方式从容器中移除。// 旧实现v1.3.2 修复前的形态现以注释形式保留在源码中 if (now (then mysql_thread___monitor_ping_interval*1000 * 3)) { MySQL_Monitor_State_Data *mmsd new MySQL_Monitor_State_Data((char *),0,NULL,false); mmsd-mysqlmy; GloMyMon-queue.add(new WorkItem(mmsd,NULL)); std::swap(*it3, lst.back()); ... }在长连接场景下Monitor 会为每个被监控的 MySQL 后端维持探测连接池当空闲探测连接积累到一定规模后上述删除路径一旦触发就容易引发崩溃并泄漏MySQL_Monitor_State_Data对象。修复后的实现v1.3.2 将清理逻辑替换为更稳健的purge_some_connectionslib/MySQL_Monitor.cpp#L521-L549修复后的实现具有如下特点仅在每个 server 连接数超过4时才回收最旧的连接remove_index_fast(0)避免频繁清理对仍保留的连接仅在空闲时长超过mysql_thread___monitor_ping_interval * 1000 * 1010 个 ping 周期时才回收回收的连接统一封装为MySQL_Monitor_State_Data(MON_CLOSE_CONNECTION, ...)并投递到GloMyMon-queue由 Monitor 工作线程统一关闭确保资源释放路径一致不再泄漏。// 修复后的实现lib/MySQL_Monitor.cpp#L521-L549 for (unsigned int i0; iservers-len; i) { MonMySrvC *srv (MonMySrvC *)servers-index(i); while (srv-conns-len 4) { MYSQL *my (MYSQL *)srv-conns-remove_index_fast(0); MySQL_Monitor_State_Data *mmsd new MySQL_Monitor_State_Data(MON_CLOSE_CONNECTION, (char *),0,false); mmsd-mysqlmy; GloMyMon-queue-add(new WorkItemMySQL_Monitor_State_Data(mmsd,NULL)); } ... }从源码结构看v1.3.2 通过限制连接池规模上限 延迟到队列统一回收的方式同时解决了崩溃与内存泄漏两个问题并把清理节奏与mysql-monitor_ping_interval全局变量解耦避免对在线监控探测造成抖动。查询路由修复session 变量与 transaction_persistent 的组合问题问题描述v1.3.1 中存在一个查询路由缺陷当客户端在会话中设置了 session 变量例如SET var...或SET SESSION ...同时该用户/会话又开启了transaction_persistenttrue时查询路由会被错误地禁用——即本应继续将请求路由到原主机组hostgroup却退化为默认路由行为导致读写分离或主机组路由策略失效。修复后的路由逻辑transaction_persistent的语义是一旦某个连接进入事务则事务内的所有查询都必须固定到该连接所在的主机组直至事务结束。仓库中 lib/HostgroupRouting.cpp#L17-L37 体现了这一核心逻辑int transaction_persistent_hostgroup, ... transaction_persistent_hostgroup -1) { ... if (transaction_persistent_hostgroup 0) { current_hostgroup transaction_persistent_hostgroup;也就是说只要transaction_persistent_hostgroup 0事务已锁定在某个主机组后续查询一律强制使用该主机组不再参与常规路由规则匹配。v1.3.2 修复的重点在于session 变量的设置本身不应改变或覆盖这个事务路由锁定状态。此前由于处理顺序问题先设置 session 变量、后开启事务的场景会让路由模块误判存在未提交的会话级变更而跳过持久路由本版本修正了状态判断的先后关系。该标志位在会话层由 lib/Base_Session.cpp#L85-L86 初始化为transaction_persistent_hostgroup -1; transaction_persistent false;用户级transaction_persistent属性则在认证阶段通过 lib/MySQL_Authentication.cpp#L196 的MySQL_Authentication::add写入用户对象ad-transaction_persistenttransaction_persistent;ClickHouse 认证路径 lib/ClickHouse_Authentication.cpp#L83 也有相同逻辑。实战验证建议在 admin 控制台为需要事务粘性的用户启用该特性UPDATE mysql_users SET transaction_persistent1 WHERE usernameapp_user;并LOAD MYSQL USERS TO RUNTIME;用同一连接执行SET SESSION autocommit0;后再执行写操作并开启事务确认事务期间的读请求仍路由到写入主机组而不是被 session 变量干扰路由。Galera 集群检查proxysql_galera_checker.sh 改进脚本用途proxysql_galera_checker.sh是 ProxySQL 官方提供的调度工具灵感来自 Percona 的clustercheck.sh用于配合 GaleraMariaDB/Percona XtraDB Cluster高可用架构动态调整 ProxySQL 中后端节点的 ONLINE/OFFLINE 状态。脚本位于仓库 tools/proxysql_galera_checker.sh共 346 行bash 实现。v1.3.2 对本脚本做了改进贡献来自 grypyrg主要体现在对节点状态判断的健壮性上。使用方式脚本通过参数传入主机组与策略典型调用方式如下# 参数write_hostgroup_id [read_hostgroup_id] [number_writers] [writers_are_readers] [log_file] tools/proxysql_galera_checker.sh 0 1 2 1 /var/log/proxysql_galera_checker.log各参数含义脚本头部 usage 说明参数必填说明HOSTGROUP_WRITER_ID是承载写流量的主机组 IDHOSTGROUP_READER_ID否默认 -1承载读流量的主机组 IDNUMBER_WRITERS否默认 0允许被标记为 ONLINE 的写节点最大数量0 表示不限制WRITER_IS_READER否默认 1为 1 时写主机组中 ONLINE 的节点将优先不在读主机组中标记 ONLINELOG_FILE否默认 /dev/null节点状态检查与变更的详细日志文件关键行为与改进点权重优先mysql_servers中 WEIGHT 更高的主机更优先被置为 ONLINE。状态保护处于OFFLINE_HARD的节点不会被检查、也不会被改变状态SHUNNED节点在 Galera 场景下会被重新检查并置为ONLINE或OFFLINE_SOFT。SYNCED 兜底重试当读/写节点均未找到wsrep_local_state4SYNCED时脚本会对每个节点重试最多 5 次以寻找wsrep_local_state4SYNCED或wsrep_local_state2DONOR/DESYNC状态的节点避免误将所有节点标记为OFFLINE_SOFT——这正是 v1.3.2 改进的核心点之一。防重入脚本通过pidof -x -o %PPID检测自身是否已在运行tools/proxysql_galera_checker.sh#L53-L60防止 cron/调度器并发触发导致状态抖动。自定义连接信息脚本头部默认使用admin/admin连接本机 admin 端口6032生产环境需修改PROXYSQL_USERNAME、PROXYSQL_PASSWORD、PROXYSQL_HOSTNAME、PROXYSQL_PORT四个变量。调度示例配合 cron* * * * * /data/web/disk1/git_repo/gh_mirrors/pr/proxysql/tools/proxysql_galera_checker.sh 0 1 2 1 /var/log/proxysql_galera_checker.log说明当前仓库中的脚本以实际可运行形态保留读者可直接阅读 tools/proxysql_galera_checker.sh 全文了解完整实现。MySQL 协议增强COM_QUERY USE 等价于 COM_INIT_DB问题背景issue #718MySQL 官方协议中切换默认数据库的标准方式是COM_INIT_DB。但部分应用例如用 Perl 编写的程序并不发送COM_INIT_DB而是以普通查询的形式发送USE dbname。v1.3.1 及此前版本中ProxySQL 仅识别COM_INIT_DB导致这类客户端无法正确切换 schema进而影响后续查询的路由与default_schema语义。v1.3.2 为此在 MySQL 协议层新增支持将COM_QUERY USE视为COM_INIT_DB处理。源码实现该能力在 lib/MySQL_Session.cpp 中实现核心链路如下在GPFC_QueryUSElib/MySQL_Session.cpp#L5776-L5817中识别 USE 语句通过strncasecmp((char *)USE ,qd,4)判断当前查询是否为USE对 PostgreSQL 会话的对应实现位于 lib/PgSQL_Session.cpp#L4599-L4629。命中后调用handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_USE_DBlib/MySQL_Session.cpp#L8001-L8039其注释明确写道this function was introduced due to isseu #718。在该 handler 中使用MySQL_Set_Stmt_Parser解析USE后的库名lib/MySQL_Set_Stmt_Parser.cpp#L532成功后通过userinfo-set_schemaname()更新会话默认 schema并直接向客户端回送 OK 包generate_pkt_OK同时递增MyHGM-status.frontend_use_db计数器解析失败则回送错误码 1148ER_NOT_SUPPORTED_YET 之外的语义由 ProxySQL 定义为无法解析并记审计日志。// lib/MySQL_Session.cpp#L8001-L8039节选 // this function was introduced due to isseu #718 // some application (like the one written in Perl) do not use COM_INIT_DB , but COM_QUERY with USE dbname void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_USE_DB(PtrSize_t *pkt) { ... string nqstring((char *)pkt-ptrsizeof(mysql_hdr)1,pkt-size-sizeof(mysql_hdr)-1); MySQL_Set_Stmt_Parser parser(nq); string errmsg ; string schemaname parser.parse_USE_query(errmsg); if (schemaname ! ) { client_myds-myconn-userinfo-set_schemaname((char *)schemaname.c_str(),schemaname.length()); ... client_myds-myprot.generate_pkt_OK(true,NULL,NULL,1,0,0,setStatus,0,NULL); GloMyLogger-log_audit_entry(PROXYSQL_MYSQL_INITDB, this, NULL); } }同时查询处理器也将USE归类为可识别命令lib/MySQL_Query_Processor.cpp#L77[MYSQL_COM_QUERY_USE] (char*)USEadmin 处理器亦对USE前缀做兼容处理lib/Admin_Handler.cpp#L4105。兼容性说明该特性对所有 MySQL 协议客户端生效不限于 mysqldump当前源码在识别 USE 语句时还兼容了USE前带注释issue #3493以及反引号写法USE\[lib/MySQL_Session.cpp#L5786-L5796](https://link.gitcode.com/i/15f093e39a65f952db0f5f517077811a#L5796)即GPFC_QueryUSE中的(strncasecmp((char *)USE,qd,4)0)分支ClickHouse 场景下走的是相反路径因为 ClickHouse 不支持COM_INIT_DBProxySQL 反而将COM_INIT_DB替换为COM_QUERY USElib/MySQL_Session.cpp#L5091-L5093两者形成互补。Admin 性能改进加速 stats_mysql_query_digest 填充v1.3.2 还提升了 admin 模块填充stats_mysql_query_digest与stats_mysql_query_digest_text两张统计表的速度。这两张表是 ProxySQL 查询分析的核心数据源前者以 digest哈希聚合统计查询次数、总耗时等后者保存 digest 对应的文本。该加速主要得益于下文所述 digest 截断机制与内部更新路径的优化——在大量唯一查询的场景下减少了对大文本的无谓拷贝从而显著加快统计表的更新与查询速度。从源码可见digest 文本的写入路径经过 lib/MySQLFFTO.cpp#L277-L282mysql_query_digest_and_first_comment计算 digeststrnlen按上限截断最终由 lib/QP_query_digest_stats.cpp#L43-L47 使用strndup(_digest_text, query_digests_max_digest_length)存入统计对象配合 PostgreSQL 会话的并行实现lib/PgSQLFFTO.cpp#L282说明该机制对 MySQL 与 PostgreSQL 双协议统一生效。新变量控制 Query Digest 内存占用的两个开关引入动机issue #766在包含大量唯一查询unique queries的负载下stats_mysql_query_digest与stats_mysql_query_digest_text会保存大量不同查询的文本。若查询文本很长例如带大量参数的动态 SQL内存占用会迅速膨胀。v1.3.2 为此引入两个全局变量让用户以精度换取内存变量说明变量默认值允许范围源码注册作用mysql-query_digests_max_digest_length20482*102416 ~ 110241024stats_mysql_query_digest.digest_text保存的最大长度字节超出部分截断mysql-query_digests_max_query_length65000legacy default16 ~ 110241024计算 digest / digest_text 时处理的 SQL 最大长度超长查询只取前 N 字节参与摘要计算两个变量在源码中的注册与取值范围定义见 lib/MySQL_Thread.cpp#L3087-L3088VariablesPointers_int[query_digests_max_digest_length] make_tuple(variables.query_digests_max_digest_length, 16, 1*1024*1024, false); VariablesPointers_int[query_digests_max_query_length] make_tuple(variables.query_digests_max_query_length, 16, 1*1024*1024, false);默认值在 lib/MySQL_Thread.cpp#L1425-L1426 中设定variables.query_digests_max_digest_length2*1024; variables.query_digests_max_query_length65000; // legacy defaultMySQL 与 PostgreSQL 两套线程模型均有同名变量lib/PgSQL_Thread.cpp#L1127-L1128、include/PgSQL_Thread.h#L1068-L1069、include/MySQL_Thread.h#L741-L742配置前缀相应为mysql-/pgsql-。底层实现截断发生在哪里查询解析截断tokenizer 在解析阶段即受mysql_thread___query_digests_max_query_length约束lib/c_tokenizer.cpp#L254超过该长度的 SQL 只解析前 N 字节直接削减解析成本与 digest 计算的输入规模旧版 tokenizerlib/c_tokenizer_legacy.cpp#L14同样遵循该上限。digest 计算截断在 lib/MySQLFFTO.cpp#L273 中opts.max_query_length mysql_thread___query_digests_max_query_length;随后在 lib/MySQLFFTO.cpp#L281 用strnlen(digest_text, mysql_thread___query_digests_max_digest_length)限制参与哈希的 digest 文本长度哈希算法为 SpookyHash见 lib/MySQLFFTO.cpp#L282。存储截断统计表写入路径使用strndup按query_digests_max_digest_length截断后落库lib/QP_query_digest_stats.cpp#L47预编译语句缓存中的查询文本同样按该上限截断lib/MySQL_PreparedStatement.cpp#L1017。配置方法在 admin 控制台默认端口 6032中-- 查看当前值 SELECT mysql-query_digests_max_digest_length; SELECT mysql-query_digests_max_query_length; -- 示例将 digest 文本上限压缩到 1KB查询参与摘要长度压缩到 16KB SET mysql-query_digests_max_digest_length1024; SET mysql-query_digests_max_query_length16384; LOAD MYSQL VARIABLES TO RUNTIME; SAVE MYSQL VARIABLES TO DISK;注意事项两个变量的最小值均为 16 字节过小会导致 digest 区分度急剧下降不同查询可能映射到相同 digest统计失真LOAD MYSQL VARIABLES TO RUNTIME后新值立即对所有 MySQL 会话线程生效线程变量通过REFRESH_VARIABLE_INT同步lib/MySQL_Thread.cpp#L5352-L5353若同时运行 PostgreSQL 协议流量需配置pgsql-query_digests_max_digest_length/pgsql-query_digests_max_query_length两个对应变量lib/PgSQL_Thread.cpp#L2375-L2376。版本升级与验证建议若要从 v1.3.1 升级到 v1.3.2建议按以下顺序操作备份当前配置在 admin 控制台执行SAVE MYSQL USERS TO DISK; SAVE MYSQL SERVERS TO DISK; SAVE MYSQL VARIABLES TO DISK;对应仓库 lib/ProxySQL_Admin.cpp 的持久化流程用新版二进制替换旧版并重启服务systemd 单元见 systemd/system/proxysql.service验证 Monitor 不再崩溃观察 error log 是否还有purge_idle相关崩溃/泄漏告警验证 USE 路由使用发送COM_QUERY USE的客户端如 Perl DBI切换数据库后确认后续查询路由与default_schema行为正确观察统计表填充速度与内存占用在高峰时段对比stats_mysql_query_digest的更新延迟与 RSS 内存视需要调低mysql-query_digests_max_digest_length。小结v1.3.2 虽为小版本发布但涵盖了三个对生产运维有实际意义的改进方向Monitor 模块的稳定性崩溃与泄漏修复、MySQL 协议兼容性COM_QUERY USE等价COM_INIT_DB、以及可量化的内存控制手段两个 digest 截断变量。结合本文给出的源码路径lib/MySQL_Monitor.cpp、lib/MySQL_Session.cpp、lib/MySQL_Thread.cpp、lib/MySQLFFTO.cpp、tools/proxysql_galera_checker.sh读者既可以理解每个修复的底层原理也可以直接参照配置命令在真实环境中落地。对于运行高唯一查询负载的 ProxySQL 实例合理配置mysql-query_digests_max_digest_length与mysql-query_digests_max_query_length是控制内存占用最直接有效的手段。赞分享后端数据库负载均衡【免费下载链接】proxysqlHigh-performance proxy for MySQL and PostgreSQL项目地址https://gitcode.com/gh_mirrors/pr/proxysql点击查看免费下载相关推荐Robot Framework 4.1.2 版本解析内存优化、Java 集成修复与 parser 崩溃修复实战指南Robot Framework 4.1.2 版本解析内存优化、Java 集成修复与 parser 崩溃修复实战指南 本篇文章以 Robot Framework测试RPA接口测试RabbitMQ 3.9.17 维护版解析内存优化、日志轮转回归与 JMS 路由修复RabbitMQ 3.9.17 维护版解析内存优化、日志轮转回归与 JMS 路由修复 RabbitMQ 3.9.17 是 3.9.x 系列中的维护版本mai后端消息队列消息路由Zcash v1.0.8-1 补丁版解析JoinSplit 交易优先级与内存池崩溃漏洞修复Zcash v1.0.8 1 补丁版解析JoinSplit 交易优先级与内存池崩溃漏洞修复 导读 本文围绕 Zcash 历史补丁版 v1.0.8 1 的发布说区块链密码学上一篇Cloudflare Email Workers 避坑指南从流消耗、静默错误到安全与限额的完整实践手册下一篇信息产品Info Products付费媒体规划claude-ads 中的五重门禁、规划问题与输出护栏创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考