MyCAT线程模型分析

MyCAT线程模型

 

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

Mycat线程介绍

 

Timer

Timer单线程仅仅负责调度,任务的具体动作交给timerExecutor。

TimerExecutor线程池,

默认大小N=2

任务通过timer单线程和timerExecutor线程池共同完成。这个1+N的设计方式比较巧妙!

但是timerExecutor跟aioExecutor大小默认一样,不太合理,定时任务没有那么大的运算量。

NIOConnect主动连接事件分离器

一个线程,负责作为客户端连接MySQL的主动连接事件

Server被动连接事件分离器

一个线程,负责作为服务端接收来自业务系统的连接事件

Manager被动连接事件分离器

一个线程,负责作为服务端接收来自管理系统的连接事件

NIOReactor读写事件分离器

默认个数N=processor size,通道建立连接后处理NIO读写事件。

由于写是采用通道空闲时其它线程直接写,只有写繁忙时才会注册写事件,再由NIOReactor分发。所以NIOReactor主要处理读操作

BusinessExecutor线程池

默认大小N=processor size,任务队列采用的LinkedTransferQueue

所有的NIOReactor把读出的数据交给BusinessExecutor做下一步的业务操作

全局只有一个BusinessExecutor线程池,所有链接通道随机分成多个组,然后每组的多个通道共享一个Reactor,所有的Reactor读取且解码后的数据下一步处理操作,又共享一个BusinessExecutor线程池

 

一个SQL请求的线程切换

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

 

Cobar线程介绍

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

Timer

Timer单线程仅仅负责调度,任务的具体动作交给timerExecutor。

TimerExecutor线程池,

默认大小N=2

任务通过timer单线程和timerExecutor线程池共同完成。这个1+N的设计方式比较巧妙!

但是timerExecutor跟aioExecutor大小默认一样,不太合理,定时任务没有那么大的运算量。

Server被动连接事件分离器

一个线程,负责作为服务端接收来自业务系统的连接事件

Manager被动连接事件分离器

一个线程,负责作为服务端接收来自管理系统的连接事件

R读写事件分离器

客户端与Server连接后,由R线程负责读写事件(写事件大部分有W线程负责,只有在网络繁忙时才会由小部分写事件是由R线程完成的)。

Handler和Executor线程池

R线程接收到读事件后解码出一个完整的MySQL协议包,下一步由Handler线程池进行SQL解析、路由计算。然后执行任务从Handler线程池转移到Executor线程池,以阻塞方式发送给后端MySQL Server。Executor收到MySQL Server应答后,会由最后一个Executor线程进行聚合,然后交给W线程

W线程

W线程不停遍历LinkedBlockingQueue检查是否有写任务,若有则写入Socket Channel。当Channel繁忙时,W线程会注册OP_WRITE事件,通过R线程进行候补写操作。

ManageExecutor线程池

Cobar对来自Manager的请求和来自Server的请求做了分离,来自管理系统的请求,专门由ManageExecutor线程池处理。

InitExecutor线程池

用来进行后端链路初始化。

 

Cobar为什么那么多个线程池?

可以发现Cobar有下面这么多个线程池

- TimerExecutor线程池(一个)

- InitExecutor线程池(一个)

- ManageExecutor线程池(一个)

- Handler线程池(N个)

- Executor线程池(N个)

 

注意上面的个数单位是线程池,不是线程!所以看起来有些眼花缭乱吧?

我不是Cobar的原作者,只能猜测最什么设计这么多线程池?那就是因为后端采用了BIO!

  • 因为后端BIO,所以每一个请求到后端查询,都要阻塞一个线程,前端NIO(Reactor-R线程)必须要把执行任务交给Executor线程池。

  • 由于存在聚合要求,前端NIO的一个SQL请求可能会对应多个后端请求,所以不只要阻塞一个Executor线程。为此增加了Handler做中间SQL解析、路由计算,路由计算完毕后再交给Executor执行

  • 由于后端是阻塞方式,在时,会导致Executor无空闲线程,为了避免管理端口输入名命令无任何响应的现象,为此增加一个ManageExecutor线程池,专门处理ManageExecutor线程

  • 在后端BIO时,除了读写是阻塞方式外,链路建立过程也是阻塞方式,若同时链路建立请求多,也会阻塞大量线程。为避免业务、管理的相互干扰,为此增加了一个InitExecutor线程池专门做后端链路建立

  • 所以如果后端BIO改为NIO,并优化逻辑执行过程,避免线程sleep或长时间阻塞,尽量通过Reactor直接计算,就可以大大降低线程上下文切换的损耗,上述各眼花缭乱的线程池就可以合并为一个业务线程池。

一个SQL请求的线程切换

下面是一个SQL请求执行过程的线程切换,可以看到Cobar的线程上下文切换还是比较多的

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

 

MyCAT与Cobar的比较

 

MyCAT比Cobar减少了线程切换

Cobar的后端采用BIO通信,后端读与后端写因为线程阻塞了,不存在线程切换,没有可比性,所以我们只比较NIO和业务逻辑部分。

Cobar的线程模型中存在着大量的上下文切换,MyCAT的线程调度尽量减少了线程间的切换,以写为例

Cobar是业务线程先把写请求交给专门的W线程,W线程再写过程中发现通道繁忙时再交给R线程;MyCAT对写的做法是业务线程发现通道空闲直接写,只有在通道繁忙时再交给Reactor线程。

减少线程切换与业务可能停顿的矛盾

MyCAT几乎已经达到了线程简化的最高境界,有一个看似可行的方法:可以配置多个NIOReactor,尽可能所有读、解码、业务处理都在Reactor线程中完成,而不必把任务交给BusinessExecutor线程池,从而减少线程的上下文切换,提高处理效率。

但是,不管配置几个Reactor,还是要求多个通道共享一个Reactor,(为什么?因为Reactor最多十几个、几十个,并发的链接通道可能上万个!)如果Reactor在读和解码请求后顺序处理业务逻辑,那么在处理业务逻辑过程中,Reactor就无法响应其它通道的事件了,这个时候如果正好有共享同一个Reactor的其它通道的请求过来,就会出现停顿的现象。

那么如何做呢,就需要具体问题具体分析,要对业务逻辑进行归类:

- 对于业务较重的,比如大结果集排序,则送到BusinessExecutor线程池进行下一步处理;

- 于业务较轻的,比如单库直接转发的情况,则由Reactor直接完成,不再送线程池,减少上下文切换。

特别说明ER分片机制

如果涉到ER分片,MyCAT目前的机制:计算路由时以阻塞同步方式调用FetchStoreNodeOfChildTableHandler,若由Reactor直接进行路由计算,会导致其它通道停顿现象。把ER分片同步改异步是个看似可行的方法,但这个改造工作量较大,会造成原来完整路由计算逻辑的碎片化。

即使ER分片同步改异步了,每次子表操作都要遍历父表对性能损耗较大,即使采用缓存也不能最终解决问题。个人觉得,ER分片这个功能比较鸡肋,建议生产部署时绕开这个功能,直接通过关联字段分片或表设计时增加冗余字段。

数据验证

1.测试sql从收到请求到下推的总时长,如果时间可容忍,则不必切换到线程池。忽略ER分片。

2.对于manager端口的命令,若存在执行时间比较的,也需要改为线程池来执行

3.对于收到的应答,大部分都不必切换到线程池。

4.对于大量数据排序,只有在排序时,构造执行任务,切换到线程池完成。

更多内容请关注微信公众号:it_haha

转载于:https://my.oschina.net/u/2836632/blog/704074

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值