MySQL服务器线程池插件

原文链接 https://www.lanmper.cn/mysql/t7447.html

MySQL服务器线程池插件
MySQL Enterprise Edition包含使用服务器插件实现的MySQL Enterprise Thread Pool。MySQL Server中的默认线程处理模型在每个客户端连接中使用一个线程执行语句。随着越来越多的客户端连接到服务器并执行语句,整体性能下降。线程池插件提供了另一种线程处理模型,旨在减少开销并提高性能。该插件实现了一个线程池,可通过有效地管理大量客户端连接的语句执行线程来提高服务器性能。

线程池解决了每个连接使用一个线程的模型的几个问题:

太多的线程堆栈使CPU缓存在高度并行执行工作负载中几乎无用。线程池可促进线程堆栈的重用,以最大程度地减少CPU缓存的占用。
由于并行执行的线程过多,上下文切换开销很高。这也给操作系统调度程序提出了艰巨的任务。线程池控制活动线程的数量,以将MySQL服务器内的并行度保持在它可以处理的水平,并且该级别适用于正在执行MySQL的服务器主机。
并行执行的事务过多会增加资源争用。在中InnoDB,这会增加保持中央互斥锁所花费的时间。线程池控制事务开始的时间,以确保不会有太多并行执行。

线程池插件
线程池功能包括以下组件:

插件库文件为线程池代码以及几个提供有关线程池操作信息的相关监视表实现了一个插件:

从MySQL 8.0.14开始,监视表是Performance Schema表。请参见“性能架构线程池表”。
在MySQL 8.0.14之前,监视表是INFORMATION_SCHEMA表;请参见“ INFORMATION_SCHEMA线程池表”。

INFORMATION_SCHEMA现在不推荐使用这些表,并将在将来的MySQL版本中将其删除。应用程序应从旧表过渡到新表。例如,如果应用程序使用此查询:

SELECT * FROM INFORMATION_SCHEMA.TP_THREAD_STATE;
应用程序应改为使用以下查询:

SELECT * FROM performance_schem .tp_thread_state;
注意
如果不加载所有监视表,则某些或所有MySQL Enterprise Monitor线程池图将为空。

有关线程池如何工作的详细说明,请参见“线程池操作”。

几个系统变量与线程池有关。该thread_handling系统变量的值,loaded-dynamically当服务器成功加载线程池插件。

其他相关变量由线程池插件实现。除非启用,否则它们不可用:

thread_pool_algorithm:用于调度的并发算法。
thread_pool_high_priority_connection:如何安排会话的语句执行。
thread_pool_prio_kickup_timer:线程池将等待执行的语句从低优先级队列移至高优先级队列的时间。
thread_pool_max_active_query_threads每个组允许多少个活动线程。
thread_pool_max_unused_threads:允许多少个睡眠线程。
thread_pool_size:线程池中的线程组数。这是控制线程池性能的最重要参数。
thread_pool_stall_limit:执行语句被认为停止之前的时间。
如果在启动时将插件实现的任何变量设置为非法值,则插件初始化失败,并且不会加载插件。

有关设置线程池参数的信息,

性能架构的工具公开了有关线程池的信息,可用于调查操作性能。要识别它们,请使用以下查询:

SELECT * FROM performance_schem .setup_instruments
WHERE NAME LIKE '%thread_pool%';
有关更多信息,请参见MySQL性能模式。

线程池安装
本节介绍如何安装MySQL Enterprise Thread Pool。有关安装插件的一般信息,请参见“MySQL服务器插件”。

要由服务器使用,插件库文件必须位于MySQL插件目录(由plugin_dir系统变量命名的目录)中。如有必要,通过设置plugin_dir服务器启动时的值来配置插件目录位置。

插件库文件的基本名称为thread_pool。每个平台的文件名后缀都不同(例如,.so对于Unix和类似Unix的系统,.dll对于Windows)。

从MySQL 8.0.14开始的线程池安装
MySQL 8.0.14之前的线程池安装
从MySQL 8.0.14开始的线程池安装
在MySQL 8.0.14和更高版本中,线程池监视表是性能模式表,与线程池插件一起加载和卸载。这些INFORMATION_SCHEMA表的版本已弃用,但仍然可用;按照MySQL 8.0.14之前的线程池安装中的说明安装它们。

要启用线程池功能,请通过使用--plugin-load-add选项启动服务器来加载插件。为此,请将以下行放入服务器my.cnf文件中(.so根据需要调整平台的后缀):

[mysqld]
plugin-load-add=thread_pool.so
要验证插件安装,请检查INFORMATION_SCHEMA.PLUGINS表或使用以下SHOW PLUGINS语句(请参见“获取服务器插件信息”)。例如:

mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS
FROM INFORMATION_SCHEMA.PLUGINS
WHERE PLUGIN_NAME LIKE 'thread%';
+----------------------- +--------------- +
| PLUGIN_NAME | PLUGIN_STATUS |
+----------------------- +--------------- +
| thread_pool | ACTIVE |
+----------------------- +--------------- +
要验证性能模式监视表是否可用,请检查该INFORMATION_SCHEMA.TABLES表或使用该SHOW TABLES语句。例如:

mysql> SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'performance_schema'
AND TABLE_NAME LIKE 'tp%';
+----------------------- +
| TABLE_NAME |
+----------------------- +
| tp_thread_group_state |
| tp_thread_group_stats |
| tp_thread_state |
+----------------------- +
如果服务器成功加载线程池插件,则会将thread_handling系统变量设置为loaded-dynamically。

如果插件未能初始化,请检查服务器错误日志以获取诊断消息。

MySQL 8.0.14之前的线程池安装
在MySQL 8.0.14之前,线程池监视表是与线程池插件分开的插件,可以单独安装。

要启用线程池功能,请通过使用--plugin-load-add选项启动服务器来加载要使用的插件。例如,如果仅命名插件库文件,则服务器将加载它包含的所有插件(即线程池插件和所有INFORMATION_SCHEMA表)。为此,请将以下行放入服务器my.cnf文件中(.so根据需要调整平台的后缀):

[mysqld]
plugin-load-add=thread_pool.so
这相当于通过单独命名所有线程池插件来加载它们:

[mysqld]
plugin-load-add=thread_pool=thread_pool.so
plugin-load-add=tp_thread_state=thread_pool.so
plugin-load-add=tp_thread_group_state=thread_pool.so
plugin-load-add=tp_thread_group_stats=thread_pool.so
如果需要,您可以从库文件中加载单个插件。要加载线程池插件而不加载INFORMATION_SCHEMA表,请使用以下选项:

[mysqld]
plugin-load-add=thread_pool=thread_pool.so
要加载线程池插件和仅加载表,请使用以下选项:TP_THREAD_STATEINFORMATION_SCHEMA

[mysqld]
plugin-load-add=thread_pool=thread_pool.so
plugin-load-add=tp_thread_state=thread_pool.so
要验证插件安装,请检查INFORMATION_SCHEMA.PLUGINS表或使用以下SHOW PLUGINS语句(请参见“获取服务器插件信息”)。例如:

mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS
FROM INFORMATION_SCHEMA.PLUGINS
WHERE PLUGIN_NAME LIKE 'thread%' OR PLUGIN_NAME LIKE 'tp%';
+----------------------- +--------------- +
| PLUGIN_NAME | PLUGIN_STATUS |
+----------------------- +--------------- +
| thread_pool | ACTIVE |
| TP_THREAD_STATE | ACTIVE |
| TP_THREAD_GROUP_STATE | ACTIVE |
| TP_THREAD_GROUP_STATS | ACTIVE |
+----------------------- +--------------- +
如果服务器成功加载线程池插件,则会将thread_handling系统变量设置为loaded-dynamically。

如果插件未能初始化,请检查服务器错误日志以获取诊断消息。

线程池操作
线程池由多个线程组组成,每个线程组管理一组客户端连接。建立连接后,线程池将以循环方式将它们分配给线程组。

线程组的数量可以使用thread_pool_size系统变量进行配置。组的默认数目为16。有关设置此变量的准则,

每个组的最大线程数为4096(在内部使用一个线程的某些系统上为4095)。

线程池将连接和线程分开,因此连接与执行从那些连接接收的语句的线程之间没有固定的关系。这不同于默认的线程处理模型,该模型将一个线程与一个连接相关联,以使给定线程执行其连接中的所有语句。

默认情况下,线程池尝试确保在任何时间在每个组中最多执行一个线程,但有时允许更多线程临时执行以达到最佳性能。该算法以以下方式工作:

每个线程组都有一个侦听器线程,该线程侦听分配给该组的连接中的传入语句。当一条语句到达时,线程组要么立即开始执行它,要么将其排队等待以后执行:

如果该语句是唯一收到的语句,并且没有语句排队或当前正在执行,则立即执行。
如果语句不能立即开始执行,则会发生排队。
如果立即执行,则执行由侦听器线程执行。(这意味着该组中暂时没有线程正在侦听。)如果该语句快速完成,则正在执行的线程将返回侦听语句。否则,线程池将认为该语句已停止,并启动另一个线程作为侦听器线程(如有必要,请创建该线程)。为确保没有任何线程组被停止的语句阻塞,线程池具有一个后台线程,该线程定期监视线程组状态。

通过使用侦听线程执行可以立即开始的语句,如果该语句快速完成,则无需创建其他线程。这样可以确保在并发线程数较少的情况下尽可能高效地执行。

