【Netty中间件子系列四】Netty实现Web服务器

本来打算用Netty来实现一个Severlet服务器,发现spring已经做了相应的支持,那么我们来看看究竟他们的性能有什么差异,如果我们要用netty实现一个severlet容器应该这么做

 【Netty中间件子系列四】Netty实现Web服务器_第1张图片

  • 测试机器:Linux CentOS6.5 4核16G
  • SpringBoot版本:2.2.2.RELEASE
  • JDK版本:jdk1.8.0_151

ab压测

我们先对上面说的三个接口进行压测,为避免网络环境影响,我们直接在服务器上使用ab进行压力测试。

压测分三组,每组压测这三个接口,每个接口发起10w请求,每组用户数分别为200、500、1000,从而查看不同用户数请求下的响应结果。

实现:

【Netty中间件子系列四】Netty实现Web服务器_第2张图片

再定义一个传统的service,为模拟真实环境请求,service下的方法请求耗时100ms:

【Netty中间件子系列四】Netty实现Web服务器_第3张图片
 

模拟耗时100ms

最后我们写三个接口,每个接口采用不同的方式:

  1. 使用自定义调度器的方式
  2. 使用缓存的弹性调度器
  3. 传统的SpringMVC方式

代码如下图所示:

 【Netty中间件子系列四】Netty实现Web服务器_第4张图片

 第一组

压力测试结果:

【Netty中间件子系列四】Netty实现Web服务器_第5张图片

 10w请求数 200用户

可以看见传统的SpringMVC方式已经有阻塞了,最长的一次请求1107ms,但是整体性能基本一致,因为200个线程刚好是tomcat的线程池最大默认数。

第二组

压测结果:

【Netty中间件子系列四】Netty实现Web服务器_第6张图片

10w请求 500用户

500用户请求时候可以看到hello3接口的响应时间已经是hello1和hello2两个接口响应时间的2倍以上了,但是基于project reactor响应编程开发方式的响应时间依旧和200用户一致。

我们继续将用户数加到1000。

第三组

压测结果:

【Netty中间件子系列四】Netty实现Web服务器_第7张图片

10w请求 1000用户

我们发现基于project reactor开发的接口响应时间依旧坚挺,传统SpringMVC方式开发的接口90%响应时间已经高达500ms了。

 【Netty中间件子系列四】Netty实现Web服务器_第8张图片

tomcat下压测10w请求1000用户

是不是发现netty的性能比tomcat更加优越?99%的请求在149ms即可完成。如果大家自己实操的话也会发现吞吐量也会较tomcat有大幅度的提升。

基于NettyServer的SpringMVC的实现

构建一个maven工程,并在pom中加入以下依赖.这里使用了netty和spring的容器作为基础.

       
          
              io.netty
              netty-all
              4.1.36.Final
          

          
          
              org.springframework
              spring-core
              5.1.9.RELEASE
          

          
          
              org.springframework
              spring-beans
              5.1.9.RELEASE
          

          
          
              org.springframework
              spring-context
              5.1.9.RELEASE
          

第二步: 构建一个Server服务端类,Netty提供了基于服务端的构建,在启动类里面

     public static void main(String[] args)throws Exception {
        //启动Spring的容器
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        //配置
        annotationConfigApplicationContext.scan("com.netty.mvc");
        annotationConfigApplicationContext.refresh();

        final DispatcherHandler dispatcherHandler = new DispatcherHandler(annotationConfigApplicationContext);

        //创建Even Loop Group
        //配置服务器的NIO线程组
        //两个Reactor 一个用于服务器接收客户端的连接  一个用于经行SocketChannel的网络读写
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            //创建ServerBootStrap
            ServerBootstrap b = new ServerBootstrap();
            //指定所使用的NIO传输Channle
            b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
                    .localAddress(8080)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) {
                            //如果ServerHandler被注为@Shareable的时候,则可以总是使用同样的实例
                            socketChannel.pipeline()
                                    .addLast(new HttpRequestDecoder())
                                    .addLast(new HttpResponseEncoder())
                                    .addLast(new WebServerHandler(dispatcherHandler));
                        }
                    });
            //异步的绑定服务器,调用sync===方法阻塞,直到绑定完成
            ChannelFuture f = b.bind().sync();
            System.out.println("netty服务端启动成功");
            //获取Channel的CloseFuture,并阻塞当前线程直到它完成
            f.channel().closeFuture().sync();
        }finally {
            //关闭EvenLoopGroup,释放所有资源
            bossGroup.shutdownGracefully().sync();
            workerGroup.shutdownGracefully().sync();
        }
    }

