本文来自:
刚刚经历了淘宝的双11,真实感受到了紧张的氛围。尽管DB淡定的度过,但是历程中的波折,可谓惊心动魄。其中MySQL在大量连接(万级)的场景下,表现出性能远远低于预期,并且出现明显的波动,成为一个非常重要的问题。问题虽然解决,但是后续的思考和方法的探索,仍然继续。以下是在MySQL层增加Thread pool方式,解决大量连接问题。
1、《MySQL Thread Pool: Problem Definition》
MySQL企业版在5.5中,引入了Thread pool方案。本文主要是分析现有方式存在的问题,并引入Thread pool方式以及需求。主要解决MySQL传统连接设计中one-to-one方式(一个连接使用一个线程服务)存在两个方面的问题:
1.1 CPU资源限制
在连接数非常多,并发较大时,CPU可用资源会成为MySQL的连接线程的瓶颈,导致MySQL性能降低。有两个原因:一个是并发数较大时,CPU的cache命中率降低,以及并发线程创建消耗内存资源,可能导致swap影响MySQL的性能;另一个原因是并发较大时,会导致通过临界资源时,产生大量的锁竞争。这些问题在系统层都很难解决,只能通过应用层解决。
1.2 Innodb mutex锁
在并发较大时,会引起Innodb的mutex锁争用。当前解决方法是通过innodb_thread_concurrency参数,但是该参数自身也存在锁争用,同样影响了MySQL的性能。在我们的优化历程中,也尝试通过优化该参数,来解决并发情况下,性能降低的问题。但是测试过程中发现,会有大量的连接等待kernel mutex锁,但是持续的压力会导致MySQL的thread running,最终导致MySQL不可用。
Thread pool主要从四个方面考虑:减少SQL并发,使得有足够的资源;使用线程组,独立管理;使用优先级队列,限制并发执行的事务;避免死锁。
在以上阐述的内容中,几乎涵盖了我们遇到的所有现象和问题,而性能影响会如此严重,是我们始料未及的。
2、《MySQL Thread Pool: Scalability solution》
基于上文中问题定义,本文主要讨论Thread pool的可扩展性解决方案。为了解决并发连接数问题,通常考虑会创建一个线程池,但是会引入可扩展性问题:每个连接或线程建立时,会对公用的数据结构加锁,修改连接或线程的状态信息。这样无异于重新引入热点。
为了避免引入新的问题,Thread pool使用线程组,每个线程组管理组内的连接和线程,线程组之间互相不受影响。并且确保每个线程组内最多有一个活跃的线程连接,这样连接和线程建立时,仅对单个线程组内的数据结构加锁,文中称之为分治法。
在文章回复中,有一个问题非常重要:线程组的引入,会出现当等待线程组时,请求不能进入而阻塞连接,而该问题会通过定时器的方式解决。另一个问题是:通过限制每个用户的连接数,而MySQL层通过innodb_thread_concurrency参数控制,来控制并发连接数。对于第二个问题,当应用达到一定重量级时,限制用户连接数变得比较困难。假设有几千台应用服务器,每个应用服务器1~2个数据库连接,同样会出现并发连接过多的问题。也就是说通过用户连接数限制,治标不治本。
3、《MySQL Thread Pool: Limiting number of concurrent statement executions》
上文回复中,提出当线程组引入后,SQL语句可能会导致线程组的等待,阻塞大量查询。本文中针对该问题,提出了通过定时器的方式解决。当SQL查询执行超过定时器的时间,线程组将会设置为不可用。
此外,当查询由于锁被阻塞时,线程会等待超时时间,然后处理新的查询请求,从而保证了线程组的可用。
4、《MySQL Thread Pool: Limiting number of concurrent transactions》
对于事务的并发控制,Thread pool采用优先级队列的方式解决。为了避免大事务导致线程组不可用,引入了thread_pool_prio_kickup_timer参数,当事务超过参数设置的超时时间时,等待线程组的查询会被移到高优先级队列中。为了避免过多的移动,Thread pool限制每个线程组在10ms内最多移动一次。
通过优先级队列,可以很大程度上解决因为事务阻塞,而产生大量查询超时的问题,还解决了其他线程组空闲的情况。
5、《MySQL Thread Pool: When to use?》
Thread pool的原理和实现在以上文章中进行了详细的解释,但是什么时候用呢?本文指出根据threads_running的监控值,来判断是否使用Thread pool。当threads_running值超出了MySQL的处理能力时,Thread pool将会保护MySQL server。
此外,Thread pool和innodb_thread_concurrency参数一起使用,可以更好的控制并发。Thread pool主要在Server层,在事务开始之前进行限制,innodb_thread_concurrency在事务处理中进行控制。
特别指出,如果查询为短查询,Thread pool会非常有效。如果查询为长查询,Thread pool会有性能影响。
6、《MySQL Thread Pool vs. Connection Pool》
针对文章2中回复的第二个问题,本文详细解释了Thread pool和Connection pool两者的区别。Thread pool主要是MySQL端采取的线程控制,用于限制MySQL实际处理的SQL并发。而Connection pool是在应用的客户端来限制,连接到MySQL Server的线程数。
在某些情况下,Connection pool并非所有的连接都有活跃的事务,但是会在MySQL Server中建立大量的连接。而通过Thread pool,可以减少MySQL Server的连接数。
7、《MySQL Thread Pool: Optimal configuration》
本文主要介绍Thread pool使用的参数进行详细的说明,主要包括:thread_pool_size指定线程池大小,默认值为16;thread_pool_stall_limit执行线程池超时时间,默认值为6(60ms);thread_pool_prio_kickup_timer限制优先级队列移动的超时时间,默认值为1000(1s);thread_pool_algorithm指定线程池算法,暂不支持;thread_pool_max_unused_threads查看当前最大未使用的线程数。
在实际使用中,根据数据库服务器的性能,MySQL Server的使用情况,以及应用查询的情况,调整这些参数,从而达到最佳的性能。
8、《MySQL Thread Pool: Benchmarking》
本文通过压力测试,比较使用Thread pool前后,性能的影响。通过测试结果可知,使用Thread pool在连接并发数较大时,性能可以稳定在较高水平,并没有由于连接数的增加,导致MySQL性能的骤减。
官方测试结果如下图所示,
|
|
|
|
结论
Thread pool的引入,解决了MySQL传统连接设计中one-to-one方式带来的不足。此外,使用线程组的方式,避免引入新的可扩展问题;使用定时器,解决长查询阻塞线程组的问题;通过优先级队列,解决大事务问题。
从测试结果来看,Thread pool在连接并发数较大时,性能可以稳定在较高水平,而没有由于连接数的增加,导致MySQL性能的骤减。
由于MySQL企业版未开放源码,只能通过黑盒测试进行评估。但是实现的原理和处理策略,对于评估开源Thread pool的实现,都非常有益。
参考
1、《MySQL Thread Pool: Problem Definition》:
2、《MySQL Thread Pool: Scalability solution》:
3、《MySQL Thread Pool: Limiting number of concurrent statement executions》:
4、《MySQL Thread Pool: Limiting number of concurrent transactions》:
5、《MySQL Thread Pool: When to use?》:
6、《MySQL Thread Pool vs. Connection Pool》:
7、《MySQL Thread Pool: Optimal configuration》:
8、《MySQL Thread Pool: Benchmarking》: