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;
}
相关推荐
董乐,快乐的乐!5 分钟前
Study-Oracle-11-ORALCE19C-ADG集群搭建
数据库·oracle
青云交1 小时前
大数据新视界 --大数据大厂之 Kafka 性能优化的进阶之道:应对海量数据的高效传输
大数据·数据库·人工智能·性能优化·kafka·数据压缩·分区策略·磁盘 i/o
Sarapines Programmer1 小时前
【Sqlite】sqlite内部函数sqlite3_value_text特性
数据库·sqlite·数据转换·科学计数法
打码人的日常分享1 小时前
企业人力资源管理,人事档案管理,绩效考核,五险一金,招聘培训,薪酬管理一体化管理系统(源码)
java·数据库·python·需求分析·规格说明书
好好学习的人1 小时前
SQL第12课——联结表
数据库·sql
程序员古德1 小时前
系统架构设计师论文《论NoSQL数据库技术及其应用》精选试读
数据库·nosql
青云交2 小时前
大数据新视界 --大数据大厂之 DataFusion:超越传统的大数据集成与处理创新工具
数据库·内存管理·apache hive·数据集成·大数据处理·datafusion·查询处理·powercenter
s_little_monster2 小时前
【QT】QT入门
数据库·c++·经验分享·笔记·qt·学习·mfc
九圣残炎2 小时前
【springboot】简易模块化开发项目整合Redis
spring boot·redis·后端
.生产的驴2 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript