Netty in Action:EventLoop和线程模型

本章包含

线程模型概览

Event Loop的概念和实现

Task调度

实现详解

简单来说,线程模型定义了操作系统,编程语言,框架或者应用程序线程管理的关键部分。线程是如何,并且何时被创建,显然对代码的执行有很大的影响。因此,开发者需要理解不同线程模型中存在的权衡利弊。无论他们是直接为自己选择模型,还是通过用一种语言或者框架隐性地来选择模型,这一点都是毋庸置疑的。

在这一章我们会详细探讨Netty的线程模型。Netty线程模型很强大,不过用起来很简单。Netty总是一如既往地简化你的应用代码,最大化应用的性能和可维护性。我们还会讨论到我们选择Netty目前这个线程模型的整个过程。

如果你对Java并发API(java.util.concurrent)有一个大致的了解,你会感觉本章的讨论很清晰明了。如果你对这些概念不熟悉,或者需要再回忆下,Brian Goetz和其他人合著的《Java并发编程实战》是个很棒的资源。

7.1 线程模型概览

在这一小节我们会大体上介绍下线程模型,然后讨论下Netty过去和现在采用的线程模型,并且回顾下两种模型各自的优势和限制。

我们在本章开头提到的,一个线程模型决定了代码将如何被执行。因为我们必须始终防范并发执行可能带来的副作用,所以理解线程模型的真正含义是很重要的(还有单线程模型)。忽略这些事,仅仅希望获得最好的结果跟赌博没什么两样——如你所愿的机会很渺茫。

因为多核或者多CPU的电脑很普遍,现代应用程序大部分都采用复杂的多线程技术来充分利用系统资源。但是相比之下,在Java早期,我们的多线程策略只不过是按需要创建和启动新的Thread,来执行并发的工作任务单元,这个粗糙的策略在高负载下表现得非常糟糕。然后Java5引入了Executor API,通过线程缓存和复用,线程池极大地提高了性能。

基本的线程池模式描述如下:

- 从池中空闲的线程中选出一个,分配一个提交的task(一个Runnable的实现)

- 当task完成,线程返回池中,等待复用(下一次task分配)

这个模式如图7.1所示。

图7.1 Executor执行逻辑

相比为每个task都创建和销毁一个线程,将线程放入池中、复用线程是一次性能的提升。但是这个模型还是无法消除上下文切换带来的开销,而这一点会随着线程数量的增加变得明显,在高负载下会变得严重。此外,在一个项目的运行过程中,因为某个应用的整体复杂度和并发需求,其他线程相关的问题也会出现。

总之,多线程是复杂的。在下面的小节里,我们会看到Netty是如何帮助你简化它的。

7.2 EventLoop接口

运行task来处理一个连接在生命周期中产生的event,是任何一个网络框架的基本功能。其相应的编程结构通常被称为一个event loop, Netty采纳了这个名字,设计了接口io.netty.channel.EventLoop。

一个event loop的基本构想如下代码所示,代码中的每个task都是一个Runnable实例(如图7.1)

代码清单7.1 在一个event loop中执行task

首先,io.netty.util.concurrent包是基于JDK包java.util.concurrent创建的,以此来提供thread executor。其次,io.netty.channel中的类扩展了这两个包的一些接口,来和Channel的event相连接。类层次结构如图7.2所示。

图7.2 EventLoop 类层次结构

This chapter requires login to view full content. You are viewing a preview.

Login to View Full Content

Course Curriculum

3

框架与 I/O:Spring、Netty 与 Web 容器

理解 Spring Boot 自动装配、AOP 与事务原理,掌握 Netty Reactor 模型及 Tomcat 连接处理机制,构建高内聚、易扩展的应用服务层。
4

高性能中间件:消息、缓存与存储

熟练运用 MySQL 索引/事务、Redis 缓存策略、Kafka/RocketMQ 消息可靠性,以及 ZooKeeper 分布式协调,搭建稳定、解耦的分布式数据底座。
6

云原生:容器化、可观测性与工程效能

通过 Docker/K8s 实现弹性部署,集成 Metrics/Logs/Traces 构建可观测体系,推动 DevOps 与自动化,让架构在云上持续交付与进化。