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;
}
相关推荐
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue4 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man4 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
Ai 编码助手5 小时前
MySQL中distinct与group by之间的性能进行比较
数据库·mysql
陈燚_重生之又为程序员5 小时前
基于梧桐数据库的实时数据分析解决方案
数据库·数据挖掘·数据分析
caridle5 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
白云如幻5 小时前
MySQL排序查询
数据库·mysql
萧鼎5 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步