ITPub博客

首页 > 数据库 > MySQL > mysql并发线程控制之thread pool和优先队列

mysql并发线程控制之thread pool和优先队列

原创 MySQL 作者:myownstars 时间:2015-03-08 11:54:34 0 删除 编辑

之前根据官方资料整理了一篇笔记,现在读起来感觉就是在凑数,在网上看到高人的一篇帖子http://www.gpfeng.com/?p=540,现在重新整理记录一下。

Maria引入thread poolpercona在此基础上引入优先队列,依次对这两种特性进行描述。

 

1 maria实现细节

1、整个连接池内部被分成N个小的groupN默认为cpu的个数,可以通过参数thread_pool_size设置,group之间通过round robin的方式分配连接,group内通过竞争方式处理连接,一个worker线程只属于一个group

2、每个group有一个动态的listenerworker线程在循环取event时,发现队列为空时会充当listener通过epoll的方式监听数据,并将监听到的event放到group中的队列;
注:每个group都创建一个epollfd,而空闲连接的socket都加入其中被监听,只有当event queue为空时才会有worker调用epoll_wait;

3、延时创建线程,group中的活动线程数为0或者group被阻塞时,worker线程会被创建,worker线程被创建的时间间隔会随着group内已有的线程数目的增加而变大;

4worker线程,数目动态变化,这也是相对于5.1版本的一个改进,并发较大时会创建更多的worker线程,当从队列中取不到eventwork线程将休眠,超过thread_pool_idle_timeout后结束生命;

5timer线程,它会每隔一段时间做两件事情:

   1)检查每个group是否被阻塞,判定条件是:group中的队列中有event,但是自上次timer检查到现在还没有worker线程从中取出event并处理;

   2kill超时连接并做一些清理工作;

connection生命周期

1 客户端发起连接,acceptor线程调用add_connection scheduler callback接收;

每个worker都可充当acceptor,将认证和线程初始化offloadworker是为了避免可能的single acceptor阻塞;

登录完毕后,threadpoolconnection socket开启异步读;

2 客户端调用querysocket发出信号,由worker负责处理;


调用接口

static scheduler_functions tp_scheduler_functions=

{

...

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

};

以上函数调用时都会传入THD

tp_init

线程池初始化工作,对每个group初始化pollfd,启动timer线程

tp_add_connection

根据thdthread_id采用round robin方式将其分配到一个group中,将thd封装到connection_t的一个结构体中,放到group中的队列中,入队时会判断group中的活动worker线程数(thread_group->active_thread_count)是否大于0,否则将唤醒或者创建一个worker线程(wake_or_create_thread

Worker线程工作逻辑

循环从group队列中get_event并调用handler_event

for(;;)

{

  connection = get_event(&this_thread, thread_group, &ts);

  if (!connection)

    break;

  this_thread.event_count++;

  handle_event(connection);

}

get_event

从队列中取event取不到则充当listener通过epoll从网络监听事件,如果最终还是没能取到event,休眠一段时间,直到被唤醒或者超时退出;

handle_event
1
)连接验证;2)处理连接请求直到连接空闲,为空闲连接设置一个超时期限,之后将连接的socket fd绑定到group中的epollfd


timer_thread运行逻辑

做了两件事情:1)检查是否有group被阻塞,阻塞将调用wake_or_create_thread2)检查是否有连接超时,超时将调用tp_post_kill_notification

for (;;)

{

/* Check stalls in thread groups */

for(i=0; i< array_elements(all_groups);i++)

{

  if(all_groups[i].connection_count)

  check_stall(&all_groups[i]);

}

/* Check if any client exceeded wait_timeout */

if (timer->next_timeout_check <= timer->current_microtime)

  timeout_check(timer);

}

 

wake_or_create_thread

唤醒或者创建线程,被三处调用:

1connection加入到queue中时,group中的队列中没有活动的worker线程:

tp_add_connection

|->queue_put

  |->wake_or_create_thread

2timer线程检查是否有group阻塞,满足两个条件中的任一个即被调用:group中没有listener并且队列为空;group被阻塞

timer_thread

|->check_stall

  |->wake_or_create_thread

3)事务被阻塞(如:锁等待)通过MYSQL_CALLBACK接口调用,等待之前会确定group中有活动的worker线程,否则唤醒或者创建worker线程

tp_wait_begin

|->wait_begin

  |->|->wake_or_create_thread

 

 

 

2 优先队列

MariaDB版本有缺陷,为了发挥线程池的优势,需要尽量控制线程池中线程数目,否则会退化成one-thread-per-connection,而如果严格控制线程池中线程数据,可能会出现调度上的死锁。

percona在移植MariaDB threadpool的实现后进一步优化了线程池性能,通过引入优先队列很好解决了这个问题。

 

工作原理

创建多个group(默认等同于cpu core数量),每个group可有多个worker

线程根据connection id被分配到group(生命周期内不变)workersql为单位处理,保证每个连接都能及时得到响应;

每个group有两个任务队列,优先队列:存放已开启事务的sql,保证事务优先被处理完(尽早释放锁);优先队列为空时才处理普通队列;

可避免调度上的死锁:(AB被分到不同的group中,A事务已经开启,并且获得了锁,可能无法立即得到调度执行,B事务依赖A事务释放锁资源,但是先于A得到调度);

额外创建一个timer线程,定期检查groups,若发现woker异常则及时唤醒(堵塞/超时/worker线程数目不够)

group任务队列为空(客户端连接却不为空)为空闲连接设置一个超时期限,之后将连接的socket fd绑定到group中的epollfd线程则调用epoll_wait()批量取任务;

参数

root@(none) 05:33:27>show global variables like '%thread_pool%';

+-------------------------------+--------------+

| 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)

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中最大线程数目,所有groupworker线程总数超过该限制后不能继续创建更多线程,默认100000

thread_pool_oversubscribe

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

thread_pool_size

threadpoolgroup数量,默认为cpu核心数,server启动时自动计算

thread_pool_stall_limit

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

thread_pool_stall_limit 是后台timer线程检测任务是否堵塞的时间间隔,在并发压力较大时,该参数设置过大可能会造成timer线程无法及时唤醒/创建worker线程

 

测试结果

详情可见http://www.gpfeng.com/?p=540

 

 

参考资料

http://blog.chinaunix.net/uid-28364803-id-3431242.html

http://www.gpfeng.com/?p=540

http://worklog.askmonty.org/worklog/Server-BackLog/?tid=246


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/15480802/viewspace-1452252/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2010-03-18

  • 博文量
    375
  • 访问量
    3091459