• Netty百万级高并发支持


    1. 百万级并发场景

    实际场景下,百万级并发请求是较多的,比如电商的促销、12306抢票、健康码查询等等,这些场景要求应用服务稳定,能最大化资源利用。所以实际应用程序设计时,会对QPS并发做预估处理,通过QPS选择合适的设计实现和对应的服务器资源数。一般会做如下要求:

    • 选择占用尽可能少的服务器资源,减少成本投入;
    • 服务器数量少,运维和维护难度降低,减少人力成本投入;
    • 优秀的应用设计会降低实现复杂度,提升可用性。        

    2. 为什么选择netty做高并发

    面临高并发场景诉求,解决办法有三个主题:

    • I/O传输模型:用什么样的通道将数据发送给对方,是BIO、NIO还是AIO,I/O传输模型在很大程度上决定了框架的性能。
    • 数据协议:采用什么样的通信协议,协议的选择不同,性能模型也就不同。
    • 线程模型:线程模型涉及读取数据包,读包之后的编解码,编解码后消息如何派发等,线程模型设计得不同,对性能也会产生非常大的影响。

    2.1 IO传输模型

    IO在计算机中指Input/Output,也就是输入和输出。计算机运行时数据是在内存中驻留,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

    LINUX中进程无法直接操作I/O设备,必须通过系统调用请求kernel来协助完成I/O动作。内核会为每个I/O设备维护一个缓冲区。IO输入时,应用进程请求内核,内核会先看缓冲区中有没有相应的缓存数据,有数据则直接复制到进程空间,没有的话再到设备中读取。通常用户进程中的一个完整IO分为两阶段:用户进程空间<-->内核空间、内核空间<- ->设备空间。

    由于CPU和内存的速度远远高于外设的速度,所以在IO编程中,就存在速度严重不匹配的问题,所以有了同步/异步,阻塞和非阻塞IO之分。

    IO模型分为:BIO、NIO、IO多路复用、信号驱动IO和AIO。

    • BIO:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程,操作成功则进程获取到数据。
    • NIO:非阻塞IO模型在内核数据没准备好,需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞;
    • IO多路复用:多个的进程的IO可以注册到一个复用器(selector)上,然后用一个进程调用该select,,select会监听所有注册进来的IO。
    • 信号驱动IO:当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。
    • AIO:当进程发起一个IO操作,进程返回(不阻塞),但也不能返回结果。内核把整个IO处理完后,会通知进程结果,如果IO操作成功则进程直接获取到数据。

    异步IO是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程都不用等待,所以只有AIon才是真正的一步IO。

    2.2 线程模型

    先了解一下Reactor模式,

    在Reactor模式中,有5种角色:

    • Initiation Dispatcher: 初始分发器,一旦事件被触发后,Initiation Dispatcher首先会分离出每一个事件,然后调用事件处理器,最后调用相关的回调方法来处理这些事件。
    • Synchronous Event Demultiplexer: 同步事件分发器,调用方在调用它的时候会被阻塞,一直阻塞到同步事件分离器上有事件产生为止。对于Linux来说,同步事件分离器指的就是常用的I/O多路复用机制,比如说select、poll、epoll等。
    • Event Handler:事件处理器,本身由多个回调方法构成,这些回调方法构成了与应用相关的对于某个事件的反馈机制。
    • Concrete Event Handler:具体事件处理器,是事件处理器的实现。它本身实现了事件处理器所提供的各种回调方法,从而实现了特定于业务的逻辑。
    • Handle:句柄,本质上表示一种资源(比如说文件描述符,或是针对网络编程中的socket描述符),是由操作系统提供的;该资源用于表示一个个的事件,事件既可以来自于外部,也可以来自于内部;外部事件比如说客户端的连接请求,客户端发送过来的数据等;内部事件比如说操作系统产生的定时事件等。它本质上就是一个文件描述符,Handle是事件产生的发源地。

    Reactor 单线程模型:

    优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程中完成
    缺点:性能问题,单线程无法完全发挥多核 CPU 的性能。Handler 在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈 。

    多线程Reactor

    优点:可以充分的利用多核cpu 的处理能力
    缺点:多线程数据共享和访问比较复杂,但是reactor处理所有的事件的监听和响应,在单线程运行, 在高并发场景容易出现性能瓶颈。

    主从Reactor

    优点:父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理。
    缺点:编程复杂度较高

    2.3 零拷贝

    • Netty接收和发送ByteBuffer采用DirectBuffer,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。
    • Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便地对组合Buffer进行操作,避免了传统的通过内存拷贝的方式将几个小Buffer合并成一个大Buffer的烦琐操作。
    • Netty中文件传输采用了transferTo()方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write()方式导致的内存拷贝问题。

    2.4 内存池

            对于缓冲区来说,尤其是对于堆外直接内存的分配和回收,是一种耗时的操作。为了尽量重复利用缓冲区内存,Netty设计了一套基于内存池的缓冲区重用机制。

    2.5 无锁化的串行设计理念

            大多数应用场景下,并行多线程处理可以提升系统的并发性能。但如果共享资源竞争激烈,就会造成严重的锁竞争,导致系统性能的下降。为了尽可能避免锁竞争带来的性能损耗,可以通过串行化设计来避免多线程竞争和同步锁,即消息的处理尽可能在同一个线程内完成,不进行线程切换。

    2.6 高效的并发编程

    Netty的高效并发编程主要体现在如下几点:

    1. volatile关键字的大量且正确的使用。

    2. CAS和原子类的广泛使用。

    3. 线程安全容器的使用。

    4. 灵活的TCP参数配置能力。

    2.7 对高性能的序列化框架的支持

    影响序列化性能的关键因素有:序列化后的码流大小(网络带宽的占用);序列化/反序列化的性能(CPU资源占用)和是否支持跨语言。Netty默认提供了对Google Protobuf的支持,用户也可以通过扩展Netty的编解码接口接入其他高性能的序列化框架进行编解码,例如Thrift的压缩二进制编解码框架。

    3. netty的使用姿势

    1. EventLoopGroup bossGroup = new NioEventLoopGroup();
    2. EventLoopGroup workerGroup = new NioEventLoopGroup();
    3. try {
    4. ServerBootstrap bootstrap = new ServerBootstrap();
    5. bootstrap.option(ChannelOption.SO_REUSEADDR, true).option(ChannelOption.SO_BACKLOG, 1024)
    6. .option(ChannelOption.SO_RCVBUF, 1024 * 128)
    7. .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
    8. bootstrap.childOption(ChannelOption.TCP_NODELAY, true)
    9. .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
    10. .childOption(ChannelOption.SO_KEEPALIVE, true);
    11. bootstrap.group(bossGroup,workerGroup)
    12. .channel(NioServerSocketChannel.class)
    13. .localAddress(new InetSocketAddress(port))
    14. .childHandler(new ChannelInitializer() {
    15. EventExecutorGroup logicGroup = new DefaultEventExecutorGroup(16);
    16. @Override
    17. protected void initChannel(SocketChannel ch) throws Exception {
    18. ch.pipeline().addLast(logicGroup,new EchoServerWithExecutorHandler());
    19. }
    20. });
    21. ChannelFuture f = bootstrap.bind().sync();
    22. System.out.println(App.class.getName() + " started and listen on " + f.channel().localAddress());
    23. f.channel().closeFuture().sync();
    24. } finally {
    25. bossGroup.shutdownGracefully().sync();
    26. workerGroup.shutdownGracefully().sync();
    27. }

    参考:Netty(二) 从线程模型的角度看 Netty 为什么是高性能的?-阿里云开发者社区

    Netty 为什么有如此高的性能? - 墨天轮

  • 相关阅读:
    用于光栅仿真的非偏振光–实例讨论
    C++的算法库
    VMware 三种网络连接模式
    云原生爱好者周刊:使用 klock 给你的 K8s 资源上一把锁 | 2022-08-29
    如何安装Vue
    放弃微服务,构建单体应用
    R语言在生态环境领域中的应用
    Ubuntu系统操作指南——命令行挂载U盘
    文件预览(img,doc,pdf,xls)
    数字孪生智慧工厂3D无代码编辑工具提供强大、简单功能
  • 原文地址:https://blog.csdn.net/chenwiehuang/article/details/126802503