本篇文章写于2021年12月25日,从公众号|掘金|CSDN手工同步过来(博客搬家),本篇为原创文章。
在xxl-job中,RPC即用于调度中心请求执行器执行job、kill job,也用于执行器请求调度中心主动注册、执行结果上报。
xxl-job实现的RPC类似Feign框架,是基于http这种七层协议实现的,而http协议是无状态的,因此一个连接不能同时被用于多个线程发送请求,只能等待一个请求响应后再放入连接池被其它线程使用。
对于执行器而言,由于只与调度中心交互,请求量也少,因此这种RPC实现不会对执行器性能有什么影响。
调度中心则不同,它需要同时与多个执行器交互,如果同一时刻需要下发几百个执行job的请求给执行器,使用这种阻塞的RPC,意味着需要开启几百个线程,使用几百个连接发送请求,而这几百个线程都需要阻塞等待响应,Job越多,需要的线程数就会越多,对调动中心的性能影响就越大。
xxl-job即便更新到最新的2.x版本,还是使用同步阻塞的RPC调用。
知道了为什么同步RPC会影响调度中心的性能,再来理解为什么异步RPC能解决这个问题的原因就容易很多。
响应式编程通过事件触发回调解决同步阻塞问题,要求整条链路上都无阻塞,即无I/O阻塞(数据库操作、网络请求响应等)。
我们重构后的新版本调度中心(xxl-job),我们使用了reactor-netty-http框架实现异步RPC,当然,我们需要解决的只是调度中心的性能问题,因此执行器是可以不用改动的、兼容旧版本的。
reactor-netty-http并非解决http这种协议的无状态问题,依然一个连接同时只能用于发送一个请求,需要等待响应后才能被用于发送其它请求。但reactor-netty-http不会创建一个线程去阻塞等待,而是通过事件轮询方式,去消费响应,释放连接回连接池。
在使用reactor-netty-http之后,我们只需要配置CPU核心数个工作线程处理向执行器发送RPC请求,reactor-netty-http在一个线程上完成请求发送后,就会继续处理其它请求发送,当轮询到某些连接收到客户端响应事件后,再处理这些响应,释放连接回连接池,调回doNext。
最终从效果上看,基于reactor-netty-http实现的RPC,类似于dubbo使用长连接实现的异步RPC。
reactor-netty-http可能会创建大量连接,但不会创建大量线程,可用使用netstat观察连接数的增长,使用jstack工具观察reactor-netty-http创建的线程数。
要解决调度的性能问题,除了异步RPC是不够的,异步RPC只能帮我们解决下发请求的阻塞问题。而且响应式编程要求整个链路上必须无阻塞。那么异步回调的事件消费也必须是异步的。
同时,我们将执行器节点信息、Job数据也完全存储在内存中,让触发->job查询->执行器查询->执行器节点查询->日记打印->调度下发整条链路都完全无阻塞。而数据的一致性,则通过分布式一致性算法保证,为了稳定以及开发简单,我们基于zookeeper实现。