ProxySQL导致的Communications link failure错误原因分析

连接数据库异常,看报错信息 2ms 之前该连接还正常可用,所以不存在连接空闲时间过长被服务端关闭的问题。程序基于 spring boot,连接数据库组件为 harika,默认 IDLE_TIMEOUT = 10分钟,默认 MAX_LIFETIME=30分钟。

程序架构如下:

其中 ProxySQL 版本为 2.3.2,MySQL 版本为 8.0.27。

经分析,当时该报错 sql 按对应的 proxysql 路由规则走从库,查看从库的监控如下: 发现 1:13 左右,数据库的连接数从 200 急剧下降到 100 以下。

查看 proxysql 的监控如下: proxysql 和后端数据库的连接数大幅减少,但客户端连接 proxysql 的连接数几乎没有变化。

注:以上客户端连接大幅超过后端连接,一是因为客户端连接是所有的 host group,二是 proxysql 默认采用了连接复用(• Connection Multiplexing),即允许多个客户端连接重复使用同一个后端连接。

查看 proxysql 的日志如下:

Shunning Server,节点临时被剔除,可能因后端 too many connections error,或者超过了可容忍延迟阀值 max_replication_lag。结合错误日志 with replication lag of 709 second 以及 detected a lagging server during query,可以确定是数据库主从延迟过大。

查看当时的从库延迟可确认该情况。

继续查看 ProxySQL 配置的最大主从延迟时间为 500s,因此超过时,ProxySQL 把从节点临时下线。经查,凌晨 1 点左右有个长事务执行(历史数据归档),大概需要 10 分钟左右。

ProxySQL 设置从库的状态为 SHUNNED,此时 ProxySQL 和从节点的连接失效,按路由规则,新的请求(新的客户端连接)应该要走主库。

正常请求发送时,客户端与 ProxySQL 建立连接,ProxySQL 与数据库建立连接,关联关系如下:

当节点 SHUNNED 时,客户端的连接还正常,会继续复用连接发送请求,之后找到对应的 ProxySQL 与从库的连接,但此时由于从库节点状态为 SHUNNED,会直接返回报错连接不可用,同时销毁 ProxySQL 和从库之间的这条连接。

查看源码验证下,以下为相关代码:

ini 复制代码
/**
 * 判断 rc 是否等于 -1,如果是,则执行方法进行错误处理
 * handler_ProcessingQueryError_CheckBackendConnectionStatus
 */
int MySQL_Session::handler() {
	...
handler_again:
	switch (status) {
		case PROCESSING_STMT_PREPARE:
		case PROCESSING_STMT_EXECUTE:
		case PROCESSING_QUERY:
			int rc;
			rc = RunQuery(myds, myconn);
			if (rc==-1) {
				int rc1 = handler_ProcessingQueryError_CheckBackendConnectionStatus(myds);
				if (rc1 == -1) {
					handler_ret = -1;
					return handler_ret;
				} 
			}
		  break;
	}
}

int MySQL_Session::RunQuery(MySQL_Data_Stream *myds, MySQL_Connection *myconn) {
	switch (status) {
		case PROCESSING_QUERY:
			rc=myconn->async_query(myds->revents, myds->mysql_real_query.QueryPtr,myds->mysql_real_query.QuerySize);
			break;
	}
	return rc;
}

/**
 * 当节点离线时,返回 -1
 */ 
int MySQL_Connection::async_query(short event, char *stmt, unsigned long length, MYSQL_STMT **_stmt, stmt_execute_metadata_t *stmt_meta) {
	if (IsServerOffline())
		return -1;
  ...
}

/**
 * 当节点处于 MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG 状态时,返回 true
 * 即认为该节点已离线
 */
bool MySQL_Connection::IsServerOffline() {
	bool ret=false;
	if (parent==NULL)
		return ret;
	server_status=parent->status; // we copy it here to avoid race condition. The caller will see this
	if (
		(server_status==MYSQL_SERVER_STATUS_OFFLINE_HARD) // the server is OFFLINE as specific by the user
		||
		(server_status==MYSQL_SERVER_STATUS_SHUNNED && parent->shunned_automatic==true && parent->shunned_and_kill_all_connections==true) // the server is SHUNNED due to a serious issue
		||
		(server_status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) // slave is lagging! see #774
	) {
		ret=true;
	}
	return ret;
}

继续查看 handler_ProcessingQueryError_CheckBackendConnectionStatus 方法:

rust 复制代码
// this function returns:
// 0 : no action
// -1 : the calling function will return
// 1 : call to NEXT_IMMEDIATE
int MySQL_Session::handler_ProcessingQueryError_CheckBackendConnectionStatus(MySQL_Data_Stream *myds) {
	MySQL_Connection *myconn = myds->myconn;
	// the query failed
	if (
		// due to #774 , we now read myconn->server_status instead of myconn->parent->status
		(myconn->server_status==MYSQL_SERVER_STATUS_OFFLINE_HARD) // the query failed because the server is offline hard
		||
		(myconn->server_status==MYSQL_SERVER_STATUS_SHUNNED && myconn->parent->shunned_automatic==true && myconn->parent->shunned_and_kill_all_connections==true) // the query failed because the server is shunned due to a serious failure
		||
		(myconn->server_status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) // slave is lagging! see #774
	) {
		if (mysql_thread___connect_timeout_server_max) {
			myds->max_connect_time=thread->curtime+mysql_thread___connect_timeout_server_max*1000;
		}
		bool retry_conn=false;
		/**
     * 当节点状态为 MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG 时,打印错误日志
		 * 和 proxysql 错误日志匹配上了
     */
		if (myconn->server_status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) {
			thread->status_variables.stvar[st_var_backend_lagging_during_query]++;
			proxy_error("Detected a lagging server during query: %s, %d\n", myconn->parent->address, myconn->parent->port);
			MyHGM->p_update_mysql_error_counter(p_mysql_error_type::proxysql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, ER_PROXYSQL_LAGGING_SRV);
		} 
		// 销毁和 MySQL 的连接
		myds->destroy_MySQL_Connection_From_Pool(false);
		return -1;
	}
	return 0;
}
相关推荐
hnlucky9 分钟前
redis 数据类型新手练习系列——Hash类型
数据库·redis·学习·哈希算法
丘山子14 分钟前
一些鲜为人知的 IP 地址怪异写法
前端·后端·tcp/ip
CopyLower38 分钟前
在 Spring Boot 中实现 WebSockets
spring boot·后端·iphone
LucianaiB1 小时前
【金仓数据库征文】_AI 赋能数据库运维:金仓KES的智能化未来
运维·数据库·人工智能·金仓数据库 2025 征文·数据库平替用金仓
时序数据说1 小时前
时序数据库IoTDB在航空航天领域的解决方案
大数据·数据库·时序数据库·iotdb
.生产的驴2 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
景天科技苑2 小时前
【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
开发语言·后端·rust·match·enum·枚举与模式匹配·rust枚举与模式匹配
AnsenZhu2 小时前
2025年Redis分片存储性能优化指南
数据库·redis·性能优化·分片
oydcm2 小时前
MySQL数据库概述
数据库·mysql
oioihoii2 小时前
C++23中if consteval / if not consteval (P1938R3) 详解
java·数据库·c++23