IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 【Netty中间件子系列四】Netty实现Web服务器 -> 正文阅读

[系统运维]【Netty中间件子系列四】Netty实现Web服务器

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

?

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

ab压测

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

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

实现:

b5d774beddc3d4a24dfea95c7bf2c7f7.png

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

c29eb7b92574769e1fa385c3e842ddce.png
?

模拟耗时100ms

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

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

代码如下图所示:

?5f425995d274686f281112b9886da2f9.png

?第一组

压力测试结果:

70cc47d6d981a67ab394480ecef4e96f.png

?10w请求数 200用户

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

第二组

压测结果:

e591803b19c9344228c60e2d2b797372.png

10w请求 500用户

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

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

第三组

压测结果:

283f6df000b62e17c3df301047cc9d06.png

10w请求 1000用户

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

?0c4a2e6bd3ad49cdbc2c360f2fe25c2c.png

tomcat下压测10w请求1000用户

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

基于NettyServer的SpringMVC的实现

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

       <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
          <dependency>
              <groupId>io.netty</groupId>
              <artifactId>netty-all</artifactId>
              <version>4.1.36.Final</version>
          </dependency>

          <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-core</artifactId>
              <version>5.1.9.RELEASE</version>
          </dependency>

          <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-beans</artifactId>
              <version>5.1.9.RELEASE</version>
          </dependency>

          <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-context</artifactId>
              <version>5.1.9.RELEASE</version>
          </dependency>

第二步: 构建一个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<SocketChannel>() {
                        @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<HandlerMapping> handlerMappings;
    //处理器的适配器
    private List<HandlerAdapter> 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<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                context, HandlerMapping.class, true, false);

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

        Map<String, HandlerAdapter> 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<String, Method> handlerMap = new LinkedHashMap<>();

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

    private void registerHandler(ApplicationContext context) {

        Map<String, Object> annotationControllerClasses = context.getBeansWithAnnotation(Controller.class);

        Set<Class<?>> 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.xspring
Vert.x CoreSpring Framework
Vert.x WebSpringBoot
Vert.x Data AccessSpring Data Jpa
Vert.x ReactiveProject Reactor
Vert.x MicroservicesSpring Cloud
Vert.x Authentication and AuthorisationSpring Security
Vert.x MQTT
Vert.x MessagingSpring AMQP
Vert.x DevopsSpring Devops
  • vert.x 与 Spring 支持的编程语言对比
vert.xspring
java,Kotlin,JavaScript, Groovy,Ruby,Scalajava,Kotlin,Groovy

参考:

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

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

3、jianshu.com/p/295e3122b466

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 18:25:00  更:2022-04-18 18:26:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 22:31:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码