MySQL 线程池总结
One-Connection-Per-Thread
,即对于每一个数据库连接,MySQL-Server都会创建一个独立的线程服务,请求结束后,销毁线程。再来一个连接请求,则再创建一个连接,结束后再进行销毁。这种方式在高并发情况下,会导致线程的频繁创建和释放。当然,通过 thread-cache,我们可以将线程缓存起来,以供下次使用,避免频繁创建和释放的问题,但是无法解决高连接数的问题。One-Connection-Per-Thread
方式随着连接数暴增,导致需要创建同样多的服务线程,高并发线程意味着高的内存消耗,更多的上下文切换(cpu cache命中率降低)以及更多的资源竞争,导致服务出现抖动。相对于 One-Thread-Per-Connection
方式,一个线程对应一个连接,Thread-Pool
实现方式中,线程处理的小单位是statement(语句),一个线程可以处理多个连接的请求。这样,在保证充分利用硬件资源情况下(合理设置线程池大小),可以避免瞬间连接数暴增导致的服务器抖动。调度方式实现
No-Threads
,One-Thread-Per-Connection
和 Pool-Threads
。No-Threads 表示处理连接使用主线程处理,不额外创建线程,这种方式主要用于调试; One-Thread-Per-Connection 是线程池出现以前常用的方式,为每一个连接创建一个线程服务; Pool-Threads 则是本文所讨论的线程池方式。Mysql-Server通过一组函数指针来同时支持3种连接管理方式,对于特定的方式,将函数指针设置成特定的回调函数,连接管理方式通过thread_handling参数控制,代码如下:
if (thread_handling <= SCHEDULER_ONE_THREAD_PER_CONNECTION)
one_thread_per_connection_scheduler(thread_scheduler,&max_connections, &connection_count);
else if (thread_handling == SCHEDULER_NO_THREADS)
one_thread_scheduler(thread_scheduler);
else
pool_of_threads_scheduler(thread_scheduler, &max_connections,&connection_count);
连接管理流程
struct scheduler_functions
{
uint max_threads;
uint *connection_count;
ulong *max_connections;
bool (*init)(void);
bool (*init_new_connection_thread)(void);
void (*add_connection)(THD *thd);
void (*thd_wait_begin)(THD *thd, int wait_type);
void (*thd_wait_end)(THD *thd);
void (*post_kill_notification)(THD *thd);
bool (*end_thread)(THD *thd, bool cache_thread);
void (*end)(void);
};
static scheduler_functions tp_scheduler_functions=
{
0, // max_threads
NULL,
NULL,
tp_init, // init
NULL, // init_new_connection_thread
tp_add_connection, // add_connection
tp_wait_begin, // thd_wait_begin
tp_wait_end, // thd_wait_end
tp_post_kill_notification, // post_kill_notification
NULL, // end_thread
tp_end // end
};
线程池的相关参数
thread_handling: 表示线程池模型。thread_pool_size:表示线程池的group个数,一般设置为当前CPU核心数目。理想情况下,一个group一个活跃的工作线程,达到充分利用CPU的目的。thread_pool_stall_limit:用于timer线程定期检查group是否“停滞”,参数表示检测的间隔。thread_pool_idle_timeout:当一个worker空闲一段时间后会自动退出,保证线程池中的工作线程在满足请求的情况下,保持比较低的水平。thread_pool_oversubscribe:该参数用于控制CPU核心上“超频”的线程数。这个参数设置值不含listen线程计数。threadpool_high_prio_mode:表示优先队列的模式。线程池实现
关键接口
创建一个connection对象 根据thread_id%group_count确定connection分配到哪个group 将connection放进对应group的队列 如果当前活跃线程数为0,则创建一个工作线程
调用get_event获取请求 如果存在请求,则调用handle_event进行处理 否则,表示队列中已经没有请求,退出结束。
获取一个连接请求 如果存在,则立即返回,结束 若此时group内没有listener,则线程转换为listener线程,阻塞等待 若存在listener,则将线程加入等待队列头部 线程休眠指定的时间(thread_pool_idle_timeout) 如果依然没有被唤醒,是超时,则线程结束,结束退出 否则,表示队列里有连接请求到来,跳转1
备注:获取连接请求前,会判断当前的活跃线程数是否超过thread_pool_oversubscribe+1,若超过,则将线程进入休眠状态。
判断连接是否进行登录验证,若没有,则进行登录验证 关联thd实例信息 获取网络数据包,分析请求 调用do_command函数循环处理请求 获取thd实例的套接字句柄,判断句柄是否在epoll的监听列表中 若没有,调用epoll_ctl进行关联 结束
调用epoll_wait进行对group关联的套接字监听,阻塞等待 若请求到来,从阻塞中恢复 根据连接的优先级别,确定是放入普通队列还是优先队列 判断队列中任务是否为空 若队列为空,则listener转换为worker线程 若group内没有活跃线程,则唤醒一个线程
备注:这里epoll_wait监听group内所有连接的套接字,然后将监听到的连接请求push到队列,worker线程从队列中获取任务,然后执行。
若没有listener线程,并且近没有io_event事件 则创建一个唤醒或创建一个工作线程 若group近一段时间没有处理请求,并且队列里面有请求,则 表示group已经stall,则唤醒或创建线程 检查是否有连接超时
备注:timer线程通过调用check_stall判断group是否处于stall状态,通过调用timeout_check检查客户端连接是否超时。
active_thread_count减1,waiting_thread_count加1 设置connection->waiting= true 若活跃线程数为0,并且任务队列不为空,或者没有监听线程,则 唤醒或创建一个线程 tp_wait_end[结束等待状态流程]
设置connection的waiting状态为false active_thread_count加1,waiting_thread_count减1
waiting_threads这个list里面的线程是空闲线程,并非等待线程,所谓空闲线程是随时可以处理任务的线程,而等待线程则是因为等待锁,或等待io操作等无法处理任务的线程。 tp_wait_begin和tp_wait_end的主要作用是由于汇报状态,即使更新active_thread_count和waiting_thread_count的信息。
线程池与连接池
one-conection-per-thread
的一个线程服务一个连接的方式,线程池服务的小单位是语句,即一个线程可以对应多个活跃的连接。通过线程池,可以将 server 端的服务线程数控制在一定的范围,减少了系统资源的竞争和线程上下文切换带来的消耗,同时也避免出现高连接数导致的高并发问题。线程池优化
调度死锁解决
大查询处理
参考文档
相关文章