java nio socket 线程池_Java并发服务器编程:将NIO线程池与业务线程池分离

用Java做后端开发不可避免的会用到线程池,比如我们启动一个web server,指定用作处理请求的线程数量,此server便可以并发处理请求,以netty为例,代码如下

EventLoopGroup boss = new NioEventLoopGroup(1);

EventLoopGroup worker = new NioEventLoopGroup(4);

ServerBootstrap serverBootstrap = new ServerBootstrap();

try {

serverBootstrap

.group(boss, worker)

.channel(NioServerSocketChannel.class)

.childHandler(new ChannelInitializer() {

@Override

protected void initChannel(SocketChannel ch) throws Exception {

ch.pipeline().addLast( new HttpServerCodec());

ch.pipeline().addLast(new HttpObjectAggregator(1024 * 1024));

ch.pipeline().addLast(new HttpServerExpectContinueHandler());

ch.pipeline().addLast(new MainRequestHandler());

}

});

ChannelFuture future = serverBootstrap.bind(1234).sync();

future.channel().closeFuture().sync();

} finally {

boss.shutdownGracefully();

worker.shutdownGracefully();

}

我们为这个netty web server指定两个线程池,boss 和 worker,boss线程池用作处理接收网络连接;worker线程池用作处理连接上的读写,于是这个web server便有了并发处理请求的能力。

MainRequestHandler类是这个web server的处理程序,我们会把业务代码写在里面

static class MainRequestHandler extends SimpleChannelInboundHandler {

@Override

protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {

sendResponse(ctx,"hello world");

}

private void sendResponse(ChannelHandlerContext ctx, String responseText ) {

FullHttpResponse response = new DefaultFullHttpResponse(

HttpVersion.HTTP_1_1,

HttpResponseStatus.OK,

Unpooled.copiedBuffer(responseText, CharsetUtil.UTF_8));

response.headers().set("Content-Type", "text/html");

response.headers().set("Content-Length", response.content().readableBytes());

ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

}

}

示例程序会输出hello world。然而,实际开发项目时逻辑肯定不会这么简单,我们假设代码如下

protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {

String url = msg.uri();

String result = "";

if( url.equals("/user_list")) {

result = getUserListFromMySql();

} if(url.equals("news_list")) {

result = getNewsListFromMySql();

}

sendResponse(ctx, result);

}

处理程序需要根据不同的url去mysql中获得对应的数据,而从mysql中取数据不可避免的会有等待,在这个过程中当前线程会阻塞,无法继续处理其他任务。假设server的线程池大小为6,某个mysql查询等待时间为200毫秒,假如在这200毫秒之内,来了7个请求,那么前6个任务占满线程池,第7个任务将无法及时被处理程序处理,也就是说没有办法一下子到达 channelRead0 方法内,只有前面6个任务中有任务处理完毕了,才轮到第7个任务。

表面上看这么做没什么问题,如果服务器实在处理不过来,大不了报502嘛,反正这都是家常便饭。但是,假如有这样的需求:当服务器的任务处理不过来时,需要将这些请求的具体信息记录下来供后续分析优化。显然在程序中无法满足这种需求,因为请求根本没有到达程序,谈何记录呢。当然,也可以给这个web server再加上一个前端,比如nginx,通过分析nginx失败日志也能得到结果,但是显然和我们的程序不在同一个层面,没有浑然一体的感觉,更不能精准的控制请求。

一种解决方法是将web server的网络处理线程池和业务处理线程池分开,Java自从有了NIO,通常服务器阻塞的原因肯定不是socket读写上的性能问题造成,性能的问题总是在业务处理上,比如数据的存取,通过将线程池分离,我们总能保证请求一定会到达程序,至于请求有没有响应都是业务处理程序的事了,跟web server上的socket读写无关。

我们将示例代码修改如下

private static ThreadPoolExecutor executor = null;

private static BlockingQueue taskList = null;

static {

taskList = new LinkedBlockingQueue<>(100);

executor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,taskList);

executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

}

@Override

protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {

try{

executor.execute(()->{

String url = msg.uri();

String result = "";

if( url.equals("/user_list")) {

result = getUserListFromMySql();

} if(url.equals("news_list")) {

result = getNewsListFromMySql();

}

sendResponse(ctx, result);

});

}catch (RejectedExecutionException e) {

System.out.println("任务堆积数量过多");

}

}

我们给业务处理程序单独建立一个线程池,线程大小为5,对应的任务队列是一个有界队列,容量限制为100, 与此同时我们给线程池指定一个饱和策略,当待处理任务数量大于任务队列容量时,放弃任务的执行并抛出异常,这也是线程池的默认饱和策略。那么,当任务数量超限时,RejectedExecutionException异常被捕获,并打印日志,当然,我们也可以在catch块中做一些其他的事情,比如记录这个请求,或者返回异常提示消息给客户端。

这种做法更容易受程序精确的控制,因为请求已经抵达了程序,我们的程序可以精准的处理每个请求,包括无法被处理的请求。如果是传统的方法,socket读写线程和业务处理线程混在一块,那么当业务负载过高时,新进的请求根本无法到达处理程序,nginx 502没商量,比如传统php就是这种德行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值