之前根据官方资料整理了一篇笔记,现在读起来感觉就是在凑数,在网上看到高人的一篇帖子http://www.gpfeng.com/?p=540,现在重新整理记录一下。
Maria引入thread pool,percona在此基础上引入优先队列,依次对这两种特性进行描述。
1 maria实现细节
1、整个连接池内部被分成N个小的group,N默认为cpu的个数,可以通过参数thread_pool_size设置,group之间通过round robin的方式分配连接,group内通过竞争方式处理连接,一个worker线程只属于一个group;
2、每个group有一个动态的listener,worker线程在循环取event时,发现队列为空时会充当listener通过epoll的方式监听数据,并将监听到的event放到group中的队列;
注:每个group都创建一个epollfd,而空闲连接的socket都加入其中被监听,只有当event queue为空时才会有worker调用epoll_wait;
3、延时创建线程,group中的活动线程数为0或者group被阻塞时,worker线程会被创建,worker线程被创建的时间间隔会随着group内已有的线程数目的增加而变大;
4、worker线程,数目动态变化,这也是相对于5.1版本的一个改进,并发较大时会创建更多的worker线程,当从队列中取不到event时work线程将休眠,超过thread_pool_idle_timeout后结束生命;
5、timer线程,它会每隔一段时间做两件事情:
1)检查每个group是否被阻塞,判定条件是:group中的队列中有event,但是自上次timer检查到现在还没有worker线程从中取出event并处理;
2)kill超时连接并做一些清理工作;
connection生命周期
1 客户端发起连接,acceptor线程调用add_connection scheduler callback接收;
每个worker都可充当acceptor,将认证和线程初始化offload到worker是为了避免可能的single acceptor阻塞;
登录完毕后,threadpool为connection socket开启异步读;
2 客户端调用query,socket发出信号,由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
根据thd的thread_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_thread;2)检查是否有连接超时,超时将调用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
唤醒或者创建线程,被三处调用:
1)connection加入到queue中时,group中的队列中没有活动的worker线程:
tp_add_connection
|->queue_put
|->wake_or_create_thread
2)timer线程检查是否有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的实现后进一步优化了线程池性能,通过引入优先队列很好解决了这个问题。
工作原理
1 创建多个group(默认等同于cpu core数量),每个group可有多个worker;
2 线程根据connection id被分配到group(生命周期内不变),worker以sql为单位处理,保证每个连接都能及时得到响应;
3 每个group有两个任务队列,优先队列:存放已开启事务的sql,保证事务优先被处理完(尽早释放锁);优先队列为空时才处理普通队列;
可避免调度上的死锁:(A和B被分到不同的group中,A事务已经开启,并且获得了锁,可能无法立即得到调度执行,B事务依赖A事务释放锁资源,但是先于A得到调度);
4 额外创建一个timer线程,定期检查groups,若发现woker异常则及时唤醒(堵塞/超时/worker线程数目不够);
5 若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中最大线程数目,所有group中worker线程总数超过该限制后不能继续创建更多线程,默认100000
thread_pool_oversubscribe
一个group中线程数过载限制,当一个group中线程数超过次限制后,继续创建worker线程会被延迟,默认3
thread_pool_size
threadpool中group数量,默认为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://worklog.askmonty.org/worklog/Server-BackLog/?tid=246
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/15480802/viewspace-1452252/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/15480802/viewspace-1452252/