揭秘MySQL线程池内幕( 二 )


  • 线程组
  • Worker线程
  • Check Stall机制
  • 任务队列
  • Listener线程
对于上面列出的几个方面 , 后文将会展开介绍 。
线程组
MySQL线程池在初始化的时候根据宿主机的CPU核心数设置thread_pool_size , 这也就是线程池的线程组的个数 。每个线程组在初始化之后会通过底层的IO库分配一个网络特殊的句柄与之关联 , IO库可以通过这个句柄监听与之绑定的socket句柄就绪的IO任务 , 线程组的结构体定义如下:
struct thread_group_t{ mysql_mutex_t mutex; connection_queue_t queue;//低优先级任务队列 connection_queue_t high_prio_queue;//高优先级任务队列 worker_list_t waiting_threads; //代表当前线程没有任务的时候进入等待队里 worker_thread_t *listener;//读取网络任务线程 pthread_attr_t *pthread_attr; int pollfd;//特殊的句柄 int thread_count;//线程组中的线程数 int active_thread_count;//当前活跃的线程 int connection_count;//分配给当前线程组的连接 int waiting_thread_count;//代表的是当前线程在执行命令的时候处于等待状态 /* Stats for the deadlock detection timer routine.*/ int io_event_count;//待处理任务数 , 从句柄中获取 int queue_event_count;//从队列移除的网络任务数 , 意味着网络任务被处理ulonglong last_thread_creation_time;//上一次创建工作线程的时候 int shutdown_pipe[2]; bool shutdown; bool stalled; } MY_ALIGNED(512);线程池由多个线程组构成 , 线程池的细节基本都在线程组内 。
worker线程
线程组内有0个或多个线程 , 这里与Netty有些不同 , Netty中有固定的线程用于轮训IO事件 , 工作线程只负责处理IO任务 , 而在MySQL线程池中listener只是一种角色 , 每个线程的角色可以是listener或者是worker , 工作线程为listener的时候负责从poolfd中读取就绪IO任务 , 处于worker角色的时候负责处理这些IO任务 , 我们需要区分工作线程的以下几种状态状态:
  • 活跃状态:当工作线程处于正在处理任务且的状态且未被阻塞的状态 , 这意味着工作线程将会消耗CPU , 增加系统的负载 。如果worker线程将自己设置为listener则不算进线程组的活跃线程状态数 。
  • 空闲状态:由于没有任务处理而被处于的空闲状态 。
  • 等待状态:如果工作线程在执行命令的过程中由于IO、锁、条件、sleep等需要等待则线程池将被通知并且将这些工作线程记作等待状态 。
在线程组中 , 关于线程的计数有如下关系:
thread_count = active_thread_count + waiting_thread_count + waiting_threads.length + listener.lengththread_count代表线程组中的总线程数 , active_thread_count代表当前正在工作且未被阻塞的线程数 , waiting_thread_count代表的是工作线程任务的过程中被阻塞的个数 , 而waiting_threads代表空闲线程列表 。
在MySQL线程池中 , 线程组中busy的线程数是active_thread_count与waiting_thread_count的总和 , 因为这些线程此时都不能处理新的任务 , 因此被认为是繁忙的 。如果处于busy状态的线程数大于一定值则线程组被任务是太繁忙(too many active)了 , 这会用于决策普通优先级的任务是否能得到及时的处理 , 这个值被定义为:
thread_pool_oversubscribe + 1默认值也就是4 。如果active_thread_count数大于等于一定值(同上算法为4) , 则线程组被认为是太活跃(too busy)了 , 此时意味着可能过饱满的CPU负载 , 这个指标用于决策线程组是否还继续执行普通优先级的任务 , 上面的逻辑总结一句话为:
在正常工作的情况下 , 当工作线程检索任务的时候 , 如果线程组太活跃(too many active)则即使有任务工作线程也不会执行 , 如果不是太繁忙(too busy)才会考虑高优先级的任务 , 对于低优先级的任务只有当线程组不是太繁忙(too busy)的时候才会执行 。
注意上面的条件 , 因此线程池对系统负载具有一定的保护作用 , 那么问题来了 , 如果存在一些耗时任务(如耗时查询) , 会不会导致后来任务被延迟处理?会不会有时候觉得SQL写得没问题 , 但是却莫名其妙的Long SQL?这就是下面要介绍的Check Stall机制 。
Check Stall机制
如果后来的IO任务被前面执行时间过长的任务影响了怎么办?这必然会导致一些无辜的任务(或是一个简单的INSERT操作 , 之所以举INSERT的例子是因为INSERT通常很快)被影响 , 结果是有可能会被延迟处理 。线程池中有一个Timer Thread , 类似我们很多系统里面的Timeout Thread线程 , 这个线程每隔一定时间间隔就会进行一次迭代 , 迭代中做的事情包括如下两个部分:


推荐阅读