历史背景
当翻阅某些互联网公司的技术架构图时,可能会发现他们当中很多有一个共同的特点,那就是应用与数据库之间有一所谓的“数据代理层”(比如美团、58同城、早期的淘宝等),至于数据代理层存在的价值,一般都会谈到以下两点:
- 分离数据操作。将许多复杂的数据操作分离到数据层实现(如:实施分库分表后访问路由、数据合并),降低业务层代码复杂度;
- 降低MySQL连接数。由于数据层需要的机器数量远小于业务层,所以数据库连接数相对较小。尤其是对于业务量庞大,需要分库分表的系统,可能一个表被水平切分到成百上千台机器上,数量更庞大的应用服务器需要与每台MySQL连接,由此导致MySQL服务器的连接数是惊人的。
然而为实现数据代理层所付出的代价也是相当可观的,不仅需要增加该层的硬件设施、增加人员维护,由于应用层与数据层之间的传输协议普遍性能不高,与MySQL自己的二进制专用协议相比,无论在序列化性能还是数据包大小都有较大差距,因此应用层需要付出更多的代价去与数据层交互。以上这些缺点对于具有海量业务的公司来说似乎不可容忍,有没有可以解决以上问题的方式呢,答案是肯定的。
首先了解一下淘宝的TDDL。
淘宝TDDL
TDDL是淘宝应用于分库分表的项目,经过多个版本的发展,整合阿里另一个项目”CORBA”之后愈发强大。TDDL主要提供分库分表场景下动态访问路由、数据合并、流控等功能。因为TDDL在代码层面被项目依赖,依托”CORBA”强大的SQL解析能力,领域模型定位于数据库与JDBC之间,所以对业务代码无侵入,基本上实现了上文中数据层的第一个任务。
可以说如果没有MySQL线程池的话,TDDL没有任何意义,因为在TDDL模式下,应用层需要直连数据库,对上文中数据层的第二个任务并不是TDDL的能力范围,需要借助线程池来解决MySQL海量连接问题。
MySQL线程池
数据库线程池并不是MySQL的独门绝技,在5.5时代MariaDB和PerconaDB就已经实现了线程池功能,MySQL在随后的5.6社区版本当中也加入了该功能。
在MySQL5.6之前,线程模型比较简单,类似于Java的BIO模型,每个线程处理只能处理一个连接,每次MySQL请求的数据IO、业务响应都在同一个线程当中。如果集群庞大的分库分表场景下,MySQL将会产生大量的TCP连接,系统会耗费大量的时间用来做线程上下文切换、缓存换入换出以及资源竞争,如果并发量陡增(比如电商系统的秒杀场景),MySQL将直接无响应。
MySQL通过线程池所实现的线程模型与当今主流IO系统的线程模型基本一致(如Mina、Netty、Tomcat、Nginx等),简要来说就是一部分线程负责网络数据包的IO(IO线程:负责在核心态内存与用户态内存间交换数据),它们每次接受SQL请求并将任务加入队列之后立即返回,另一部分线程负责响应请求(业务线程),不断从队列获取任务并执行。IO线程使用非阻塞模型(epoll),可以创建海量连接且保持性能稳定,由于与业务线程独立,当业务线程繁忙时不会影响IO响应。两部分线程之间有队列负责缓冲任务,保护数据库引擎不会在短时间内涌入太多任务,保证正常运行。
线程池相关参数
- thread_handling:表示线程池模型。
- thread_pool_size:表示线程池的group个数,一般设置为当前CPU核心数目。理想情况下,一个group一个活跃的工作线程,达到充分利用CPU的目的。
- thread_pool_stall_limit:用于timer线程定期检查group是否“停滞”,参数表示检测的间隔。
- thread_pool_idle_timeout:当一个worker空闲一段时间后会自动退出,保证线程池中的工作线程在满足请求的情况下,保持比较低的水平。
- thread_pool_oversubscribe:该参数用于控制CPU核心上“超频”的线程数。这个参数设置值不含listen线程计数。
- threadpool_high_prio_mode:表示优先队列的模式。
线程池实现

每一个绿色的方框代表一个group,group的数量由thread_pool_size参数决定。
每个group包含一个普通队列和一个优先队列,包含一个listen线程和若干worker线程,两种线程间可以动态转换,worker线程数量由工作负载决定,同时也受thread_pool_oversubscribe参数影响。普通队列中的任务超过一定时间未被执行会被列入优先队列,而优先队列的任务会被优先执行。
整个group组有一个timer监控线程。