做响应式网站有什么插件,wordpress钻石插件,河南省人事考试中心,wordpress 页面编辑前言 对于应用开发人员来说肯定听说过连接池#xff0c;却不一定听说过线程池#xff0c;虽然二者都是池化的概念#xff0c;但还是有所不同的#xff1a; 连接池面向的是数据库连接#xff0c;是针对数据库Client侧的优化。连接池可将数据库连接数固定在一定范围内#… 前言 对于应用开发人员来说肯定听说过连接池却不一定听说过线程池虽然二者都是池化的概念但还是有所不同的 连接池面向的是数据库连接是针对数据库Client侧的优化。连接池可将数据库连接数固定在一定范围内避免业务端创建过多连接、达到server端最大连接数导致后续连接失败的问题同时由于保留了一定数量的连接当业务端新请求到达时可直接复用、无需新建连接节省业务侧建连时间。 线程池面向的是数据库内的工作线程是针对数据库Server侧的优化。线程池将工作线程数量固定在一定范围内当连接数过多时会涉及排队可避免并发过高时频繁上下文切换、缓存失效等问题有效提升CPU利用率及数据库吞吐。 背景 社区版MySQL的连接处理方法默认是 one-thread-per-connection 即为每个连接创建一个工作线程简称每 线程Per_thread模式。这种模式存在如下弊端 由于系统的资源是有限的随着连接数的增加资源的竞争也会增加连接的响应时间也随之增加如 response time图所示。 在资源未耗尽时数据库整体吞吐随着连接数增加。一旦连接数超过了某个耗尽系统资源的临界点由于各线 程互相竞争 CPU时间片在大量线程间频繁调度不同线程上下文频繁切换徒增系统开销数据库整体吞吐 反而会下降如下图所示 问如何避免在连接数暴增时因资源竞争而导致系统吞吐下降的问题呢 MariaDB Percona中给出了简洁的答案 线程池。 线程池的原理在percona blog 中有生动的介绍其大致可类比为早高峰期间大量汽车想通过一座大桥如果采 ⽤ one-thread-per-connection 的方式则放任汽车自由行驶由于桥面宽度有限最终将导致所有汽车寸步难 行。线程池的解决方案是限制同时行驶的汽车数让桥面时刻保持最大吞吐尽快让所有汽车抵达对岸。 回归到数据库本身MySQL默认的每线程模式每个会话都会创建一个独占的线程。 当有大量的会话存在时会导 致大量的资源竞争同时大量的系统线程调度和缓存失效也会导致性能急剧下降。 线程池功能旨在解决以上问题在存在大量连接的场景下通过线程池实现线程复用 当连接多、并发低时通过连接复用避免创建大量空闲线程减少系统资源开销。 当连接多、并发高时通过限制同时运行的线程数将其控制在合理的范围内可避免线程调度工作过多和大 量缓存失效减少线程池间上下文切换和热锁争用从而对OLTP场景产生积极影响。 当连接数上升时在线程池的帮助下将数据库整体吞吐维持在一个较高水准如下图所示 适用场景 线程池采用一定数量的工作线程来处理连接请求在查询相对较短且工作负载受CPU限制的情况下效率最高通常 适应于 OLTP 工作负载的场景 对于大量连接的OLTP短查询场景有较大收益 对于大量连接的只读短查询也有明显收益 可有效避免大量连接高并发下数据库性能衰减。 如果工作负载不受CPU限制那么仍然可以通过限制线程数量来节省数据库内存缓冲区所占用的内存。 线程池的不足在于当请求偏向于慢查询时工作线程阻塞在高时延操作上难以快速响应新的请求导致系统吞吐 量反而相较于传统 one-thread-per-connection 模式更低。因此不太适用于以下场景 具有突发工作负载的场景。在这种场景下许多用户往往长时间处于非活跃状态但个别时候又处于特别活跃 的状态同时对延迟的容忍度较低因此线程池节流效果不太理想。不过即使在这种情况下也可以通 过调整线程的退役频率 thread_pool_idle_timeout 参数来提高性能。 高并发、长耗时语句的场景。在这种场景下并发较多且都是执行时间较长的语句会导致工作线程堆积 一旦达到上限完全阻止后续语句的执行比如最常见的数据仓库场景。当然这样的场景下不管是否使用线 程池数据库的表现都是不够理想的 需要应用侧控制慢查询的并发度。 有较严重的锁冲突的场景。如果处于锁等待的工作线程数超过总线程数也会堆积起来阻止无锁等待的处理 请求。比如某个会话执行 FLUSH TABLES WITH READ LOCK 语句获得全局锁后暂停 那么其他执行写操作的 客户端连接就会阻塞当阻塞的数量超过线程池的上限时整个 server 都会阻塞。当然这样的场景下不管 是否使用线程池数据库的表现都是不够理想的 需要应用侧进行优化。 极高并发的Prepared Statement请求。使用Prepared Statement时会使用MySQL Binary Protocol会 增加很多的网络开销比如参数的绑定、结果集的返回在极高请求压力下会给epoll监听进程带来一定的压 力处于事务状态中时可能会让普通请求得不到执行机会。 为了应对上述的阻塞问题一般会允许配置 或 来管理连接。 总结一句话 线程池更适合短连接或短查询的场景。 行业方案 由于市面上的线程池方案大多都借鉴了 percona 、mariadb 的方案因此首先介绍下 percona 线程池的工作机 制再说明其他方案相较于 percona 做了什么改进。 腾讯云TXSQL整合了percona的线程池方案在此基础上实现了线程池的动态切换动态开启或关闭线程 池、负载均衡优化percona分配线程组时采用的轮询算法TXSQL做了改进。 阿里云AliSQL一定程度上也借鉴了percona的线程池方案主要不同在于其采用了两层队列第一层为网 络请求队列区分为普通队列、高优先级队列第二层为工作任务队列区分为查询队列、更新队列、事 务队列。 Percona Percona 的实现移植自MariaDB并在此基础上支持了优先级队列是现在主流的开源线程池方案。其基本原理为 预先创建一定数量的工作线程worker线程。在线程池监听线程listener线程从现有连接中监听到新请求时判断当前请求是否属于高优先级队列若属于则放入高优先级队列反之则放入低优先级队列之后由工作线程按照先高优后低优的顺序来处理请求。工作线程在服务结束之后不销毁线程处于 idle 状态一段时间后会退出而是保留在线程池中继续等待下一个请求来临。 MariaDB vs Percona Percona 的实现移植自 MariaDB并在此基础上添加了一些功能。特别是 Percona 在 5.5-5.7 版本添加了优先级 调度。而 MariaDB 10.2 也支持了优先级调度和 Percona 的工作方式类似只是细节有所不同。 MariaDB 10.2 版本的参数 thread_pool_priorityauto,high,low 对应于 Percona 的 MariaDB 10.2 版本中只有处于事务中的连接才是高优先级而 Percona 中符合高优先级的情况包括 1处 于事务中 2持有表锁 3持有 MDL 锁 4持有全局读锁 5持有 backup 锁。 关于避免低优先级队列语句饿死的问题 Percona 有一个 thread_pool_high_prio_tickets 参数用于指定每个连接在高优先级队列中的 tickets 数量而 MariaDB 没有相应参数。 MariaDB 有一个 thread_pool_prio_kickup_timer 参数可让低优先队列中的语句在等待指定时间 后移入高优先级队列而 Percona 没有相应参数。 MariaDB 有参数 thread_pool_dedicated_listener 、 thread_pool_exact_stats 而 Percona 没 有。 thread_pool_dedicated_listener 可用于指定专有listener线程其只负责 epoll_wait 等待网 络事件不会变为 worker 线程。默认为OFF表示不固定listener。 thread_pool_exact_stats 是否使用高精度时间戳。 MariaDB 比如 10.9 版本在 information_schema 中新增了四张表 THREAD_POOL_GROUPS 、 THREAD_POOL_QUEUES 、 THREAD_POOL_STATS 、 THREAD_POOL_WAITS 便 于监控线程池状态。 MySQL 企业版 vs MariaDB MySQL 企业版是在5.5 版本引入的线程池以插件的方式实现的。 相同点 都具备线程池功能都支持 thread_pool_size 参数。 · 都支持专有 listener 线程 thread_pool_dedicated_listeners 参数。 都支持高低优先级队列且在避免低优先级队列事件饿死方面二者采用了相同方案即低优先级队列事件等 待一段时间 thread_pool_prio_kickup_timer 参数即可移入高优先级队列。 都使用相同的机制来探测处于停滞stall状态的线程都提供了 thread_pool_stall_limit 参数 MariaDB 单位是 ms MySQL 企业版单位是10ms。 不同点 Windows 平台实现方式不同。 MariaDB 使用Windows自带的线程池而 MySQL企业版的实现用到了 WSAPoll() 函数为了便于移植 Unix 程序而提供因此 MySQL 企业版的实现将不能使用命名管道和共享内存。 MariaDB 为每个操作系统都使用最高效的 IO 多路复用机制。 Windows原生线程池 ○ Linux epoll ○ Solaris ( event ports ) FreeBSD and OSX kevent ) ⽽ MySQL 企业版只在 Linux 上才使用优化过的 IO 多路复用机制 epoll 其他平台则用 poll 。 移动云方案 概述 核心功能与 percona 线程池方案类似优先级调度算法 及 避免低优先级队列语句饿死的策略也有所参考除此之外额外做了一些改进 使用插件方式实现。 · 借鉴了 MariaDB 的实现添加了参数 thread_pool_dedicated_listener 即支持固定 listener 功能。 借鉴了 MariaDB 的实现在 information_schema 中新增了四张表 THREAD_POOL_GROUPS 、 THREAD_POOL_QUEUES 、 THREAD_POOL_STATS 、 THREAD_POOL_WAITS 便 于监控线程池状态。 一些优化点 ○ 添加参数 thread_pool_toobusy 表示线程组是否过于忙碌的线程数阈值。当线程组中活跃的工作线 程数锁或IO等待中的工作线程数该阈值加1时认为线程组过于忙碌不再处理低优先级的任务等 待当前执行的任务和高优先级队列中的任务被处理直到线程组回到非忙碌的状态。 该优化能避免 percona的问题——极端高并发场景下随着工作线程的持续创建退化为每线程模式。 高优先级 session 独占 worker 线程在连接数很大高负载时对于一些事务取得了锁等资源时可 优先处理。 . percona/mariadb的处理逻辑是此类连接发生可读事件后会被线程组加到优先队列中等待空闲 worker线程优先处理。 . 移动云优化后的逻辑 需要优先处理的session不将当前worker还给线程池继续独占当前 worker线程类似每线程每连接的模式独占worker线程专用于处理该优先连接之后的所有语 句直到该连接释放了优先资源转为普通连接例如该连接事务执行结束释放锁资源。 listener 线程调用 io_poll_wait 后只要线程组不繁忙则按需批量唤醒或创建一批 worker 线程根据 本次获得的 event 数量、活跃线程数来决定worker数量。 设计方案 下面从线程池架构、新连接的创建与分配、listener线程、worker线程、timer线程等几个方面来介绍线程池的实现。 1. 线程池的架构 线程池由多个线程组thread group 和timer线程组成如下图所示。 线程组的数量是线程池并发的上限通常而言线程组的数量需要配置成数据库实例的CPU核心数量 通过参 数 thread_pool_size 设置从而充分利用CPU。线程组之间通过 线程ID % 线程组数 的方式分配连接线程组 内通过竞争方式处理连接。 线程池中还有一个服务于所有线程组的timer线程负责周期性检查时间间隔为 threadpool_stall_limit 毫 秒检查线程组是否处于阻塞状态。当检测到阻塞的线程组时 timer线程会通过唤醒或创建新的工作线程来让线程组恢复工作。 创建新的工作线程并不是每次都能创建成功的要根据当前的线程组中的线程数是否大于线程组中的连接数活跃线程 数是否为0以及上一次创建线程的时间间隔是否超过阈值这个阈值与线程组中的线程数有关线程组中的线程 数越多时间间隔越大等条件来决定。 线程组内部由多个worker线程、0或1个动态listener线程、高低优先级事件队列由网络事件event构成、 mutex 、epollfd、统计信息等组成。如下图所示 worker 线程主要作用是从队列中读取并处理事件。 · 如果该线程所在组中没有listener线程则该worker线程将成为listener线程通过epoll的方式监听数据并 将监听到的event放到线程组中的队列。 worker线程数目动态变化并发较大时会创建更多的worker线程当从队列中取不到event时 work线程将 休眠超过一定时间后结束线程。 一个worker线程只属于一个线程组。 listener 线程当高低队列为空listener线程会自己处理无论这次获取到多少事务。否则listener线程会把请求加入到队列中如果此时active_thread_count0则唤醒一个工作线程。 高低优先级队列为了提高性能将队列分为高优先队列和普通队列。这里采用引入两个新变量 thread_pool_high_prio_tickets 和 thread_pool_high_prio_mode 。由它们控制高优先级队列策略。对每 个新连接分配可以进入高优先级队列的ticket。 2. 新连接的创建与分配 新连接接入时线程池按照新连接的线程id取模线程组个数来确定新连接归属的线程组 group_count 。 选定新连接归属的线程组后 新连接申请被作为事件放入低优先级队列中等待线程组中worker线程将高优先级事 件队列处理完后就会处理低优先级队列中的请求。 3. listener线程 listener线程是负责监听连接请求的线程 每个线程组都有一个listener线程。 线程池的listener采用epoll实现。当epoll监听到请求事件时 listener会根据请求事件的类型来决定将其 放入哪个优先级事件队列。 将事件放入高优先级队列的条件如下 当前线程池的工作模式为高优先级模式在此模式下只启用高优先级队列。 当前线程池的工作模式为事务模式在此模式下每个连接的event最多被放入高优先级队 列 threadpool_high_prio_tickets 次。超过 threadpool_high_prio_tickets 次后该连接的请求事件 只能被放入低优先级同时也会重置票数。 以下条件只需要满足其一即可 连接持有表锁 连接持有mdl锁 连接持有全局读锁 连接持有backup锁 被放入高优先级队列的事件可以优先被worker线程处理。 只有当高优先级队列为空且当前线程组不繁忙的时候才处理低优先级队列中的事件。线程组繁忙的判断条件是当前组内活跃工作线程数组内处于等待状态的线程数大于线程组工作线程额定值 thread_pool_oversubscribe1 。 listener线程将事件放入高低优先级队列后如果线程组的活跃worker数量为0则唤醒或创建新的worker线程来 处理事件。 线程池中listener线程和worker线程是可以互相切换的详细的切换逻辑会在「worker线程」一节介绍。 epoll监听到请求事件时如果高低优先级事件队列都为空意味着此时线程组非常空闲大概率不存在活跃 的worker线程。 listener在此情况下会将除第一个事件外的所有事件按前述规则放入高低优先级事件队列 然后退出监听任 务亲自处理第一个事件。 这样设计的好处在于当线程组非常空闲时可以避免listener线程将事件放入队列唤醒或创建worker线程来 处理事件的开销提高工作效率。 4. worker线程 worker线程是线程池中真正干活的线程正常情况下每个线程组都会有一个活跃的worker线程。 worker在理想状态下可以高效运转并且快速处理完高低优先级队列中的事件。但是在实际场景中worker经常 会遭遇IO、锁等等待情况而难以高效完成任务此时任凭worker线程等待将使得在队列中的事件迟迟得不到处理 甚至可能出现长时间没有listener线程监听新请求的情况。为此每当worker遭遇IO、锁等等待情况如果此时线 程组中没有listener线程或者高低优先级事件队列非空并且没有过多活跃worker则会尝试唤醒或者创建一个 worker。 为了避免短时间内创建大量worker带来系统吞吐波动线程池创建worker线程时有一个控制单位时间创建 worker线程上限的逻辑线程组内连接数越多则创建下一个线程需要等待的时间越长。在极端情况下可能会出现worker线程总数接近最大连接数max_connections的情况相当于退化为每线程模式。 当线程组活跃worker线程数量大于等于 too_many_active_threads1 时认为线程组的活跃worker数量过多。 此时需要对worker数量进行适当收敛首先判断当前线程组是否有listener线程 如果没有listener 线程则将当前worker线程转化为listener线程。 如果当前有listener线程则在进入休眠前尝试通过 epoll_wait 获取一个尚未进入队列的事件成功获取到 后立刻处理该事件否则进入休眠等待被唤醒等待 threadpool_idle_timeout 时间后仍未被唤醒则销毁 该worker线程。 worker线程与listener线程的切换如下图所示 5. timer线程 timer线程每隔 threadpool_stall_limit 时间进行一次所有线程组的扫描。 当线程组高低优先级队列中存在事件并且自上次检查至今没有新的事件被worker消费则认为线程组处于停滞状 态。 停滞的主要原因可能是长时间执行的非阻塞请求。 timer线程会通过唤醒或创建新的worker线程来让停滞的线程组恢复工作。 timer线程除上述工作外 还负责终止空闲时间超过 wait_timeout 秒的客户端。 性能结果 移动云优化后的线程池会将工作线程数控制在一定范围内随着并发数的增加性能基本与最高点持平无明显下降趋势。 总结 最后总结下本文中几种方案在使用方面的区别。 功能 MySQL 企业版 MariaDB Percona 移动云 功 能 实 现 方 式 插件 非插件 非插件 插件 版 本 5.5 版本引入 5.5 版本引入 10.2 版 本完善 5.5-5.7/8.0 5.7/8.0 借 鉴 方 案 - - MariaDB Percona MariaDB 10.2 及之 后版本 动 态 开 关 线 程 池 插件式不支持 不支持 不支持 插件式不支持 优 先 级 处 理 策 略 设定高低优先级且低 优先级事件等待一段时 间可升为高优先级队列 设定高低优先级且低 优先级事件等待一段时 间可升为高优先级队列 设定高低优先级 且限制每个连接在 高优先级队列中的 票数 设定高低优先级 且限制每个连接在 高优先级队列中的 票数 监 控 - 2个状态变量 2个状态变量 4张状态信息表 如果线程池阻塞了怎么处理 MySQL 8.0.14 以前的版本使用 admin_port 功能。 功能percona mariadb 8.0.14及之后版本官方支持了 参数 由于业内线程池方案基本都会参考 MariaDB或 Percona因此以 Percona和 MariaDB 的参数为准基于MySQL 8.0总结其他方案是否有相同或类似参数。 注意 MySQL 企业版核心方案与 MariaDB 类似且关于差异点官方描述较少因此不做对比。 监控 Percona只有两个状态变量 移动云借鉴了MariaDB的实现方式在 information_schema 中增加了四张状态信息表 THREAD_POOL_GROUPS 查询线程组相关信息。 THREAD POOL_QUEUES 查询线程组队列中连接的信息。 THREAD_POOL_STATS 查询线程组状态信息的统计值比如线程组由于check_stall创建的线程数、由listener 线程poll到的任务数等。 THREAD_POOL_WAITS 提供线程组的worker线程在执行SQL语句时各类等待原因的统计数据。 等待原因 有 UNKNOWN 、SLEEP 、DISKIO 、ROW_LOCK 、GLOBAL_LOCK 、META_DATA_LOCK 、TABLE_LOCK、 USER_LOCK 、BINLOG 、GROUP_COMMIT 、SYNC 、NET。 参考链接 1. Percona 1. Thread pool - Percona Server for MySQL
2. SimCity outages, traffic control and Thread Pool for MySQL (percona.com)
3. 线程池详解
2. MariaDB
1. Thread Pool in MariaDB - MariaDB Knowledge Base
3. MySQL 企业版
1. MySQL :: MySQL 8.0 Reference Manual :: 5.6.3 MySQL Enterprise Thread Pool 作者
卢文双中国移动云能力中心数据库产品部 - MySQL内核研发工程师