第二步: 构建一个WebServerHandler类来处理i来处理请求.

@ChannelHandler.Sharable
public class WebServerHandler extends ChannelInboundHandlerAdapter{

    private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN;

   //请求分发器
    DispatcherHandler dispatcherHandler;

    public WebServerHandler(DispatcherHandler dispatcherHandler){
        this.dispatcherHandler = dispatcherHandler;
    }
    /**
     * 每个信息入站都会调用
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        Object result = "";
        if(msg instanceof HttpRequest){
            result  = dispatcherHandler.handle((HttpRequest)msg);
        }
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK,
                Unpooled.wrappedBuffer(result.toString().getBytes())); // 2

        HttpHeaders heads = response.headers();
        heads.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8");
        heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); // 3
        heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        //将接受到的消息写给发送者
        ctx.write(response);
    }


    /**
     * 通知处理器最后的channelread是当前批处理中的最后一条信息调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //将未决消息冲刷到远程节点,并关闭该Channel
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();  //打印异常栈追踪
        ctx.close(); //关闭该channel
    }
}

第四步: 就是构建一个DispatcherHandler作为请求处理份发器

@Configuration
public class DispatcherHandler implements WebHandler, ApplicationContextAware {

    //url-->hanlder的映射
    private List handlerMappings;
    //处理器的适配器
    private List handlerAdapters;

    /**
     * Create a new {@code DispatcherHandler} for the given {@link ApplicationContext}.
     * @param applicationContext the application context to find the handler beans in
     */
    public DispatcherHandler(ApplicationContext applicationContext) {
        initStrategies(applicationContext);
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        initStrategies(applicationContext);
    }

    protected void initStrategies(ApplicationContext context) {

        Map mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                context, HandlerMapping.class, true, false);

        ArrayList mappings = new ArrayList<>(mappingBeans.values());
        AnnotationAwareOrderComparator.sort(mappings);
        this.handlerMappings = Collections.unmodifiableList(mappings);

        Map adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                context, HandlerAdapter.class, true, false);

        this.handlerAdapters = new ArrayList<>(adapterBeans.values());
        AnnotationAwareOrderComparator.sort(this.handlerAdapters);

    }

    @Override
    public Object handle(HttpRequest httpRequest) {
      //请求处理器
        for(HandlerMapping handlerMapping : handlerMappings){
           Object handler = handlerMapping.getWebHandler(httpRequest);
           if(handler != null){
               for(HandlerAdapter adapter: handlerAdapters){
                    if(adapter.support(handler)){
                      return  adapter.handle(httpRequest,handler);
                    }
               }
           }
        }
        return null;
    }

}

第五步: 构建SprintMVC中最核心的HandlerMapping中映射处理器的注册,这里是借助了Spring的ComponentScan组件实现对于自定义@Controller的注解的扫描.

@Configuration
public class DefaultWebHandler implements WebHandler {


    public DefaultWebHandler(ApplicationContext applicationContext) {
        registerHandler(applicationContext);
    }

    /**
     * url -> Method对应
     */
    private Map handlerMap = new LinkedHashMap<>();

    /**
     * method—>controller的对应
     */
    private Map controllerMap = new HashMap<>();

