Netty(二) 从线程模型的角度看 Netty 为什么是高性能的?

简介: 在之前的 SpringBoot 整合长连接心跳机制 一文中认识了 Netty。但其实只是能用,为什么要用 Netty?它有哪些优势?这些其实都不清楚。本文就来从历史源头说道说道。

传统 IO


在 Netty 以及 NIO 出现之前,我们写 IO 应用其实用的都是用 java.io.* 下所提供的包。


比如下面的伪代码:


ServeSocket serverSocket = new ServeSocket(8080);
Socket socket = serverSocket.accept() ;
BufferReader in = .... ;
String request ;
while((request = in.readLine()) != null){
  new Thread(new Task()).start()
}


大概是这样,其实主要想表达的是:这样一个线程只能处理一个连接


如果是 100 个客户端连接那就得开 100 个线程,1000 那就得 1000 个线程。


要知道线程资源非常宝贵,每次的创建都会带来消耗,而且每个线程还得为它分配对应的栈内存。


即便是我们给 JVM 足够的内存,大量线程所带来的上下文切换也是受不了的。


并且传统 IO 是阻塞模式,每一次的响应必须的是发起 IO 请求,处理请求完成再同时返回,直接的结果就是性能差,吞吐量低。


Reactor 模型


因此业界常用的高性能 IO 模型是 Reactor


它是一种异步、非阻塞的事件驱动模型。


通常也表现为以下三种方式:


单线程



从图中可以看出:


它是由一个线程来接收客户端的连接,并将该请求分发到对应的事件处理 handler 中,整个过程完全是异步非阻塞的;并且完全不存在共享资源的问题。所以理论上来说吞吐量也还不错。


但由于是一个线程,对多核 CPU 利用率不高,一旦有大量的客户端连接上来性能必然下降,甚至会有大量请求无法响应。 最坏的情况是一旦这个线程哪里没有处理好进入了死循环那整个服务都将不可用!


多线程



因此产生了多线程模型。


其实最大的改进就是将原有的事件处理改为了多线程。


可以基于 Java 自身的线程池实现,这样在大量请求的处理上性能提示是巨大的。


虽然如此,但理论上来说依然有一个地方是单点的;那就是处理客户端连接的线程。


因为大多数服务端应用或多或少在连接时都会处理一些业务,如鉴权之类的,当连接的客户端越来越多时这一个线程依然会存在性能问题。


于是又有了下面的线程模型。


主从多线程



该模型将客户端连接那一块的线程也改为多线程,称为主线程。


同时也是多个子线程来处理事件响应,这样无论是连接还是事件都是高性能的。


Netty 实现


以上谈了这么多其实 Netty 的线程模型与之的类似。


我们回到之前 SpringBoot 整合长连接心跳机制 中的服务端代码:


private EventLoopGroup boss = new NioEventLoopGroup();
    private EventLoopGroup work = new NioEventLoopGroup();
    /**
     * 启动 Netty
     *
     * @return
     * @throws InterruptedException
     */
    @PostConstruct
    public void start() throws InterruptedException {
        ServerBootstrap bootstrap = new ServerBootstrap()
                .group(boss, work)
                .channel(NioServerSocketChannel.class)
                .localAddress(new InetSocketAddress(nettyPort))
                //保持长连接
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new HeartbeatInitializer());
        ChannelFuture future = bootstrap.bind().sync();
        if (future.isSuccess()) {
            LOGGER.info("启动 Netty 成功");
        }
    }


其实这里的 boss 就相当于 Reactor 模型中处理客户端连接的线程池。


work 自然就是处理事件的线程池了。


那么如何来实现上文的三种模式呢?其实也很简单:


单线程模型:


private EventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
                .group(group)
                .childHandler(new HeartbeatInitializer());


多线程模型:


private EventLoopGroup boss = new NioEventLoopGroup(1);
private EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
                .group(boss,work)
                .childHandler(new HeartbeatInitializer());


主从多线程:


private EventLoopGroup boss = new NioEventLoopGroup();
private EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
                .group(boss,work)
                .childHandler(new HeartbeatInitializer());


相信大家一看也明白。


总结


其实看过了 Netty 的线程模型之后能否对我们平时做高性能应用带来点启发呢?


我认为是可以的:


  • 接口同步转异步处理。


  • 回调通知结果。


  • 多线程提高并发效率。


无非也就是这些,只是做了这些之后就会带来其他问题:


  • 异步之后事务如何保证?


  • 回调失败的情况?


  • 多线程所带来的上下文切换、共享资源的问题。


这就是一个博弈的过程,想要做到一个尽量高效的应用是需要不断磨合试错的。

上文相关的代码:


github.com/crossoverJi…


相关文章
|
5月前
|
编解码 分布式计算 网络协议
Netty高性能网络框架(一)
Netty高性能网络框架(一)
|
6月前
|
监控 Java 测试技术
Java并发编程最佳实践:设计高性能的多线程系统
Java并发编程最佳实践:设计高性能的多线程系统
84 1
|
6月前
|
编解码 网络协议 API
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
|
4月前
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
3月前
|
消息中间件 编解码 网络协议
Netty从入门到精通:高性能网络编程的进阶之路
【11月更文挑战第17天】Netty是一个基于Java NIO(Non-blocking I/O)的高性能、异步事件驱动的网络应用框架。使用Netty,开发者可以快速、高效地开发可扩展的网络服务器和客户端程序。本文将带您从Netty的背景、业务场景、功能点、解决问题的关键、底层原理实现,到编写一个详细的Java示例,全面了解Netty,帮助您从入门到精通。
339 0
|
4月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
42 1
|
5月前
|
消息中间件 存储 NoSQL
剖析 Redis List 消息队列的三种消费线程模型
Redis 列表(List)是一种简单的字符串列表,它的底层实现是一个双向链表。 生产环境,很多公司都将 Redis 列表应用于轻量级消息队列 。这篇文章,我们聊聊如何使用 List 命令实现消息队列的功能以及剖析消费者线程模型 。
131 20
剖析 Redis List 消息队列的三种消费线程模型
|
4月前
|
缓存 负载均衡 Java
c++写高性能的任务流线程池(万字详解!)
本文介绍了一种高性能的任务流线程池设计,涵盖多种优化机制。首先介绍了Work Steal机制,通过任务偷窃提高资源利用率。接着讨论了优先级任务,使不同优先级的任务得到合理调度。然后提出了缓存机制,通过环形缓存队列提升程序负载能力。Local Thread机制则通过预先创建线程减少创建和销毁线程的开销。Lock Free机制进一步减少了锁的竞争。容量动态调整机制根据任务负载动态调整线程数量。批量处理机制提高了任务处理效率。此外,还介绍了负载均衡、避免等待、预测优化、减少复制等策略。最后,任务组的设计便于管理和复用多任务。整体设计旨在提升线程池的性能和稳定性。
110 5
|
4月前
|
NoSQL Redis 数据库
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
本文解释了Redis为什么采用单线程模型,以及为什么Redis单线程模型的效率和速度依然可以非常高,主要原因包括Redis操作主要访问内存、核心操作简单、单线程避免了线程竞争开销,以及使用了IO多路复用机制epoll。
76 0
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
|
5月前
|
存储 消息中间件 NoSQL
Redis的单线程设计之谜:高性能与简洁并存
Redis的单线程设计之谜:高性能与简洁并存
63 1