MySQL线程池(THREAD POOL)的原理

MySQL常用(目前线上使用)的线程调度方式是one-thread-per-connection(每连接一个线程),server为每一个连接创建一个线程来服务,连接断开后,这个线程进入thread_cache或者直接退出(取决于thread_cache设置及系统当前已经cache的线程数目),one-thread-per-connection调度的好处是实现简单,而且能够在系统没有遇到瓶颈之前保证较小的响应时间,比较适合活跃的长连接的应用场景,而在大量短连接或者高并发情况下,one-thread-per-connection需要创建/调度大量的线程,产生较高的的context-switch代价,从而使得系统性能下降 

为了解决这个问题,Oracle和MariaDB分别推出了threadpool方案,目前Oracle的threadpool实现为plugin方式,并且只添加到在Enterprise版本中,没有公布代码,MariaDB threadpool在5.5版本中引入,我们一直密切关注社区动态并在第一时间测试了MariaDB threapool性能,并且发现了一些其中的问题,比如:要像发挥线程池的优势,需要尽量控制线程池中线程数目,否则会退化成one-thread-per-connection,而如果严格控制线程池中线程数据,可能会出现调度上的死锁,percona在移植MariaDB threadpool的实现后进一步优化了线程池性能,通过引入优先队列很好解决了这个问题,经测试效果明显,因此我们将这个特性port到了AliMySQL中

实现简介

1、threadpool中worker线程处理单位为一个statement,而不是one-thread-per-connection对应的一个连接;当worker线程处理完A连接发送来的一个sql后,A连接没有立刻发送第二条sql,worker线程回去服务其它连接发送来的sql,因此worker线程工作效率更高,系统需要的线程数也更少 
2、threadpool本质上是一个生产者-消费者模型,为了减小竞争,threadpool被划分为N个group(n默认为cpu核心数),连接发送的sql根据连接id分配到不同的group中,因此,同一个连接发送的所有sql是被同一个group中的worker线程处理的 
3、每个group都有2个任务队列, 即优先队列和普通队列 ,如果一个sql所在的事务已经开启,则将任务放到优先队列中,否则放到普通队列中,worker线程优先从优先队列中取任务执行,当优先队列为空则从普通队列取任务执行,这个可以保证已经开启的事务优先得到执行,从而尽早释放其占用的资源(主要是锁),可以有效减小响应时间,别且避免调度上的死锁(A和B被分到不同的group中,A事务已经开启,并且获得了锁,可能无法立即得到调度执行,B事务依赖A事务释放锁资源,但是先于A得到调度) 
4、每个group中每个worker线程地位一样,如果遇到任务队列为空的情况,线程会调用epoll_wait批量取任务 
5、threadpool额外创建了一个timer线程,每隔一段时间检查一遍所有的group,如果发现group出现异常(堵塞/超时/worker线程数目不够),及时唤醒线程

关于MySQL线程调度方案接口以及threadpool实现细节可以参考之前我写的两篇博客: mysql thread sheduler 源码分析 , mariadb 5.5 threadpool 源码分析


+-------------------------------+--------------+
| Variable_name                 | Value        |
+-------------------------------+--------------+
| thread_pool_high_prio_mode    | transactions |
| thread_pool_high_prio_tickets | 4294967295   |
| thread_pool_idle_timeout      | 60           |
| thread_pool_max_threads       | 100000       |
| thread_pool_oversubscribe     | 3            |
| thread_pool_size              | 24           |
| thread_pool_stall_limit       | 500          |
+-------------------------------+--------------+
7 rows in set (0.00 sec)


threadpool相关参数> thread_pool_high_prio_mode 

有三个取值:transactions / statements / none 
transactions(default): 使用优先队列和普通队列,对于事务已经开启的statement,放到优先队列中,否则放到普通队列中 
statements:只使用优先队列 
none: 只是用普通队列,本质上和statements相同,都是只是用一个队列

thread_pool_high_prio_tickets 
取值0~4294967295,当开启了优先队列模式后(thread_pool_high_prio_mode=transactions),每个连接最多允许thread_pool_high_prio_tickets次被放到优先队列中,之后放到普通队列中,默认为4294967295

thread_pool_idle_timeout 
worker线程最大空闲时间,单位为秒,超过限制后会退出,默认60