    private void registerHandler(ApplicationContext context) {

        Map annotationControllerClasses = context.getBeansWithAnnotation(Controller.class);

        Set> handlerTypes = new LinkedHashSet<>();
        Class specificHandlerType = null;

        for (Object targetType : annotationControllerClasses.values()) {
            if (!Proxy.isProxyClass(targetType.getClass())) {
                specificHandlerType = ClassUtils.getUserClass(targetType);
                handlerTypes.add(specificHandlerType);
            }
            final Class targetClass = (specificHandlerType != null ? specificHandlerType : targetType.getClass());

            ReflectionUtils.doWithMethods(specificHandlerType, method -> {
                String url = "";
                if (targetClass.isAnnotationPresent(RequestMapping.class)) {
                    url = targetClass.getAnnotation(RequestMapping.class).value();
                }
                Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                if (specificMethod.isAnnotationPresent(RequestMapping.class)) {
                    url += specificMethod.getAnnotation(RequestMapping.class).value();
                }
                handlerMap.put(url, specificMethod);
                controllerMap.put(specificMethod, targetType);
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }
    }

    @Override
    public Object handle(HttpRequest httpRequest) {
        Method method = handlerMap.get(httpRequest.uri());

        if(method != null){
            try {
                return method.invoke(controllerMap.get(method));
            }catch (IllegalAccessException e){
                e.printStackTrace();
            }catch (InvocationTargetException e){
                e.printStackTrace();
            }
        }
        return null;
    }
}

第六步构建测试用例

@Controller
@RequestMapping(value = "/test")
@Configuration
public class TestController {

    @RequestMapping(value = "/get",method = RequestMethod.GET)
    public String test(){
        return "Hi Netty SpringMVC";
    }

}

vert.x响应式编程 

  • vert.x是Eclipse软件基金会顶级java开源项目之一,它基于netty的、运行在jvm之上的、支持多种编程语言的高性能异步、非阻塞、响应式全栈java web框架。它在techempower.com网站多项性能测试中占据java语言榜首。官网地址如下:https://vertx.io/
  • ert.x采用单一组件结构设计,即Verticle,所有业务功能都使用Verticle这种单一的组件编程完成,克服以往应用框架和平台包含众多类型的组件模式,使得开发人员能极快适应Vert.x编程,加快项目的开发速度。
  • Vert.x中所有的Verticle组件都是完全解耦合的,任何组件之间不能直接调用,只能通过在Vert.x的事件总线上发送事件来完成,彻底解决了传统应用系统中管理组件间相互依赖的复杂性,最终使得Vert. x应用编程极其简单高效。
  • Vert.x使用单线程事件驱动的异步工作模式,编写Vert.x组件时,不需要考虑复杂的多线程编程难题,并不需要关注线程之间的调用、同步、加锁等繁琐处理编程,简化了编程代码,提高了编程效率。
  • Vert. x通过提供一整套的异步编程API实现异步编程模型,在Vert. x中所有的请求处理都是通过注册事件监听处理器机制完成的。编程TCP处理服务器Verticle,通过注册TCPSocket的数据到达事件监听器,实现数据到达后的回调处理,而不是采用一直等待数据读取的阻塞模式,实现的是非阻塞的异步工作模式。
  • Vert.x的核心运行机制是事件循环,当Vert.x实例启动后,Vert.x框架在每个CPU的内核创建一个事件循环线程。此事件循环线程永不结束,它不断监听出现的各种事件,如事件总线的事件到达WebSocket上的数据接收,HTTP上的请求到达HTTP响应结束,定时器触发等等,并把事件分发到注册了监听此事件的Verticle,再继续监听其他的事件,如此反复直到Vert.x实例停止。

、vert.x 与 spring的对比

  • spring是单体架构设计,
  • vert.x面向分布式设计,性能高,在vert.x的概念中,没有MVC,没有AOP,没有ORM。二者的生态框架对比图如下
vert.x spring
Vert.x Core Spring Framework
Vert.x Web SpringBoot
Vert.x Data Access Spring Data Jpa
Vert.x Reactive Project Reactor
Vert.x Microservices Spring Cloud
Vert.x Authentication and Authorisation Spring Security
Vert.x MQTT
Vert.x Messaging Spring AMQP
Vert.x Devops Spring Devops
  • vert.x 与 Spring 支持的编程语言对比
vert.x spring
java,Kotlin,JavaScript, Groovy,Ruby,Scala java,Kotlin,Groovy

参考:

1、基于NettyServer的SpringMVC的实现 - 掘金

 2、netty web 容器_Tomcat和Netty的战场:SpringMVC&WebFlux性能大比拼_文小宁的博客-CSDN博客

3、jianshu.com/p/295e3122b466

你可能感兴趣的:(日常编程,记录,netty)