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;
}
相关推荐
2501_9032386525 分钟前
自定义登录页面的Spring Security实践
java·后端·spring·个人开发
飞翔的佩奇1 小时前
Java项目: 基于SpringBoot+mybatis+maven+mysql实现的图书管理系统(含源码+数据库+答辩PPT+毕业论文)
java·数据库·spring boot·mysql·spring·毕业设计·图书管理
一 乐2 小时前
基于vue船运物流管理系统设计与实现(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端·船运系统
jerry6093 小时前
注解(Annotation)
java·数据库·sql
沈韶珺3 小时前
Elixir语言的安全开发
开发语言·后端·golang
vcshcn5 小时前
DBASE DBF数据库文件解析
数据库·dbase
码界筑梦坊5 小时前
基于Django的个人博客系统的设计与实现
后端·python·django·毕业设计
AIGC大时代6 小时前
对比DeepSeek、ChatGPT和Kimi的学术写作撰写引言能力
数据库·论文阅读·人工智能·chatgpt·数据分析·prompt
如风暖阳6 小时前
Redis背景介绍
数据库·redis·缓存
酷爱码7 小时前
springboot 动态配置定时任务
java·spring boot·后端