Netty与Voovan并发性能对比

原创
2017/03/07 18:14
阅读数 9.5K

Netty: 大名鼎鼎, Javaer 人尽皆知的优秀框架,作为曾今的使用者对 netty 的作者也有无限的崇拜之情.对于 netty 的特点和优势这里不在多言,以免言多必失,大家可以问度娘.嘿嘿

Voovan:  Voovan奉行简约、优雅,其核心功能则是异步通信,同时还包含了动态编译、反射、ORM、Http 服务和客户端、websocket 服务和客户端、JSON 的序列化以及日志等一款综合性工具框架。

既然 Voovan 和 Netty 都是已 socket 通信作为核心的通信框架.这里我们对 Voovan 和 Netty 做一个性能对比的测试,同时我们对编码也进行对比,来看看两款框架有什么神奇的区别。

异同点:

     1.都支持 TLS、SSL 加密。

     2.都支持 TCP、UDP 通信模型。

     3.都使用异步事件驱动。

     4.都实现了 HTTP、WEBSOCKET 协议的支持。

     5.Voovan 比 Netty 多了一个消息分割类,具体看后面的介绍 。

     6.Voovan 框架额外实现了一个 Web 服务。    

     7.文档方面两个框架都有着较为丰富的文档,只是 Netty 文档多为英文,而 Voovan 由于是国人开发,多为中文文档.

     8.Netty 提供了较多的协议的默认实现.

     9.Voovan 提供了额外的更多的工具类的支持.

 

一、主机环境

     资源有限使用虚拟机进行性能测试

     OS              Ubuntu 16.04
     CPU            Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz
     CPUCount   4核
     内存            512M
     JDK            OpenJDK  1.8.0_111-8u111-b14-2 @ ubuntu0.16.04.2-b14
     命令            ab -c 100 -n 10000 http://10.0.0.102:2808?/

二、测试说明

     测试目的: 对比 Voovan 和 Netty 在并发场景下能够支撑的并发连接数.

     测试工具: Apache bench

     原料:Netty 4.1.8 、 Voovan1.0-RC-1

     为避免自己编码的压测工具带来各种风险,同时也为了使测试结果更加公平、真实、可信。测试方法如下:

          1. 使用 ab 进行压力测试 

         2. Voovan 和 Netty 两个框架分别模拟 Http 服务,在接受到 HTTP 请求后不进行解析,直接返回相同的 Http 响应后关闭连接。

         3. 每次响应都关闭连接,保证不会因 HTTP 的 KeepAlive 头带来的长连接影响测试结果的准确性。

三、测试代码

Voovan主类: AioPerformTest

public static void main(String[] args) throws IOException {
		AioServerSocket serverSocket = new AioServerSocket("0.0.0.0",28081,5000);
		serverSocket.filterChain().add(new StringFilter()); //配置过滤器
                serverSocket.handler(new PerformTestHandler());     //配置业务类
		serverSocket.messageSplitter(new PerformTestSpliter()); // 配置消息截断器
		serverSocket.start();  //开启监听
	}
        ........      

 

Voovan业务类 PerformTestHandler:

public class PerformTestHandler implements IoHandler {

	private String responseStr ;
	
	public PerformTestHandler(){
		responseStr  =  "HTTP/1.1 200 OK\r\n" +
				  "Date: Wed, 10 Jun 2009 11:22:58 GMT\r\n" +
				  "Server: Microsoft-IIS/6.0\r\n" +
				  "X-Powered-By: ASP.NET\r\n" +
				  "Content-Length: 2\r\n" +
				  "Content-Type: text/html\r\n" +
				  "Cache-control: private\r\n\r\n"+
				  "OK\r\n\r\n";
	}

	@Override
	public Object onReceive(IoSession session, Object obj) {
		return responseStr;
	}

	@Override
	public void onSent(IoSession session, Object obj) {
		session.close(); //关闭连接
	}

    ........
}

 

Netty主类:

        netty 的业务累使用了内联类的方式写入到主类中了,以下代码的主体来自 netty 官网