thread_pool_max_threads 
threadpool中最大线程数目,所有group中worker线程总数超过该限制后不能继续创建更多线程,默认100000

thread_pool_oversubscribe 
一个group中线程数过载限制,当一个group中线程数超过次限制后,继续创建worker线程会被延迟,默认3

thread_pool_size 
threadpool中group数量,默认为cpu核心数,server启动时自动计算

thread_pool_stall_limit 
timer线程检测间隔,单位为毫秒,默认500


-----------------------------------------------------------------------------------------------------------------------------------------------



线程池是MySQL5.6企业版的一个核心功能,对于服务器应用而言,无论是web应用服务还是DB服务,高并发请求始终是一个绕不开的话题。应用发起一个对数据库的操作时,在整个应用中是一个不小的开销,从建立连接之初,CPU要给它划分一定的thread stack,然后进行用户身份认证,建立上下文信息,最后请求完成,关闭连接,同时释放资源,可以称的上是秒级的过程,当有大量请求并发访问时,一定伴随着资源的不断创建和释放,导致资源利用率低,降低了服务质量。线程池是一种通用的技术,通过预先创建一定数量的线程,当有请求达到时,线程池分配一个线程提供服务,请求结束后,该线程又去服务其他请求。 通过这种方式,避免了线程和内存对象的频繁创建和释放,降低了服务端的并发度,减少了上下文切换和资源的竞争,提高资源利用效率。所有服务的线程池本质都是位了提高资源利用效率,并且实现方式也大体相同。本文主要说明MySQL线程池的实现原理。



mysql线程池,就是I/O多路复用的体现。至于I/O多路复用是什么,请查看http://blog.csdn.net/stubborn_cow/article/details/50247269


在MySQL5.6出现以前,MySQL处理连接的方式是One-Connection-Per-Thread,即对于每一个数据库连接,MySQL-Server都会创建一个独立的线程服务,请求结束后,销毁线程。再来一个连接请求,则再创建一个连接,结束后再进行销毁。这种方式在高并发情况下,会导致线程的频繁创建和释放。当然,通过thread-cache,我们可以将线程缓存起来,以供下次使用,避免频繁创建和释放的问题,但是无法解决高连接数的问题。One-Connection-Per-Thread方式随着连接数暴增,导致需要创建同样多的服务线程,高并发线程意味着高的内存消耗,更多的上下文切换(cpu cache命中率降低)以及更多的资源竞争,导致服务出现抖动。相对于One-Thread-Per-Connection方式,一个线程对应一个连接,Thread-Pool实现方式中,线程处理的最小单位是statement(语句),一个线程可以处理多个连接的请求。这样,在保证充分利用硬件资源情况下(合理设置线程池大小),可以避免瞬间连接数暴增导致的服务器抖动。
调度方式实现
MySQL-Server同时支持3种连接管理方式,包括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);
连接管理流程
1.通过poll监听mysql端口的连接请求
2.收到连接后,调用accept接口,创建通信socket
3.初始化thd实例,vio对象等
4.根据thread_handling方式设置,初始化thd实例的scheduler函数指针
5.调用scheduler特定的add_connection函数新建连接
下面代码展示了scheduler_functions模板和线程池对模板回调函数的实现,这个是多种连接管理的核心。
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
};
线程池的相关参数
1.thread_handling:表示线程池模型。
2.thread_pool_size:表示线程池的group个数,一般设置为当前CPU核心数目。理想情况下,一个group一个活跃的工作线程,达到充分利用CPU的目的。
3.thread_pool_stall_limit:用于timer线程定期检查group是否“停滞”,参数表示检测的间隔。
4.thread_pool_idle_timeout:当一个worker空闲一段时间后会自动退出,保证线程池中的工作线程在满足请求的情况下,保持比较低的水平。
5.thread_pool_oversubscribe:该参数用于控制CPU核心上“超频”的线程数。这个参数设置值不含listen线程计数。
6.threadpool_high_prio_mode:表示优先队列的模式。
线程池实现

上面描述了Mysql-Server如何管理连接,这节重点描述线程池的实现框架,以及关键接口。如图1