启动线程池插件时,它将为每个组创建一个线程(侦听器线程),再创建一个后台线程。根据需要创建其他线程来执行语句。

thread_pool_stall_limit系统变量的值确定上一项中“快速完成”的含义。线程被认为停止之前的默认时间为60ms,但可以设置为最多6s。此参数是可配置的,使您可以平衡服务器的工作负载。较短的等待值使线程可以更快地启动。较短的值也可以更好地避免死锁情况。长等待值对于包含长时间运行的语句的工作负载很有用,以避免在执行当前语句时启动太多新语句。
如果thread_pool_max_active_query_threads为0,则默认算法如前所述适用于确定每个组的最大活动线程数。默认算法将停滞的线程考虑在内,并可能暂时允许更多活动线程。如果thread_pool_max_active_query_threads大于0,则对每个组的活动线程数进行限制。
线程池的重点是限制并发短运行语句的数量。在执行语句到达停顿时间之前,它会阻止其他语句开始执行。如果该语句执行超过停顿时间,则可以继续执行该语句,但不再阻止其他语句启动。通过这种方式,线程池将尝试确保每个线程组中的短运行语句不会超过一个,尽管可能会有多个长运行语句。让长时间运行的语句阻止其他语句执行是不可取的,因为对等待的数量没有限制。例如,
如果语句遇到磁盘I / O操作或用户级别的锁(行锁或表锁),则该语句将被阻塞。该块将导致线程组变得未使用,因此对线程池进行了回调,以确保线程池可以立即启动该组中的新线程来执行另一条语句。当阻塞的线程返回时,线程池允许它立即重新启动。
有两个队列,一个高优先级队列和一个低优先级队列。事务中的第一条语句进入低优先级队列。如果事务正在进行(事务的语句已开始执行),则该事务的以下任何语句都将进入高优先级队列,否则,将进入低优先级队列。启用thread_pool_high_priority_connection系统变量会影响队列分配,这会导致会话的所有排队语句进入高优先级队列。

非事务性存储引擎或事务引擎(如果utocommit已启用)的语句被视为低优先级语句,因为在这种情况下,每个语句都是一个事务。因此,给定混合的for InnoDB和MyISAM table 语句InnoDB,MyISAM除非utocommit启用了线程池,否则优先于for的线程池。随着utocommit启用时,所有的语句将低优先级。

当线程组选择一个排队的语句来执行时,它首先在高优先级队列中查找,然后在低优先级队列中查找。如果找到一条语句,则将该语句从其队列中删除并开始执行。
如果一条语句在低优先级队列中停留的时间过长,则线程池将移至高优先级队列。thread_pool_prio_kickup_timer系统变量的值控制移动之前的时间。对于每个线程组,每10ms最多有一个语句或每秒100个语句将从低优先级队列移到高优先级队列。
线程池重用最活跃的线程,以更好地利用CPU缓存。这是一个很小的调整,会对性能产生重大影响。
当线程从用户连接执行语句时,性能架构检测会将线程活动记入用户连接。否则,性能架构会将活动计入线程池。
这是一个线程组可能有多个线程开始执行语句的条件示例:

一个线程开始执行一条语句,但是运行了足够长的时间才被认为已停止。即使第一个线程仍在执行,线程组也允许另一个线程开始执行另一个语句。
一个线程开始执行一条语句,然后被阻塞,并将其报告回线程池。线程组允许另一个线程开始执行另一个语句。
一个线程开始执行一条语句,被阻塞,但是没有报告该语句已被阻塞,因为在使用线程池回调进行检测的代码中不会发生阻塞。在这种情况下,线程在线程组中似乎仍在运行。如果该块的持续时间足够长,以至于该语句被认为已停止,则该组将允许另一个线程开始执行另一个语句。
线程池被设计为可在越来越多的连接上进行扩展。它还旨在避免因限制正在执行的语句数而引起的死锁。重要的是,不向线程池报告的线程不会阻止其他语句的执行,从而导致线程池死锁。此类声明的示例如下:

长时间运行的语句。这些将导致仅少数语句使用所有资源,并且它们可能阻止所有其他语句访问服务器。
二进制日志转储线程读取二进制日志并将其发送到从属服务器。这是一种长时间运行的“语句”,可以运行很长时间,并且不应阻止其他语句的执行。
在行锁,表锁,睡眠或其他任何未由MySQL Server或存储引擎报告回线程池的阻塞活动中阻塞的语句。
在每种情况下,为防止死锁,该语句在未快速完成时将其移至停顿类别,以便线程组可以允许另一个语句开始执行。通过这种设计,当线程执行或长时间阻塞时,线程池将线程移至停滞类别,并且在语句执行的其余部分,它不会阻止其他语句执行。

