Author: xijia
之前已经有过两篇有关连接的月报 网络通信模块浅析 mysql认证阶段漫游
本文先介绍新连接建立的主要调用栈,再分析thread cache 和每个连接的资源限制
mysql 支持三种连接方式
named pipe 和 shared memory 只能在本地连接数据库,适用场景较少,本文主要介绍 socket 连接方式
从主线程开始
main
mysqld_admin
network_init
/*
Connection_acceptor 是一个模板类
template <typename Listener> class Connection_acceptor
三种连接方式分别传入各自的 Listener
Mysqld_socket_listener
Named_pipe_listener
Shared_mem_listener
最常用的是 Mysqld_socket_listener
*/
Connection_acceptor<Mysqld_socket_listener>::init_connection_acceptor
Connection_acceptor
Mysqld_socket_listener::listen_for_connection_event
/* 监听socket文件,没有新连接时,线程在这里等待 */
poll
/* 返回新连接的信息 */
return channel_info
Connection_handler_manager::process_new_connection
/* 检查max_connections */
check_and_incr_conn_count
/*
Connection_handler 有两个子类,
Per_thread_connection_handler:一个连接一个线程
One_thread_connection_handler:一个线程处理所有连接
我们一般使用 Per_thread_connection_handler
*/
m_connection_handler->add_connection(Per_thread_connection_handler::add_connection)
/* 查看 thread_cache 中是否有空闲thread,如有,使用cached thread */
if (!check_idle_thread_and_enqueue_connection(channel_info))
DBUG_RETURN(false);
/* 建立新线程,从 handle_connection 开始执行 */
mysql_thread_create(handle_connection)
Global_THD_manager::get_instance()->inc_thread_created();
用户连接线程栈如下
handle_connection
my_thread_init
for (;;)
THD *thd= init_new_thd(channel_info);
/* 第一次循环执行prepare,后面跳过 */
thd_prepare_connection(thd)
login_connection
check_connection
/* 权限认证 */
acl_authenticate
prepare_new_connection_state
/* 第二次循环开始,执行do_command */
while (thd_connection_alive(thd))
do_command(thd)
end_connection(thd);
close_connection(thd, 0, false, false);
thd->release_resources();
/* 进入 thread cache,等待新连接复用 */
channel_info= Per_thread_connection_handler::block_until_new_connection();
if (channel_info == NULL)
break;
参数 thread_cache_size 控制了 thread_cache 的大小, 设为0时关闭 thread_cache,不缓存空闲thread
mysql> show status like 'Threads%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_cached | 1 |
| Threads_connected | 1 |
| Threads_created | 2 |
| Threads_running | 1 |
+-------------------+-------+
4 rows in set (0.02 sec)
Threads_cached:缓存的 thread,新连接建立时,优先使用cache中的thread
Threads_connected:已连接的 thread
Threads_created:建立的 thread 数量
Threads_running:running状态的 thread 数量
Threads_created = Threads_cached + Threads_connected
Threads_running <= Threads_connected
MySQL 建立新连接非常消耗资源,频繁使用短连接,又没有其他组件实现连接池时,可以适当提高 thread_cache_size,降低新建连接的开销
channel_info | 连接信息 |
---|---|
waiting_channel_info_list | channel_info的等待链表 |
COND_thread_cache | block线程被唤醒的信号量,唤醒后从waiting_channel_info_list取出头部channel_info建立新连接 |
blocked_pthread_count | 被block的线程数 |
max_blocked_pthreads | 被block的最大线程数,也就是thread_cache_size |
wake_pthread | waiting_channel_info_list的链表长度 |
handle_connection 线程结束之前,会执行 block_until_new_connection,尝试进入 thread cache 等待其他连接复用
如果 blocked_pthread_count < max_blocked_pthreads,blocked_pthread_count++,然后等待被 COND_thread_cache 唤醒,唤醒之后 blocked_pthread_count– , 返回 waiting_channel_info_list 中的一个 channel_info ,进行 handle_connections 的下一个循环
检查是否 blocked_pthread_count > wake_pthread (有足够的block状态线程用来唤醒) 如有 插入 channel_info 进入 waiting_channel_info_list,并发出 COND_thread_cache 信号量
除了参数 max_user_connections 限制每个用户的最大连接数,还可以对每个用户制定更细致的限制
以下四个限制保存在mysql.user表中
GRANT
priv_type [(column_list)]
[, priv_type [(column_list)]] ...
ON [object_type] priv_level
TO user [auth_option] [, user [auth_option]] ...
[REQUIRE {NONE | tls_option [[AND] tls_option] ...}]
[WITH {GRANT OPTION | resource_option} ...]
resource_option: {
| MAX_QUERIES_PER_HOUR count
| MAX_UPDATES_PER_HOUR count
| MAX_CONNECTIONS_PER_HOUR count
| MAX_USER_CONNECTIONS count
}
ALTER USER 'jeffrey'@'localhost' WITH MAX_QUERIES_PER_HOUR 90;
保存用户连接限制的结构体,作为成员属性存在于各个和连接限制相关的类
typedef struct user_resources {
/* MAX_QUERIES_PER_HOUR */
uint questions;
/* MAX_UPDATES_PER_HOUR */
uint updates;
/* MAX_CONNECTIONS_PER_HOUR */
uint conn_per_hour;
/* MAX_USER_CONNECTIONS */
uint user_conn;
enum {QUERIES_PER_HOUR= 1, UPDATES_PER_HOUR= 2, CONNECTIONS_PER_HOUR= 4,
USER_CONNECTIONS= 8};
uint specified_limits;
} USER_RESOURCES;
保存用户认证相关信息的类 USER_RESOURCES 是它的成员属性
ACl_USER 对象保存在数组 acl_users 中,每次mysqld启动时,从mysql.user表中读取数据,初始化 acl_users,初始化过程在函数 acl_load 中
调用栈如下
main
mysqld_main
acl_init
acl_reload
acl_load
保存用户资源使用的结构体,建立连接时,调用 get_or_create_user_conn 为 THD 绑定 USER_CONN 对象
每个用户第一个连接创建时,建立一个新对象,存入 hash_user_connections
第二个连接开始,从 hash_user_connections 取出 USER_CONN 对象和 THD 绑定
同一个用户的连接,THD 都和同一个 USER_CONN 对象绑定
typedef struct user_conn {
char *user;
char *host;
ulonglong reset_utime;
size_t len;
/* 当前用户连接数 */
uint connections;
/* 每小时连接数,请求数,更新数使用情况(实时更新) */
uint conn_per_hour, updates, questions;
/* 本用户资源限制 */
USER_RESOURCES user_resources;
} USER_CONN;
get_or_create_user_conn 调用栈如下
handle_connection
thd_prepare_connection(thd)
login_connection
check_connection
acl_authenticate
get_or_create_user_conn
3.1.4 资源限制在源码中的位置
MAX_USER_CONNECTIONS MAX_CONNECTIONS_PER_HOUR | check_for_max_user_connections |
---|---|
MAX_QUERIES_PER_HOUR MAX_UPDATES_PER_HOUR | check_mqh |
调用栈如下
handle_connection
thd_prepare_connection(thd)
login_connection
check_connection
acl_authenticate
check_for_max_user_connections
do_command
dispatch_command
mysql_parse
check_mqh