共计 3653 个字符,预计需要花费 10 分钟才能阅读完成。
作者:郭奥门
爱可生 DBLE 研发成员,负责分布式数据库中间件的新性能开发,答复社区 / 客户 / 外部提出的一般性问题。
本文起源:原创投稿
* 爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。
背景
在理论生产环境,我的项目上线初期流量比拟小,等前面我的项目流量涨上来,dble 内原有的线程配置可能支撑不了上游的压力,此时可能会遇到一系列性能问题,这时就须要调大 processors、backendProcessors 等线程池参数,并依据预期指标及理论线程应用状况屡次调整至最优。
在之前,批改完配置中的线程数之后须要重启能力让配置失效,但这种形式不是很灵便,甚至可能会影响上游的应用。dble 也是思考到这一点,在 3.21.06.* 版本提供了不重启调整线程池数目的形式
命令
update dble_information.dble_thread_pool set core_pool_size = 2 where name = 'BusinessExecutor';
留神:
- 反对动静调整的线程池为:businessExecutor、writeToBackendExecutor、processors(nio 场景下:bootstrap.cnf 中的 usingAIO 值为 0)、backendProcessors(nio 场景下)、backendBusinessExecutor、complexQueryExecutor
- 不反对动静调整 AIO 场景下的 processors、backendProcessors 对应的线程池
- 因为 JDK 原生线程池(ThreadPoolExecutor)扩缩容机制问题,新建的线程和行将被回收的线程须要肯定的机会才会被解决,所以设置 core_pool_size 并不会立刻通过 dble_thread_pool 表查到,但此时应用该线程池不受影响
- 只管线程池数目能够容许不停机的形式调整,但为了防止出现未知问题,倡议不要在流量大的时候调整
原理解读
dble 内线程的应用形式
以后 dble 内应用线程池次要蕴含两种形式:一是 JDK 内置线程池,另外是外置队列 +JDK 内置线程池,上面咱们简略讲一下两种形式的原理
外置队列 + 线程池
DBLE 为了高并发、响应快等特点,在网络 IO 层面采纳了经典的主从 Reactor 多线程模型,这里只阐明 Reactor 模型如何在 DBLE 中落地,不分明模型原理的同学能够参阅:彻底搞懂 Reactor 模型和 Proactor 模型(文末有链接)
DBLE 目前网络模型如上图所示:
1、Reactor 主线程 —— NIOAcceptor 通过 select、accept 事件读取并初步解决 client 连贯,并通过 frontRegisterQueue 队列传递给 Reactor 子线程
2、Reactor 子线程 —— RW 从外置队列中(非线程池外部队列,为了辨别称此时队列为外置队列)取到连贯并注册到以后子线程中,后续通过 read 办法读取数据包并通过 frontHandlerQueue 队列或本地队列传递给工作线程
3、工作线程池内的子线程从外置队列中接管到工作,通过后续的一系列剖析解决后,将后果通过 writeQueue 队列传递给 writeToBackendExecutor 线程,继而发送给前端 client,或通过本地队列传递给 backendBuinessExecutor/complexQueryExecutor 线程间接将后果返回给前端
从 DBLE 网络模型能够看出其外部应用队列 + 线程池的形式来散发解决工作,除此之外在业务解决的过程中也是大量应用了线程池来解决一些耗时的工作
JDK 内置线程池
dble 内线程池是借助了 java 的 ThreadPoolExecutor 类,其运行流程如下:
线程池在外部实际上构建了一个生产者消费者模型,将线程和工作两者解耦,并不间接关联,从而良好的缓冲工作,复用线程。线程池的运行次要分成两局部:工作治理、线程治理。工作治理局部充当生产者的角色,当工作提交后,线程池会判断该工作后续的流转:
(1)间接申请线程执行该工作;
(2)缓冲到队列中期待线程执行;
(3)回绝该工作。线程治理局部是消费者,它们被对立保护在线程池内,依据工作申请进行线程的调配,当线程执行完工作后则会持续获取新的工作去执行,最终当线程获取不到工作的时候,线程就会被回收。
联合 dble 中目前的构造,其外部次要线程应用的形式为:
- businessExecutor、writeToBackendExecutor 相干线程通过外置队列 + 线程池实现调度,在 dble 启动时初始化线程池,线程外部通过轮询形式获取工作(启动时创立的线程称之为常驻线程)
- processors、backendProcessors 相干线程在 nio 状况下通过外置队列 + 线程池实现调度;在 aio 状况下通过 AsynchronousChannelGroup 机制(内置线程池)实现线程治理
- backendBusinessExecutor 相干线程在性能模式下(usePerformanceMode=1),通过外置队列 + 线程池实现调度;反之在后续工作达到时由线程池间接调度
- complexQueryExecutor 相干线程由线程池间接调度(不是在启动时创立的线程称之为十分驻线程,随工作朝生暮死)
具体实现
为了动静的调整线程池的数目,保障扩缩容之后工作都能失常被解决,须要针对以上两种形式作独自解决,具体实现形式如下:
线程池
JDK 原生线程池 ThreadPoolExecutor 提供了如下几个 public 的 setter 办法,如下图所示:
JDK 容许线程池应用方通过 ThreadPoolExecutor 的实例来动静设置线程池的外围策略,以 setCorePoolSize 为办法例,在运行期线程池应用方调用此办法设置 corePoolSize 之后,线程池会间接笼罩原来的 corePoolSize 值,并且基于以后值和原始值的比拟后果采取不同的解决策略。对于以后值小于当前工作线程数的状况,阐明有多余的 worker 线程,此时会向以后 idle 的 worker 线程发动中断请求以实现回收,多余闲暇的 worker 在工作执行实现后也会被回收(有提早);对于以后值大于原始值且以后队列中有待执行工作,则线程池会创立新的 worker 线程来执行队列工作,如果以后线程外部队列没有待执行工作,则会在下次工作须要执行时新建 work 线程(有提早)。
外置队列 + 线程池
线程池能够借助 JDK 提供的 set 办法动静设置池的大小,以后场景下扩容时额定须要为新建的线程绑定外置队列,保障后续的工作能通过外置队列被新建的线程接管并解决,那么在代码中新建线程时须要增加外置队列的援用
缩容时,ThreadPoolExecutor 线程池通过以上形式对闲暇的线程进行回收,针对十分驻线程就只须要设置大小即可;针对正在运行的线程不会被动进行回收,解铃还须系铃人,此时就须要咱们手动敞开,先通过 interrupt 办法把线程标记为 interrupted 状态,同时在线程外部依据状态值判断是否须要退出轮询,后续再借助线程池外部的缩容策略进行回收线程
通过以上两种形式能够实现动静的批改线程池数目,然而为了让 processors、backendProcessors 相干 IO 线程能在扩缩容时平稳过渡,须要额定的做一些必要的“善后”工作
“善后”
processors、backendProcessors 所对应的线程为 DBLE 的 IO 线程,负责对注册到以后线程的连贯申请的接管和后端后果的接管,那么扩容时就须要保障新建的 IO 线程能解决后续的 IO 申请,相应的缩容时须要对回收的线程所绑定的连贯可能转移到其余 IO 线程,保障连贯后续申请能被失常解决
在 DBLE 中对于善后工作所做的解决就是先勾销以后线程中的连贯,再将这些连贯从新注册到新的 IO 线程中,此时删除和从新注册的选取策略为:删除时优先选择线程中绑定连接数最小的,从新注册时优先选择线程中连接数最大的,并依据删除的线程往下抉择
总结
dble 在 3.21.06.* 版本及之后提供了能够不重启来批改线程参数的命令,因为 dble 内不只是简略的应用 JDK 内置的线程池,那么动静批改的命令也不只是应用 JDK 内置的办法去实现,同时也为了兼容外置队列、IO 连贯而做了额定的工作。
尽管咱们在并发测试中动静调整线程池数目并未发现异常状况,然而仍旧倡议在并发量小的时候进行调整,不仅为了线程间切换平稳过渡,也是为了缩小线程调整时资源的应用。理论在应用该命令的时候遇到一些问题,能够反馈在 actiontech/dble: A High Scalability Middle-ware for MySQL Sharding (github.com),帮忙咱们改善
参考
- 彻底搞懂 Reactor 模型和 Proactor 模型:https://cloud.tencent.com/dev…
- Java 线程池实现原理及其在美团业务中的实际:https://tech.meituan.com/2020…