可以出现的最大线程数是max_connections和的总和thread_pool_size。在所有连接都处于执行模式并且为每个组创建一个额外的线程以侦听更多语句的情况下,可能会发生这种情况。这不一定是经常发生的状态,但理论上是可能的。

线程池调整
本节提供有关设置线程池系统变量以获得最佳性能的指南,该变量使用诸如每秒事务之类的指标进行测量。

thread_pool_size是控制线程池性能的最重要参数。只能在服务器启动时设置。我们在测试线程池方面的经验表明:

如果主存储引擎是InnoDB,则最佳thread_pool_size设置可能在16到36之间,最常见的最佳值往往在24到36之间。我们没有看到任何设置超过36的最佳情况。在小于16的最佳值的特殊情况下。

对于诸如DBT2和Sysbench之类的工作负载,最佳InnoDB似乎通常约为36。对于非常密集的写工作负载,最佳设置有时可能会更低。

如果主存储引擎是MyISAM,则该thread_pool_size设置应该相当低。最佳性能通常是从4到8的值。较高的值往往会对性能产生负面影响,但影响不大。
另一个系统变量thread_pool_stall_limit对处理阻塞的和长时间运行的语句很重要。如果所有阻止MySQL服务器的调用都报告给线程池,它将始终知道何时阻止执行线程。但是,这可能并不总是正确的。例如,在未使用线程池回调进行检测的代码中可能发生块。对于这种情况,线程池必须能够识别似乎被阻塞的线程。这可以通过超时来完成,可以使用thread_pool_stall_limit系统变量来调整其长度。此参数确保服务器不会被完全阻止。的价值thread_pool_stall_limit上限为6秒,以防止服务器死锁。

thread_pool_stall_limit还使线程池能够处理长时间运行的语句。如果允许长时间运行的语句阻止线程组,则分配给该组的所有其他连接都将被阻止,直到长时间运行的语句完成,才能开始执行。在最坏的情况下,这可能需要数小时甚至数天。

thread_pool_stall_limit应该选择的值,以使执行时间长于其值的语句被视为停顿。停顿的语句会产生很多额外的开销,因为它们涉及额外的上下文切换,在某些情况下甚至会涉及额外的线程创建。另一方面,将thread_pool_stall_limit参数设置得太高意味着长时间运行的语句将阻塞许多短时间运行的语句,其持续时间超过了必要。较短的等待值使线程可以更快地启动。较短的值也可以更好地避免死锁情况。长等待值对于包含长时间运行的语句的工作负载很有用,以避免在执行当前语句时启动太多新语句。

假设服务器执行一个工作负载,即使加载了服务器,其中99.9%的语句在100ms内完成,而其余语句花费100ms到2个小时相当均匀地散布。在这种情况下,设置thread_pool_stall_limit为10(意味着100ms)是有意义的。默认值60ms适用于主要执行非常简单的语句的服务器。

thread_pool_stall_limit可以在运行时更改该参数,以使您达到适合服务器工作负载的平衡。假设tp_thread_group_stats已启用该表,则可以使用以下查询来确定已暂停的已执行语句的比例:

SELECT SUM(STALLED_QUERIES_EXECUTED) / SUM(QUERIES_EXECUTED)
FROM performance_schem .tp_thread_group_stats;
此数字应尽可能低。要减少语句停顿的可能性,请增加的值thread_pool_stall_limit。

一条语句到达时,在实际开始执行之前最多可以延迟多少时间?假设满足以下条件:

低优先级队列中有200条语句排队。
高优先级队列中有10条语句排队。
thread_pool_prio_kickup_timer设置为10000(10秒)。
thread_pool_stall_limit设置为100(1秒)。
在最坏的情况下,这10个高优先级语句代表10个长时间连续执行的事务。因此,在最坏的情况下,不会将任何语句移到高优先级队列,因为它始终已经包含等待执行的语句。10秒钟后,新语句可以移动到高优先级队列中。但是,在可以移动它之前,必须先移动它之前的所有语句。这可能还需要2秒钟,因为每秒最多将100条语句移到高优先级队列。现在,当语句到达高优先级队列时,它前面可能会存在许多长时间运行的语句。在最坏的情况下从高优先级队列中检索下一个语句之前,每个语句都会停滞,每个语句将花费1秒。因此,在这种情况下,将需要222秒才能开始执行新语句。

此示例显示了应用程序的最坏情况。如何处理取决于应用程序。如果应用程序对响应时间有很高的要求,那么它很可能应该将用户本身限制在更高的级别。否则,它可以使用线程池配置参数来设置某种最大等待时间。

posted on 2022-06-27 14:59  多一点  阅读(184)  评论(0编辑  收藏  举报

导航