每一个绿色的方框代表一个group,group数目由thread_pool_size参数决定。每个group包含一个优先队列和普通队列,包含一个listener线程和若干个工作线程,listener线程和worker线程可以动态转换,worker线程数目由工作负载决定,同时受到thread_pool_oversubscribe设置影响。此外,整个线程池有一个timer线程监控group,防止group“停滞”。
关键接口
1. tp_add_connection[处理新连接]
1)  创建一个connection对象
2)  根据thread_id%group_count确定connection分配到哪个group
3)  将connection放进对应group的队列
4)  如果当前活跃线程数为0,则创建一个工作线程
2. worker_main[工作线程]
1)  调用get_event获取请求
2)  如果存在请求,则调用handle_event进行处理
3)  否则,表示队列中已经没有请求,退出结束。
3. get_event[获取请求]
1)  获取一个连接请求
2)  如果存在,则立即返回,结束
3)  若此时group内没有listener,则线程转换为listener线程,阻塞等待
4)  若存在listener,则将线程加入等待队列头部
5)  线程休眠指定的时间(thread_pool_idle_timeout)
6)  如果依然没有被唤醒,是超时,则线程结束,结束退出
7)  否则,表示队列里有连接请求到来,跳转1
备注:获取连接请求前,会判断当前的活跃线程数是否超过了
thread_pool_oversubscribe+1,若超过了,则将线程进入休眠状态。
4. handle_event[处理请求]
1)  判断连接是否进行登录验证,若没有,则进行登录验证
2)  关联thd实例信息
3)  获取网络数据包,分析请求
4)  调用do_command函数循环处理请求
5)  获取thd实例的套接字句柄,判断句柄是否在epoll的监听列表中
6)  若没有,调用epoll_ctl进行关联
7)  结束
5.listener[监听线程]
1)  调用epoll_wait进行对group关联的套接字监听,阻塞等待
2)  若请求到来,从阻塞中恢复
3)  根据连接的优先级别,确定是放入普通队列还是优先队列
4)  判断队列中任务是否为空
5)  若队列为空,则listener转换为worker线程
6)  若group内没有活跃线程,则唤醒一个线程
备注:这里epoll_wait监听group内所有连接的套接字,然后将监听到的连接
请求push到队列,worker线程从队列中获取任务,然后执行。
6. timer_thread[监控线程]
1)  若没有listener线程,并且最近没有io_event事件
2)  则创建一个唤醒或创建一个工作线程
3)  若group最近一段时间没有处理请求,并且队列里面有请求,则
4)  表示group已经stall,则唤醒或创建线程
备注:timer的作用是避免group处于stall状态

7.tp_wait_begin[进入等待状态流程]
1)  活跃线程数减1,等待线程数加1
2)  若活跃线程数为0,并且任务队列不为空,或者没有监听线程,则
3)  唤醒或创建一个线程
备注:waiting_threads这里面的线程是空闲线程,并非等待线程,所谓空
闲线程是随时可以处理任务的线程,而等待线程则是因为等待锁,或等待io操
作等无法处理任务的线程。
8.tp_wait_end[结束等待状态流程]
1)  设置connection的waiting状态为false
2)  活跃线程数加1,等待线程数减1
线程池与连接池
连接池通常实现在Client端,是指应用(??户端)创建预先创建一定的连接,利用这些连接服务于客户端所有的DB请求。如果某一个时刻,空闲的连接数小于DB的请求数,则需要将请求排队,等待空闲连接处理。通过连接池可以复用连接,避免连接的频繁创建和释放,从而减少请求的平均响应时间,并且在请求繁忙时,通过请求排队,可以缓冲应用对DB的冲击。线程池实现在server端,通过创建一定数量的线程服务DB请求,相对于one-conection-per-thread的一个线程服务一个连接的方式,线程池服务的最小单位是语句,即一个线程可以对应多个活跃的连接。通过线程池,可以将server端的服务线程数控制在一定的范围,减少了系统资源的竞争和线程上下文切换带来的消耗,同时也避免出现高连接数导致的高并发问题。连接池和线程池相辅相成,通过连接池可以减少连接的创建和释放,提高请求的平均响应时间,并能很好地控制一个应用的DB连接数,但无法控制整个应用集群的连接数规模,从而导致高连接数,通过线程池则可以很好地应对高连接数,保证server端能提供稳定的服务。如图2所示,每个web-server端维护了3个连接的连接池,对于连接池的每个连接实际不是独占db-server的一个worker,而是可能与其他连接共享。这里假设db-server只有3个group,每个group只有一个worker,每个worker处理了2个连接的请求。

图 2(连接池与线程池框架图)

线程池优化

1.优先队列

由于一个group会同时处理多个连接,但多个连接不是对等的。比如,有的连接是第一次发送请求;而有的连接对应的事务已经开启,并且持有了部分锁资源。为了减少锁资源争用,后者显然应该比前者优先处理,以达到尽早释放锁资源的目的。因此在group里面,可以添加一个优先级队列,将已经持有锁的连接发起的请求放入优先队列,工作线程首先从优先队列获取任务执行。

2.大查询处理

假设一种场景,某个group里面的连接都是大查询,那么group里面的工作线程数很快就会达到thread_pool_oversubscribe参数设置值,对于后续的连接请求,则会响应不及时(没有更多的连接来处理),这时候group就发生了stall。通过前面分析知道,timer线程会定期检查这种情况,并创建一个新的worker线程来处理请求。如果长查询来源于业务请求,则此时所有group都面临这种问题,此时主机可能会由于负载过大,导致hang住的情况。这种情况线程池本身无能为力,因为源头可能是烂SQL并发,或者SQL没有走对执行计划导致,通过其他方法,比如SQL高低水位限流或者SQL过滤手段可以应急处理。但是,还有另外一种情况,就是dump任务。很多下游依赖于数据库的原始数据,通常通过dump命令将数据拉到下游,而这种dump任务通常都是耗时比较长,所以也可以认为是大查询。如果dump任务集中在一个group内,并导致其他正常业务请求无法立即响应,这个是不能容忍的,因为此时数据库并没有压力,只是因为采用了线程池策略,才导致了请求响应不及时,为了解决这个问题,我们将group中处理dump任务的线程不计入thread_pool_oversubscribe累计值,避免上述问题。


  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MySQL 5.6及以上版本支持线程池,可以通过以下步骤配置: 1. 打开MySQL配置文件my.cnf,在[mysqld]下添加如下配置: ``` thread_handling=pool-of-threads thread_pool_max_threads=100 thread_pool_idle_timeout=60 ``` - `thread_handling` 指定线程池的工作模式,pool-of-threads表示启用线程池; - `thread_pool_max_threads` 指定线程池最多可以创建的线程数; - `thread_pool_idle_timeout` 指定线程池中空闲线程的超时时间,单位为秒。 2. 重启MySQL服务,使配置生效。 注意事项: - 线程池模式不适用于所有情况,需要根据具体场景进行测试和评估。 - 在高并发情况下,线程池的性能表现通常优于线程模式,但是在低并发情况下,线程池的性能可能会有所下降。 - 线程池的配置需要根据服务器硬件性能和应用负载情况进行调整,不同的应用场景可能需要不同的配置参数。 ### 回答2: MySQL线程池MySQL数据库中的一个重要配置参数,旨在提高数据库的并发处理能力。 要配置MySQL线程池,需要修改MySQL的配置文件my.cnf。以下是配置MySQL线程池的步骤: 1. 打开MySQL的配置文件my.cnf。在Linux系统上,该文件通常位于/etc/mysql/my.cnf。 2. 在文件中找到[mysqld]或[mysql]的段落,这是数据库服务器的配置段落。 3. 在[mysqld]或[mysql]段落下添加以下配置行: thread_handling = pool-of-threads thread_pool_size = 数字 - thread_handling用于指定MySQL线程池的处理方式,"pool-of-threads"表示启用线程池。 - thread_pool_size用于指定线程池的大小,即同时处理连接的最大线程数。根据系统的性能,可以设置适当的数值。 4. 保存并关闭my.cnf文件。 5. 重启MySQL服务,使配置生效。 配置MySQL线程池后,数据库服务器将使用线程池来处理客户端的连接请求。线程池会根据线程池大小的配置,同时处理多个连接,提高数据库的并发处理能力。 需要注意的是,在配置MySQL线程池时,应根据系统的实际情况来设置线程池的大小。如果线程池设置过大,可能导致资源浪费;如果线程池设置过小,可能影响并发处理能力。因此,需要根据数据库服务器的硬件配置、负载以及应用程序的特点来调整线程池的大小,以达到最佳性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值