package org.voovan.test.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * 类文字命名
 *
 * @author helyho
 *         <p>
 *         Voovan Framework.
 *         WebSite: https://github.com/helyho/Voovan
 *         Licence: Apache v2 License
 */
public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new DiscardServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture f = b.bind(28080).sync();

            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static class DiscardServerHandler extends ChannelInboundHandlerAdapter {
        private String retVal;

        public DiscardServerHandler(){
            retVal = "HTTP/1.1 200 OK\r\n" +
                    "Server: Voovan-WebServer/V1.0-RC-1\r\n" +
                    "Connection: keep-alive\r\n" +
                    "Content-Length: 2\r\n" +
                    "Date: Thu, 05 Jan 2017 04:55:20 GMT\r\n" +
                    "Content-Type: text/html\r\n"+
                    "\r\n"+
                    "OK\r\n\r\n";
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            //byte[] buffer = new byte[((ByteBuf) msg).readableBytes()];
            //((ByteBuf) msg).readBytes(buffer);
            //System.out.println(new String(buffer));
            //((ByteBuf) msg).clear();
            ((ByteBuf) msg).release();

            ByteBuf bf = ctx.alloc().buffer(retVal.length());
            bf.writeBytes(retVal.getBytes());
            ctx.writeAndFlush(bf);
            ctx.close();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
            // Close the connection when an exception is raised.
            cause.printStackTrace();
            ctx.close();
        }
    }
}

以上完整的测试代码均在https://git.oschina.net/helyho/Voovan可以找到。

2017-07-01: 有网友质疑采用 ctx.alloc()方式没有从 Bytebuf 池中获取.经过调试截图如下:    

        

PooledByteBufAllocator 对象是用来分配bytebuf池中非堆内存的,所以通ctx.alloc().buffer(retVal.length());的方式实际上是从bytebuf池中获取的 bytebuf。

四、代码比对

     首先请以初学者的角度来看待以上两段代码:

     1.Voovan 仅仅5行代码就可以完成对 Socket 的初始化工作,每行代码都有他独立的作用,结构清晰易懂.Netty 则最少需要8行代码来实现而且包含一个内联类,对于初学者来说可能理解有些难度多数初学者就是复制粘贴直接使用。

     2.Voovan 框架通过 PerformTestSpliter 消息分割类将消息转换成你期望的类型,这样在 IoHandler的实现类中的OnRecive 事件中可以直接使用.Netty 则需要在接收事件中进行处理.其实提供消息分割器的目的是为了优雅的解决粘包的问题。

     3.Voovan 和 Netty 都提供过滤链来对消息进行处理,但 Voovan 的过滤链是在消息分割类之后是可以有针对性的过滤一个有效且完成的报文。

     4.Voovan 和 Netty 都采用了自管理线程池能根据业务情况自动扩充线程池的大小。

    基于以上的代码做了一个简单的对比分析,两个框架在目的和功能上具有相通性,采用了不同的方法进行实现。

   

五、测试步骤

    1.首先进行5次预热,以保证框架的线程池管理达到最优状态

    2.接着,连续运行10次并记录结果。

    3.首先测试 Voovan 框架,测试完成后,等待3分钟后进行 Netty 框架的测试

    由于机器是虚拟机所以各项ab的参数设置的并不高,但好在两个测试程序运行在同样的环境中,并使用同样的参数进行测试,横向对比的结果还是可靠的

 

六、数据说话

下面我们来看看测试数据:

 

框架 第一次 第二次 第三次 第四次 第五次 第六次 第七次 第八次 第九次 第十次 平均
Voovan 18734.1 18573.97 18990.18 19394.95 18946.86 16115.36 14878.41 18410.92 21638.65 19569.17 18525.257
Netty 19382.62 18808.45 19255.73 12422.08 20086.29 20176.91 19604.92 19338.77 14812.84 16474.74 18036.335

 

通过上表可以看出:

Voovan 框架在10次测试后平均并发数据为: 18525

Netty 框架在10次测试后平均并发数据为: 18036

 

