线程池是Mysql5.6的一个核心功能。对于server应用而言,不管是web应用服务还是DB服务,高并发请求始终是一个绕不开的话题。当有大量请求并发訪问时,一定伴随着资源的不断创建和释放。导致资源利用率低。减少了服务质量。

线程池是一种通用的技术,通过预先创建一定数量的线程,当有请求达到时,线程池分配一个线程提供服务,请求结束后,该线程又去服务其它请求。 通过这样的方式。避免了线程和内存对象的频繁创建和释放,减少了服务端的并发度,减少了上下文切换和资源的竞争,提高资源利用效率。全部服务的线程池本质都是位了提高资源利用效率,而且实现方式也大体同样。

本文主要说明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(语句),一个线程能够处理多个连接的请求。

这样,在保证充分利用硬件资源情况下(合理设置线程池大小)。能够避免瞬间连接数暴增导致的server抖动。

调度方式实现

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监听mysqlport的连接请求
  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

图 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,则唤醒或创建线程

5)检查是否有连接超时

备注:timer线程通过调用check_stall推断group是否处于stall状态。通过调用timeout_check检查client连接是否超时。

7.tp_wait_begin[进入等待状态流程]

1) active_thread_count减1,waiting_thread_count加1

2)设置connection->waiting= true

3) 若活跃线程数为0。而且任务队列不为空,或者没有监听线程。则

4) 唤醒或创建一个线程

8.tp_wait_end[结束等待状态流程]

1) 设置connection的waiting状态为false

2) active_thread_count加1。waiting_thread_count减1

备注:

1)waiting_threads这个list里面的线程是空暇线程。并不是等待线程。所谓空暇线程是随时能够处理任务的线程。而等待线程则是由于等待锁,或等待io操作等无法处理任务的线程。

2)tp_wait_begin和tp_wait_end的主要作用是因为汇报状态,即使更新active_thread_count和waiting_thread_count的信息。

9. tp_init/tp_end

分别调用thread_group_init和thread_group_close来初始化和销毁线程池

线程池与连接池

连接池通常实如今Client端,是指应用(client)创建预先创建一定的连接,利用这些连接服务于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.调度死锁解决

引入线程池攻克了多线程高并发的问题,但也带来一个隐患。

如果,A,B两个事务被分配到不同的group中运行,A事务已经開始。而且持有锁,但因为A所在的group比較繁忙。导致A运行一条语句后,不能马上获得调度运行。而B事务依赖A事务释放锁资源。尽管B事务能够被调度起来。但因为无法获得锁资源。导致仍然须要等待,这就是所谓的调度死锁。

因为一个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累计值,避免上述问题。

MySQL具体解释(7)-----------MySQL线程池总结(一)的更多相关文章

  1. mysql小白系列_03 体系结构-线程池

    thread pool的原理是什么? 为什么用double write就能解决page坏的问题? Innodb redo log 与 binlog有什么区别?有了Innodb redo log为什么还 ...

  2. MYSQL 源码解读系列 [线程池。。] ----dennis的博客

    http://blog.sina.com.cn/s/articlelist_1182000643_0_1.html

  3. 线程池:ThreadPoolExecutor

    [ThreadPoolExecutor的使用和思考]   public ThreadPoolExecutor(int corePoolSize,                             ...

  4. Java线程池是如何诞生的?

    时间回到2003年,那时我还是一个名不见经传的程序员,但是上级却非常看好我,他们把整个并发模块,都交给了我一个人开发. 这个星期,我必须要完成并发模块中非常重要的一个功能--线程池.  注:文末有福利 ...

  5. 如何优雅的关闭Java线程池

    面试中经常会问到,创建一个线程池需要哪些参数啊,线程池的工作原理啊,却很少会问到线程池如何安全关闭的. 也正是因为大家不是很关注这块,即便是工作三四年的人,也会有因为线程池关闭不合理,导致应用无法正常 ...

  6. linux C 线程池(物不可穷也~)

    Linux 多线程编程之 线程池 的原理和一个简单的C实现,提高对多线程编 程的认知,同步处理等操作,以及如何在实际项目中高效的利用多线程开 发. 1.  线程池介绍 为什么需要线程池??? 目前的大 ...

  7. JUC——线程池

    线程池本质的概念就是一堆线程一起完成一件事情. Executor package java.util.concurrent; public interface Executor { void exec ...

  8. JDK源码分析之concurrent包(二) -- 线程池ThreadPoolExecutor

    上一篇我们简单描述了Executor框架的结构,本篇正式开始并发包中部分源码的解读. 我们知道,目前主流的商用虚拟机在线程的实现上可能会有所差别.但不管如何实现,在开启和关闭线程时一定会耗费很多CPU ...

  9. linux线程池thrmgr源码解析

    linux线程池thrmgr源码解析 1         thrmgr线程池的作用 thrmgr线程池的作用是提高程序的并发处理能力,在多CPU的服务器上运行程序,可以并发执行多个任务. 2      ...

  10. 从使用到原理,探究Java线程池

    什么是线程池 当我们需要处理某个任务的时候,可以新创建一个线程,让线程去执行任务.线程池的字面意思就是存放线程的池子,当我们需要处理某个任务的时候,可以从线程池里取出一条线程去执行. 为什么需要线程池 ...

随机推荐

  1. qt: flush: BitBlt failed

    "BitBlt" is a graphics accelerator function. The message is a warning, not an error. It te ...

  2. hdu 3395

    KM裸题 每个鱼都认为自己是雄性,而且会攻击它认为是雌性的鱼,每个鱼只能被攻击一次,被攻击后会产卵(个数是给的两条鱼的值的异或运算) #include<string.h> #include ...

  3. Tomcat架构以及理解sever.xml

    Tomcat架构图 当用户在地址栏输入访问地址后,首先识别访问协议(假设为http),那么通过针对于http协议传输的Connector连接器,连接到tomcat的服务中,连接后开始检测Engine下 ...

  4. passwd-shadow文件

    [root@rusky /]# vi /etc/passwd root:x:::Redhat5:/root:/bin/bash rusky:x::::/home/rusky:/bin/bash 1.r ...

  5. 利用ajax从txt读取数据

    html代码: <div id="news"></div> txt: [ {"id":"1", "news ...

  6. Python - SQLAlchemy之连表操作

    ORM的两种创建方式 数据库优先:指的是先创建数据库,包括表和字段的建立,然后根据数据库生成ORM的代码,它是先创建数据库,再创建相关程序代码 代码优先:就是先写代码,然后根据代码去生成数据库结构. ...

  7. 任务栈 启动模式 Flag taskAffinity

    关于任务栈Task 栈的概念 栈(Stack)是一种常用的数据结构,栈只允许访问栈顶的元素,栈就像一个杯子,每次都只能取杯子顶上的东西,而对于栈就只能每次访问它的栈顶元素,从而可以达到保护栈顶元素以下 ...

  8. getParameter百科

    获取数据库中的参数数据 getParameter().   request.getParameter("username");其中的这个username 是接受前台的参数 比如in ...

  9. (转)Newtonsoft.Json序列化和反序列

    这里下载:http://www.newtonsoft.com/products/json/安装:   1.解压下载文件,得到Newtonsoft.Json.dll   2.在项目中添加引用.. 序列化 ...

  10. c - 输出 101 至 200之间的素数.

    #include <stdio.h> #include <math.h> //判断 101-200 之间有多少个素数,并输出所有素数. int main(void) { , e ...