RPC框架(6)——对服务端进行并发优化

194 阅读2分钟

1、分析当前存在的问题

image.png 当数据抵达provider之后,首先经过一个自定义解码器,然后进入到ServerHandle中进行处理

在ServerHandle中,根据第2节中的代码可以看出,ChannelHandler中的业务逻辑,正常是由NioEventLoop线程串行执行的。这时候就会出现问题了:

首先,通过解码器将字节序列转换为消息对象,再将消息对象传给后续业务的handler进行处理,如果后续业务需要进行IO查询,或者调用第三方插件,有可能会消耗大量的时间,占用额外的线程资源。

  • 在ChannelHandler中编写了可能会阻塞NIO线程的代码,但是用户没有意识到,如数据库查询、第三方服务的远程调用、消息中间件等
  • 用户知道需要耗时,但是处理过程中出错了,比如业务线程池阻塞、任务队列已满等

所以,不能使用默认的串行编程,否则一旦出现阻塞,就将影响其他服务的远程调用

服务端改善

查看java.nio.channels.SelectionKey的源码 ,可以知道只有四种NIO事件:read、write、connect、accept。

Netty自身设计了不同的线程池管理不同的事件,服务端的workerGroup负责读写事件,bossGroup负责accept事件。

但是业务线程最好不要交给NioEventLoopGroup线程池处理。否则可能导致IO处理任务与业务执行任务之间发生资源竞争,造成阻塞。所以,面向具体业务,较给独立的业务线程池处理

在Netty的内部初始化channelHandler的时候,我们可以通过手动指定线程池的方式来保证handler所使用的线程池和NioEventLoopGroup线程池进行隔离。

使用阻塞队列提高吞吐性能

设置一条单独的阻塞队列用来接收请求,然后在队尾设置一个业务线程池进行消息的消费。当访问量巨大的时候,还可以通过横向扩展,非常简单地实现扩容。我们可以专注于提高单点地性能。

初始化一个容量为512的阻塞队列,当收到链接通知的时候,就将它放入到阻塞队列里面,然后在读取出来,放到具体的业务池中进行处理。

image.png

优化之后的ServerHandler就可以变得很简单了

image.png

客户端改善:

在Rpc包装类中写上异步相关的属性:

image.png 此时,只需要注意两点:

  • 客户端启动时,将异步属性设为true;
  • 在代理模块中,加入一个if判断,如果为异步请求,就不必要在RESP_MAP中判断是否有响应结果了。