OneSQL :: 不同的操作使用不同的线程池,多队列更能有效地管理机器资源!

并发往往是系统设计中的一个大问题,应用程序正在使用象Docker或OpenStack之类的技术在做规模化布署,从而避免去解决单个应用实例的高并发问题,不够了加实例即可。但对于数据库来讲,就没这么容易了,数据的自由分布和移动是件非常困难的事情,所以一个数据库中往往存贮了来自于多个应用系统的数据,许多许多的应用需要与单个数据库保持通信连接,单个数据库上有成千上万个会话也不是什么希奇的事情,数据库在高并发的性能表现对应用非常重要。

操作系统可能会花费大量的资源来管理成千上万个并发连接,如果没有有效的线程池或者并发调度机制,每一个连接都会对应到后端的一个进程(Oracle)或线程(MySQL),大量的数据库连接也许会用尽机器的所有CPU资源,让操作系统的调度进程也很难顺利工作,从而影响数据库服务器的稳定性。在MySQL企业版本中,线程池(Thread Pool)插件可以有效地控制高并发连接下的资源消耗,从而提升整体的性能,当然MySQL其他分支(MariaDB和Percona)中也有开源版本的连接池实现。

下图来自Oracle官方MySQL网站,红线是具有线程池功能的企业版本随连接数上涨后的TPS曲线,而蓝线是没有连接池功能的社区版的TPS曲线,可以看到超过一定的连接数后,社区版本的性能开始急剧下降,大量的CPU被并发线程的切换操作所消耗。

mysql_thread_pooling

但在我们测试和使用的过程中,针对目前的线程池实现机制,发现很难设置一个合适的最大线程池大小,因为所有的操作都在共享同一个线程池,数据库的操作可以有简单的主键查询、复杂的统计查询、一条条简单的更新语句、以及一个由上百个SQL组成的复杂SQL组成,如果他们全使用同一个线程池,很难统计设置一个合理的线程池大小,对于简单操作可以设置一个较大的值来提高资源利用率,而对于复杂操作则最好设置一个较小的值,以防资源被用光影响稳定性。在高速公路上,我们可以看到一条一条道被隔出来,以提升交通能力,比如来和云的方向中间有绿化带隔离,以免相互干扰。

dedicate-chaneles-high-way

如果高速公路中间没有隔离带,没有划分车道(比如大车靠边道行使),那么交通将会常常堵塞,这是大家都有过的经历。在数据库里,也可以将操作进行分类,在OneSQL中我们将所有的操作分为以下四个类型,并分别为之创建了独立的线程池,可以独立调节线程池的最大值。

  1. 简单查询: 比如简单高效的依据主键访问的查询,带统计、子查询或排序子句的SQL语句都属于代价较高的复杂查询,在这里简单是指数据库后端处理起来比较快速的意思。
  2. 复杂查询:查询语句里包含统计函数、需要排序或者有子查询的,在数据库后端执行成本比较高的SQL语句,当数据不均衡时,返回大量数据的SQL语句也应当被理解成复杂查询。
  3. 简单事务:所有执行完后马上自动提交的DML语句。
  4. 复杂事务:所有显式开启的事务中的SQL语句(包括查询语句),都属于复杂事务,指用”start transaction”命令开启的事务,或者设置了”autocommit=0″选项。

如同高速公路一样,我们可以控制每个类型操作允许的最大并发度(最大线程池大小),这个功能称之为多队列线程池技术。如果我们设置复杂查询的最大并发度为不超过CPU的个数,那么就不再需要担心复杂的SQL语句将所有的CPU用光了,简单查询操作的性能可以得到保证。并且OneSQL可以根据语法分析自动识别和区分这些操作类型,并结事事务的上下文环境,对应用来讲完全透明,无应用改造成本。下面四个参数可以用来动态调节不同操作类型的最大并发度,需要根据你业务的SQL特征来做优化处理。

+-------------------------------------+-------+
| Variable_name                       | Value |
+-------------------------------------+-------+
| tcc_max_autocommit_concurrency      | 10    |
| tcc_max_big_query_concurrency       | 6     |
| tcc_max_query_concurrency           | 10    |
| tcc_max_transaction_concurrency     | 40    |
+-------------------------------------+-------+

接下来我们来测试一下,下面是测试的用例,一个最简化的两个账户之间的转账事务处理。

declare
  vid1 int 1 500
  vid2 int 501 1000
begin
  start transaction;
    update test_table set col2 = col2 - 1 where id = :vid1;
    update test_table set col2 = col2 + 1 where id = :vid2;
  commit;
end;

可以用”create table test_table (id int not null primary key, col2 int)”语句来创建表结构,并且插入1000条记录,然后用不同的MySQL版本进行压测。下图是OneSQL在8核Intel E5620 CPU上的测试结果,没有用SSD盘,并且启用了Binlog,并运行在最大数据保护模式(事务等所有日志刷盘后响应客户端)下,X轴是测试程序的并发度,Y轴是每秒完成的事务数。

onesql-thread-pool-testing

如果我们混合不同操作类型的请求,比如查询和更新,然后每种操作都开启500个并发进行压测,结果会如何呢?没有多队列线程池技术,不同操作类型之间会出现明显的相互干扰情况,而OneSQL则会表现良好。