《Mysql实例MySQL的线程池原理学习教程》要点:
本文介绍了Mysql实例MySQL的线程池原理学习教程,希望对您有用。如果有疑问,可以联系我们。
线程池是Mysql5.6的一个核心功能,对于服务器应用而言,无论是web应用服务还是DB服务,高并发哀求始终是一个绕不开的话题.当有大量哀求并发访问时,一定伴随着资源的不断创建和释放,导致资源利用率低,降低了服务质量.线程池是一种通用的技术,通过预先创建一定数量的线程,当有哀求达到时,线程池分配一个线程提供服务,哀求结束后,该线程又去服务其他哀求. 通过这种方式,避免了线程和内存对象的频繁创建和释放,降低了服务端的并发度,减少了上下文切换和资源的竞争,提高资源利用效率.所有服务的线程池本质都是位了提高资源利用效率,并且实现方式也大体相同.本文主要说明Mysql线程池的实现原理.MYSQL入门
在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入门
MYSQL入门
调剂方式实现MYSQL入门
Mysql-Server同时支持3种连接管理方式,包含No-Threads,One-Thread-Per-Connection和Pool-Threads.No-Threads表示处理连接使用主线程处理,不额外创建线程,这种方式主要用于调试;One-Thread-Per-Connection是线程池出现以前最常用的方式,为每一个连接创建一个线程服务;Pool-Threads则是本文所讨论的线程池方式.Mysql-Server通过一组函数指针来同时支持3种连接管理方式,对于特定的方式,将函数指针设置成特定的回调函数,连接管理方式通过thread_handling参数控制,代码如下:MYSQL入门
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);
连接治理流程MYSQL入门
通过poll监听mysql端口的连接哀求
收到连接后,调用accept接口,创建通信socket
初始化thd实例,vio对象等
根据thread_handling方式设置,初始化thd实例的scheduler函数指针
调用scheduler特定的add_connection函数新建连接
下面代码展示了scheduler_functions模板和线程池对模板回调函数的实现,这个是多种连接管理的核心.MYSQL入门
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 };
线程池的相关参数MYSQL入门
线程池实现MYSQL入门
上面描述了Mysql-Server如何管理连接,这节重点描述线程池的实现框架,以及症结接口.如图1
MYSQL入门
MYSQL入门
每一个绿色的方框代表一个group,group数目由thread_pool_size参数决定.每个group包括一个优先队列和普通队列,包括一个listener线程和若干个工作线程,listener线程和worker线程可以动态转换,worker线程数目由工作负载决定,同时受到thread_pool_oversubscribe设置影响.此外,整个线程池有一个timer线程监控group,防止group“停滞”.MYSQL入门
症结接口MYSQL入门
1. tp_add_connection[处置新连接]MYSQL入门
1) 创立一个connection对象MYSQL入门
2) 根据thread_id%group_count确定connection分派到哪个groupMYSQL入门
3) 将connection放进对应group的队列MYSQL入门
4) 如果当前活跃线程数为0,则创立一个工作线程MYSQL入门
2. worker_main[工作线程]MYSQL入门
1) 调用get_event获取哀求MYSQL入门
2) 如果存在哀求,则调用handle_event进行处理MYSQL入门
3) 否则,表示队列中已经没有哀求,退出结束.MYSQL入门
3. get_event[获取哀求]MYSQL入门
1) 获取一个连接哀求MYSQL入门
2) 如果存在,则立即返回,停止MYSQL入门
3) 若此时group内没有listener,则线程转换为listener线程,阻塞期待MYSQL入门
4) 若存在listener,则将线程参加等待队列头部MYSQL入门
5) 线程休眠指定的光阴(thread_pool_idle_timeout)MYSQL入门
6) 如果依然没有被叫醒,是超时,则线程结束,结束退出MYSQL入门
7) 否则,表示队列里有连接哀求到来,跳转1MYSQL入门
备注:获取连接哀求前,会判断当前的活跃线程数是否超过了MYSQL入门
thread_pool_oversubscribe+1,若跨越了,则将线程进入休眠状态.MYSQL入门
4. handle_event[处理哀求]MYSQL入门
1) 断定连接是否进行登录验证,若没有,则进行登录验证MYSQL入门
2) 联系关系thd实例信息MYSQL入门
3) 获取网络数据包,分析哀求MYSQL入门
4) 调用do_command函数循环处理哀求MYSQL入门
5) 获取thd实例的套接字句柄,断定句柄是否在epoll的监听列表中MYSQL入门
6) 若没有,挪用epoll_ctl进行关联MYSQL入门
7) 停止MYSQL入门
5.listener[监听线程]MYSQL入门
1) 调用epoll_wait进行对group关联的套接字监听,阻塞期待MYSQL入门
2) 若哀求到来,从阻塞中恢复MYSQL入门
3) 依据连接的优先级别,确定是放入普通队列还是优先队列MYSQL入门
4) 断定队列中任务是否为空MYSQL入门
5) 若队列为空,则listener转换为worker线程MYSQL入门
6) 若group内没有活跃线程,则叫醒一个线程MYSQL入门
备注:这里epoll_wait监听group内所有连接的套接字,然后将监听到的连接MYSQL入门
哀求push到队列,worker线程从队列中获取任务,然后执行.MYSQL入门
6. timer_thread[监控线程]MYSQL入门
1) 若没有listener线程,并且最近没有io_event变乱MYSQL入门
2) 则创立一个唤醒或创立一个工作线程MYSQL入门
3) 若group最近一段时间没有处理哀求,并且队列里面有哀求,则MYSQL入门
4) 表现group已经stall,则唤醒或创建线程MYSQL入门
5)反省是否有连接超时MYSQL入门
备注:timer线程通过挪用check_stall判断group是否处于stall状态,通过挪用timeout_check检查客户端连接是否超时.MYSQL入门
7.tp_wait_begin[进入期待状态流程]MYSQL入门
1) active_thread_count减1,waiting_thread_count加1MYSQL入门
2)设置connection->waiting= trueMYSQL入门
3) 若活跃线程数为0,而且任务队列不为空,或者没有监听线程,则MYSQL入门
4) 唤醒或创立一个线程MYSQL入门
8.tp_wait_end[结束期待状态流程]MYSQL入门
1) 设置connection的waiting状态为falseMYSQL入门
2) active_thread_count加1,waiting_thread_count减1MYSQL入门
备注:MYSQL入门
1)waiting_threads这个list里面的线程是空闲线程,并非等待线程,所谓空闲线程是随时可以处置任务的线程,而等待线程则是因为等待锁,或等待io操作等无法处置任务的线程.MYSQL入门
2)tp_wait_begin和tp_wait_end的主要作用是由于报告请示状态,即使更新active_thread_count和waiting_thread_count的信息.MYSQL入门
9. tp_init/tp_endMYSQL入门
分别挪用thread_group_init和thread_group_close来初始化和销毁线程池MYSQL入门
MYSQL入门
线程池与连接池MYSQL入门
连接池通常实现在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个连接的哀求.
MYSQL入门
MYSQL入门
线程池优化MYSQL入门
1.调度死锁办理MYSQL入门
引入线程池解决了多线程高并发的问题,但也带来一个隐患.假设,A,B两个事务被分配到不同的group中执行,A事务已经开始,并且持有锁,但由于A所在的group比较繁忙,导致A执行一条语句后,不能立即获得调度执行;而B事务依赖A事务释放锁资源,虽然B事务可以被调度起来,但由于无法获得锁资源,导致仍然需要等待,这就是所谓的调度死锁.由于一个group会同时处理多个连接,但多个连接不是对等的.比如,有的连接是第一次发送哀求;而有的连接对应的事务已经开启,并且持有了部分锁资源.为了减少锁资源争用,后者显然应该比前者优先处理,以达到尽早释放锁资源的目的.因此在group里面,可以添加一个优先级队列,将已经持有锁的连接,或者已经开启的事务的连接发起的哀求放入优先队列,工作线程首先从优先队列获取任务执行.MYSQL入门
2.大查询处置MYSQL入门
假设一种场景,某个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累计值,避免上述问题.MYSQL入门
one-connection-per-threadMYSQL入门
依据scheduler_functions的模板,我们也可以列出one-connection-per-thread方式的几个关键函数.MYSQL入门
static scheduler_functions con_per_functions= { max_connection+1, // max_threads NULL, NULL, NULL, // init Init_new_connection_handler_thread, // init_new_connection_thread create_thread_to_handle_connection, // add_connection NULL, // thd_wait_begin NULL, // thd_wait_end NULL, // post_kill_notification one_thread_per_connection_end, // end_thread NULL // end };
MYSQL入门
1.init_new_connection_handler_threadMYSQL入门
这个接口比拟简单,主要是调用pthread_detach,将线程设置为detach状态,线程结束后自动释放所有资源.MYSQL入门
2.create_thread_to_handle_connectionMYSQL入门
这个接口是处置新连接的接口,对于线程池而言,会从thread_id%group_size对应的group中获取一个线程来处置,而one-connection-per-thread方式则会判断是否有thread_cache可以使用,如果没有则新建线程来处置.具体逻辑如下:MYSQL入门
(1).判断缓存的线程数是否使用完(比拟blocked_pthread_count 和wake_pthread大小)MYSQL入门
(2).若还有缓存线程,将thd参加waiting_thd_list的队列,唤醒一个等待COND_thread_cache的线程MYSQL入门
(3).若没有,创建一个新的线程处置,线程的入口函数是do_handle_one_connectionMYSQL入门
(4).调用add_global_thread参加thd数组.MYSQL入门
3.do_handle_one_connectionMYSQL入门
这个接口被create_thread_to_handle_connection调用,处理哀求的主要实现接口.MYSQL入门
(1).循环调用do_command,从socket中读取网络包,而且解析执行;MYSQL入门
(2). 当远程客户端发送关闭连接COMMAND(好比COM_QUIT,COM_SHUTDOWN)时,退出循环MYSQL入门
(3).挪用close_connection关闭连接(thd->disconnect());MYSQL入门
(4).挪用one_thread_per_connection_end函数,确认是否可以复用线程MYSQL入门
(5).根据返回成果,确定退出工作线程还是继续循环执行命令.MYSQL入门
4.one_thread_per_connection_endMYSQL入门
断定是否可以复用线程(thread_cache)的主要函数,逻辑如下:MYSQL入门
(1).挪用remove_global_thread,移除线程对应的thd实例MYSQL入门
(2).挪用block_until_new_connection判断是否可以重用threadMYSQL入门
(3).判断缓存的线程是否跨越阀值,若没有,则blocked_pthread_count++;MYSQL入门
(4).阻塞期待条件变量COND_thread_cacheMYSQL入门
(5).被唤醒后,表示有新的thd必要重用线程,将thd从waiting_thd_list中移除,使用thd初始化线程的thd->thread_stackMYSQL入门
(6).调用add_global_thread参加thd数组.MYSQL入门
(7).如果可以重用,返回false,不然返回tureMYSQL入门
MYSQL入门
线程池与epollMYSQL入门
在引入线程池之前,server层只有一个监听线程,负责监听mysql端口和本地unixsocket的哀求,对于每个新的连接,都会分配一个独立线程来处理,因此监听线程的任务比较轻松,mysql通过poll或select方式来实现IO的多路复用.引入线程池后,除了server层的监听线程,每个group都有一个监听线程负责监听group内的所有连接socket的连接哀求,工作线程不负责监听,只处理哀求.对于overscribe为1000的线程池设置,每个监听线程需要监听1000个socket的哀求,监听线程采用epoll方式来实现监听.MYSQL入门
Select,poll,epoll都是IO多路复用机制,IO多路复用通过一种机制,可以监听多个fd(描述符),好比socket,一旦某个fd就绪(读就绪或写就绪),能够通知程序进行相应的读写操作.epoll相对于select和poll有了很大的改进,首先epoll通过epoll_ctl函数注册,注册时,将所有fd拷贝进内核,只拷贝一次不需要重复拷贝,而每次调用poll或select时,都需要将fd集合从用户空间拷贝到内核空间(epoll通过epoll_wait进行等待);其次,epoll为每个描述符指定了一个回调函数,当设备就绪时,唤醒等待者,通过回调函数将描述符加入到就绪链表,无需像select,poll方式采用轮询方式;最后select默认只支持1024个fd,epoll则没有限制,具体数字可以参考cat /proc/sys/fs/file-max的设置.epoll贯穿在线程池使用的过程中,下面我就epoll的创建,使用和销毁生命周期来描述epoll在线程中是如何使用的.MYSQL入门
线程池初始化,epoll通过epoll_create函数创建epoll文件描述符,实现函数是thread_group_init;
端口监听线程监听到哀求后,创建socket,并创建THD和connection对象,放在对应的group队列中;
工作线程获取该connection对象时,若还未登录,则进行登录验证
若socket还未注册到epoll,则调用epoll_ctl进行注册,注册方式是EPOLL_CTL_ADD,并将connection对象放入epoll_event结构体中
若是老连接的哀求,仍然需要调用epoll_ctl注册,注册方式是EPOLL_CTL_MOD
group内的监听线程调用epoll_wait来监听注册的fd,epoll是一种同步IO方式,所以会进行等待
哀求到来时,获取epoll_event结构体中的connection,放入到group中的队列
线程池销毁时,调用thread_group_close将epoll关闭.
备注:MYSQL入门
1.注册在epoll的fd,若哀求就绪,则将对应的event放入到events数组,并将该fd的事务类型清空,因此对于老的连接哀求,依然需要调用epoll_ctl(pollfd, EPOLL_CTL_MOD, fd, &ev)来注册.MYSQL入门
MYSQL入门
线程池函数挪用关系MYSQL入门
(1)创立epollMYSQL入门
tp_init->thread_group_init->tp_set_threadpool_size->io_poll_create->epoll_create
(2)封闭epollMYSQL入门
tp_end->thread_group_close->thread_group_destroy->close(pollfd)
(3)联系关系socket描述符MYSQL入门
handle_event->start_io->io_poll_associate_fd->io_poll_start_read->epoll_ctl
(4)处理连接哀求MYSQL入门
handle_event->threadpool_process_request->do_command->dispatch_command->mysql_parse->mysql_execute_command
(5)工作线程余暇时MYSQL入门
worker_main->get_event->pthread_cond_timedwait
期待thread_pool_idle_timeout后,退出.MYSQL入门
(6)监听epollMYSQL入门
worker_main->get_event->listener->io_poll_wait->epoll_wait
(7)端口监听线程MYSQL入门
main->mysqld_main->handle_connections_sockets->poll
MYSQL入门
one-connection-per-thread函数挪用关系MYSQL入门
(1) 工作线程等待哀求MYSQL入门
handle_one_connection->do_handle_one_connection->do_command-> my_net_read->net_read_packet->net_read_packet_header->net_read_raw_loop-> vio_read->vio_socket_io_wait->vio_io_wait->poll
备注:与线程池的工作线程有监听线程帮助其监听哀求不同,one-connection-per-thread方式的工作线程在空闲时,会调用poll阻塞等待网络包过来;MYSQL入门
而线程池的工作线程只需要专心处理哀求即可,所以使用也更充分.MYSQL入门
(2)端口监听线程
与线程池的(7)雷同MYSQL入门
欢迎参与《Mysql实例MySQL的线程池原理学习教程》讨论,分享您的想法,维易PHP学院为您提供专业教程。