两个框架在并发性能上差异为:489,平均导10次每次的差异约为49,鉴于我们进行测试的总请求量是10000,这个差异可能是机器的各种不可控因素导致的差异,个人认为是可以忽略的.并不影响两个框架的整体性能。

 

性能曲线分析:

    橙色的线条是 Netty 的数据

    蓝色的线条是 Voovan 的数据

 

 

 

可以看到 Voovan 的出现了一次较大的向下波动,Netty 出现了两次较大的向下波动.具体波动原因不得而知,推测可能是虚拟机较弱,因 GC 导致,由于只有10次测试的样本并所以不能通过较大的波动次数来评判框架的好坏。

总的来说两个框架在并发性能上应该是处于相同级别的,或者说都具有优秀的并发性能。

 

最后各位童鞋,你们怎么看呢?

有兴趣了解 Voovan 的朋友可以移步【Voovan】

展开阅读全文
打赏
4
46 收藏
分享
加载中

引用来自“beykery”的评论

引用来自“光的笔记”的评论

开源项目受到质疑是常有的事情的,作者继续努力👍

我不太肯定你说的是什么意思。你鼓励作者继续努力没错,谁都需要努力。我的意思是,你没必要这样说,我只是在进行纯粹的技术讨论,不需要你说什么受到质疑请继续努力,这都是没有意义没有力量的屁话。
你可以申辩说你们是在进行讨论,但在我看来,你们其实不是在做讨论,因为讨论是会有核心议题的,很显然你们没有,所以说是质疑。当然质疑本身这并没有错。现在回到源头,在开源中国对开源作者进行留言鼓励,算是基本的礼貌,我不太明白鼓励作者为啥也会让你如此激动到喷脏话?凡是不同意你观点的,你都要这样?
2018/07/09 14:54
回复
举报

引用来自“光的笔记”的评论

开源项目受到质疑是常有的事情的,作者继续努力👍

我不太肯定你说的是什么意思。你鼓励作者继续努力没错,谁都需要努力。我的意思是,你没必要这样说,我只是在进行纯粹的技术讨论,不需要你说什么受到质疑请继续努力,这都是没有意义没有力量的屁话。
2018/07/04 16:55
回复
举报
开源项目受到质疑是常有的事情的,作者继续努力👍
2017/12/15 08:55
回复
举报
该评论暂时无法显示,详情咨询 QQ 群:912889742
Voovan博主
我们完全不在一个频道,socket出入都是流操作关于流的特性这些基本的东西我就不解释了,如果你其他地方需要就需要自己想办法存起来,和这个非堆内存的缓冲有什么关系呢?,非堆内存的管理风险很高,没有任何框架会以牺牲稳定性为代价将非堆内存暴露给用户的,如果用户自己想用就需要用户自己管理
2017/07/17 23:26
回复
举报
该评论暂时无法显示,详情咨询 QQ 群:912889742
Voovan博主
该评论暂时无法显示,详情咨询 QQ 群:912889742
该评论暂时无法显示,详情咨询 QQ 群:912889742
Voovan博主
该评论暂时无法显示,详情咨询 QQ 群:912889742
Voovan博主

引用来自“jackygurui”的评论

为啥在你的框架里面的返回的值是以实例的字段responseStr来记录的,然而在netty的测试中是在请求代码里?这样不是每次请求里都要初始化一次?虽然java有string pool,但这样还是会对比较产生影响啊。

还有为什么要ctx.alloc()这种明显影响性能的用法而不是采用通常从bytebuf池里面获取?
关于第一个点确实是我的失误, 已修正, 经过重新测试确实如你所说在性能上并没有什么明显的变化. Netty 和 Voovan 的测试结果相互领先的情况均有发生.
关于第二点,你可以用 debug 查看一下, 通过 ctx.alloc() 方法获得的是一个 PooledByteBufAllocator 对象,而这个对象就是用来分配bytebuf池中非堆内存的,所以通过ctx.alloc().buffer(retVal.length());的方式实际上是从bytebuf池中获取的 bytebuf.我在 blog 中更新了,我 debug 时的截图.
2017/07/01 04:47
回复
举报
更多评论
打赏
16 评论
46 收藏
4
分享
返回顶部
顶部