MySQL线程化请求处理

来源:互联网 发布:风中劲草使用方法 知乎 编辑:程序博客网 时间:2024/05/18 17:58
当实现一个服务器之时,首先面临着一个进退两难的选择,选择多进程还是多线程结构。
两者各有良莠。从一开始,MySQL便选择了多线程。

线程与进程
两者最大的不同可能是heap的同享,这个是选择哪种模型的潜在依据。

线程优势
1、内存利用的简化
2、对于全局数据的访问无需高级技术
3、快速创建
4、内核调度成本
线程劣势
1、异常隔离要求
2、错误代价高
3、同步BUG难以测试
4、锁竞争导致过度的上下文切换
5、单进程地址空间限制
6、线程占据stack,堆空间被压缩

进程优势:
1、代码错误不至于拖垮整个server
2、不存在并发带来的同步问题
3、减少奇怪BUG,易于重现
4、空间利用率
进程劣势:
1、内存利用率,fork子进程成本大
2、一些特定技术使用于进程间同步
3、创建进程需要copy父进程数据段,改进copy-on-write
4、上下文切换成本

MySQL需要共享buffers,data,所以选择了多线程
多线程意味着更多的编程技巧。空间有限的栈、线程并发问题。


请求处理实现
主线程监听连接,每个连接都有一个对应的线程处理请求。
根据服务端配置以及先行状态,新分配的线程可能是新建或者从线程缓存中获得。
处理请求,直至收到COM_QUIT或者会话异常中断,后续中止线程或者返回线程缓存。

涉及结构、变量、类和API
THD:线程描述封装类
一旦创建了线程,对应的THD会放入全局变量I_List<THD> threads中.该全局变量有三个作用:
1、SHOW PROCESSLIST使用
2、KILL定位到目标线程
3、SHUTDOWN中止所有线程
另外的THD链表thread_cache,使用方式有点奇怪:主线程将THD对象传入到thread_cache的线程。
sql/mysqld.cc中的create_new_thread(),start_cached_thread(),end_thread()

所有创建、中止、跟踪线程的操作都有一个互斥量保护:LOCK_thread_count;
COND_thread_count确保所有线程清理了自己的工作后,并在主线程之前退出。
COND_thread_cache被广播当主线程决定唤醒一个缓存线程来处理当前客户端请求。
COND_flush_thread_cache用于cache thread退出时的唤醒变量

全局变量
abort_loop:all threads loop flag
cache_thread_count:SHOW STATUS命令中的threads_connetced


执行路径
1、select/accept调用。由sql/mysqld.cc的handle_connection_sockets()触发
2、accept创建THD实例
3、create_new_thread()(sql/mysqld.cc).确认线程如何得到,使用cached的线程还是创建新线


4、如果使用cached,唤醒sleep线程,传入THD。尽管cached thread的引入能提高性能,但最终

的加入理由是为了调试timing问题。start_cached_thread()(sql/mysqld.cc)唤醒线程。
5、如果没有cached线程可用或者没有开启cachedthread开关,新的线程会创建来服务请求。
pthread_create创建线程,启动入口为handle_one_connection()(sql/sql_parse.cc),参数依旧是THD
6、handle_one_connection()函数while循环,调用do_command(thd);
循环退出情况:网络错误、thread kill、COM_QUIT、master中断feed到slave(COM_BINLOG_DUMP)
7、退出while后,清理阶段。end_thread()(sql/mysqld.cc)调用。
8、判断退出还是putinto cached thread.放入cached thread之后,等待条件
   pthread_cond_wait(&COND_thread_cache, &LOCK_thread_count)。
等待start_cached_thread()调用或者SIGHUP处理例程或者shutdown例程唤醒。获取thread_cache_list的THD(由start_cached_thread传入),从handle_one_connection重新启动。
9、如果thread不放入cached,则直接pthread_exit();

线程编程问题

>标准库调用:线程安全调用。mysys/strings 封装屏蔽一些平台非线程安全调用
>互斥锁:锁的引入导致复杂度的增长,多少锁才是最合适,怎么设计保护数据。
极端来说,每个共享变量使用独立锁,减少锁竞争,但死锁风险增大,同时频繁调用加锁/释放锁导致性能下降。
极端,所有的事情就一个锁。并发度是个灾难。
MySQL选择,分组加锁。一系列全局锁。如LOCK_delayed_insert/LOCK_error_log/LOCK_mapped_file/THR_LOCK_malloc
>读写锁:一大堆读写锁优势,读写锁能够实现常规互斥锁的特性,但为什么不一直使用读写锁。
基于实现成本,复杂的实现机制消耗更多的CPU周期,即便是马上得到锁的情况下。短暂锁定,mutex往往更优于读写锁,MySQL存在几种读写锁应用情况: LOCK_grant/LOCK_sys_init_connect/LOCK_sys_init_slave
>同步
通过条件变量实现多个线程之间的同步。
>抢占
MySQL使用"responsible citizen"处理抢占。
待抢占线程设置合适的标志,被抢占线程检查标志,清理后退出或者等待。
有个异常情况,如果被抢占线程正处于阻塞I/O,并没有机会检查抢占标志位。
MySQL使用thread alarm技术解决这个问题:
1、当线程准备陷入阻塞I/O调用时,设定一个超时处理机制(thr_alarm()).
2、如果I/O完成,通过调用end_thr_alarm取消超时处理
3、超时到来时,大部分系统会中断I/O阻塞,因此有个机会去检查抢占位,做相应的处理。
thr_alarm与end_thr_alarm函数使用的句柄需要通过init_thr_alarm初始化。定义于mysys/thr_alarm.c
原创粉丝点击