假设你正在为一个重要的大型公司开发一款全新的任务关键型的应用程序。在第一次会议上,你得知该系统必须要能够扩展到支撑150 000名并发用户,并且不能有任何的性能损失,这时所有的目光都投向了你。你会怎么说呢? 如果你可以自信地说:“当然,没问题。”那么大家都会向你脱帽致敬。但是,我们大多数人可能会采取一个更加谨慎的立场,例如:“听上去是可行的。”然后,一回到计算机旁,我们便开始搜索“high performance Java networking”(高性能Java网络编程)。 如果你现在搜索它,在第一页结果中,你将会看到下面的内容: Netty: Home
如果你和大多数人一样,通过这样的方式发现了Netty,那么你的下一步多半是:浏览该网站,下载源代码,仔细阅读Javadoc和一些相关的博客,然后写点儿代码试试。如果你已经有了扎实的网络编程经验,那么可能进展还不错,不然则可能是一头雾水。 这是为什么呢?因为像我们例子中那样的高性能系统不仅要求超一流的编程技巧,还需要几个复杂领域(网络编程、多线程处理和并发)的专业知识。Netty优雅地处理了这些领域的知识,使得即使是网络编程新手也能使用。但到目前为止,由于还缺乏一本全面的指南,使得对它的学习过程比实际需要的艰涩得多——因此便有了这本书。 我们编写这本书的主要目的是:使得Netty能够尽可能多地被更加广泛的开发者采用。这也包括那些拥有创新的内容或者服务,却没有时间或者兴趣成为网络编程专家的人。如果这适用于你,我们相信你将会非常惊讶自己这么快便可以开始创建你的第一款基于Netty的应用程序了。当然在另一个层面上讲,我们也需要支持那些正在寻找工具来创建他们自己的网络协议的高级从业人员。 Netty确实提供了极为丰富的网络编程工具集,我们将花大部分的时间来探究它的能力。但是,Netty终究是一个框架,它的架构方法和设计原则是:每个小点都和它的技术性内容一样重要,穷其精妙。因此,我们也将探讨很多其他方面的内容,例如:
在这第1章中,我们将从一些与高性能网络编程相关的背景知识开始铺陈,特别是它在Java开发工具包(JDK)中的实现。有了这些背景知识后,我们将介绍Netty,它的核心概念以及构建块。在本章结束之后,你就能够编写你的第一款基于Netty的客户端和服务器应用程序了。 1.1 Java网络编程早期的网络编程开发人员,需要花费大量的时间去学习复杂的C语言套接字库,去处理它们在不同的操作系统上出现的古怪问题。虽然最早的Java(1995—2002)引入了足够多的面向对象façade(门面)来隐藏一些棘手的细节问题,但是创建一个复杂的客户端/服务器协议仍然需要大量的样板代码(以及相当多的底层研究才能使它整个流畅地运行起来)。 那些最早期的Java API( 代码清单1-1 阻塞I/O示例
44代码清单1-1实现了
这段代码片段将只能同时处理一个连接,要管理多个并发客户端,需要为每个新的客户端 图1-1 使用阻塞I/O处理多个连接 让我们考虑一下这种方案的影响。第一,在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费。第二,需要为每个线程的调用栈都分配内存,其默认值大小区间为64 KB到1 MB,具体取决于操作系统。第三,即使Java虚拟机(JVM)在物理上可以支持非常大数量的线程,但是远在到达该极限之前,上下文切换所带来的开销就会带来麻烦,例如,在达到10 000个连接的时候。 虽然这种并发方案对于支撑中小数量的客户端来说还算可以接受,但是为了支撑100 000或者更多的并发连接所需要的资源使得它很不理想。幸运的是,还有一种方案。 1.1.1 Java NIO除了代码清单1-1中代码底层的阻塞系统调用之外,本地套接字库很早就提供了非阻塞调用,其为网络资源的利用率提供了相当多的控制:
Java对于非阻塞I/O的支持是在2002年引入的,位于JDK 1.4的 新的还是非阻塞的
1.1.2 选择器图1-2展示了一个非阻塞设计,其实际上消除了上一节中所描述的那些弊端。 图1-2 使用
总体来看,与阻塞I/O模型相比,这种模型提供了更好的资源管理:
尽管已经有许多直接使用Java NIO API的应用程序被构建了,但是要做到如此正确和安全并不容易。特别是,在高负载下可靠和高效地处理和调度I/O操作是一项繁琐而且容易出错的任务,最好留给高性能的网络编程专家——Netty。 1.2 Netty简介不久以前,我们在本章一开始所呈现的场景——支持成千上万的并发客户端——还被认定为是不可能的。然而今天,作为系统用户,我们将这种能力视为理所当然;同时作为开发人员,我们期望将水平线提得更高[3]。因为我们知道,总会有更高的吞吐量和可扩展性的要求——在更低的成本的基础上进行交付。 不要低估了这最后一点的重要性。我们已经从漫长的痛苦经历中学到:直接使用底层的API暴露了复杂性,并且引入了对往往供不应求的技能的关键性依赖[4]。这也就是,面向对象的基本概念:用较简单的抽象隐藏底层实现的复杂性。 这一原则也催生了大量框架的开发,它们为常见的编程任务封装了解决方案,其中的许多都和分布式系统的开发密切相关。我们可以确定地说:所有专业的Java开发人员都至少对它们熟知一二。[5]对于我们许多人来说,它们已经变得不可或缺,因为它们既能满足我们的技术需求,又能满足我们的时间表。 在网络编程领域,Netty是Java的卓越框架。[6]它驾驭了Java高级API的能力,并将其隐藏在一个易于使用的API之后。Netty使你可以专注于自己真正感兴趣的——你的应用程序的独一无二的价值。 在我们开始首次深入地了解Netty之前,请仔细审视表1-1中所总结的关键特性。有些是技术性的,而其他的更多的则是关于架构或设计哲学的。在本书的学习过程中,我们将不止一次地重新审视它们。 表1-1 Netty的特性总结
1.2.1 谁在使用NettyNetty拥有一个充满活力并且不断壮大的用户社区,其中不乏大型公司,如Apple、Twitter、Facebook、Google、Square和Instagram,还有流行的开源项目,如Infinispan、HornetQ、Vert.x、Apache Cassandra和Elasticsearch[8],它们所有的核心代码都利用了Netty强大的网络抽象[9]。在初创企业中,Firebase和Urban Airship也在使用Netty,前者用来做HTTP长连接,而后者用来支持各种各样的推送通知。 每当你使用Twitter,你便是在使用Finagle[10],它们基于Netty的系统间通信框架。Facebook在Nifty中使用了Netty,它们的Apache Thrift服务。可伸缩性和性能对这两家公司来说至关重要,他们也经常为Netty贡献代码[11]。 反过来,Netty也已从这些项目中受益,通过实现FTP、SMTP、HTTP和WebSocket以及其他的基于二进制和基于文本的协议,Netty扩展了它的应用范围及灵活性。 1.2.2 异步和事件驱动因为我们要大量地使用“异步”这个词,所以现在是一个澄清上下文的好时机。异步(也就是非同步)事件肯定大家都熟悉。考虑一下电子邮件:你可能会也可能不会收到你已经发出去的电子邮件对应的回复,或者你也可能会在正在发送一封电子邮件的时候收到一个意外的消息。异步事件也可以具有某种有序的关系。通常,你只有在已经问了一个问题之后才会得到一个和它对应的答案,而在你等待它的同时你也可以做点别的事情。 在日常的生活中,异步自然而然地就发生了,所以你可能没有对它考虑过多少。但是让一个计算机程序以相同的方式工作就会产生一些非常特殊的问题。本质上,一个既是异步的又是事件驱动的系统会表现出一种特殊的、对我们来说极具价值的行为:它可以以任意的顺序响应在任意的时间点产生的事件。 这种能力对于实现最高级别的可伸缩性至关重要,定义为:“一种系统、网络或者进程在需要处理的工作不断增长时,可以通过某种可行的方式或者扩大它的处理能力来适应这种增长的能力。”[12] 异步和可伸缩性之间的联系又是什么呢?
将这些元素结合在一起,与使用阻塞I/O来处理大量事件相比,使用非阻塞I/O来处理更快速、更经济。从网络编程的角度来看,这是构建我们理想系统的关键,而且你会看到,这也是Netty的设计底蕴的关键。 在1.3节中,我们将首先看一看Netty的核心组件。现在,只需要将它们看作是域对象,而不是具体的Java类。随着时间的推移,我们将看到它们是如何协作,来为在网络上发生的事件提供通知,并使得它们可以被处理的。 1.3 Netty的核心组件在本节中我将要讨论Netty的主要构件块:
这些构建块代表了不同类型的构造:资源、逻辑以及通知。你的应用程序将使用它们来访问网络以及流经网络的数据。 对于每个组件来说,我们都将提供一个基本的定义,并且在适当的情况下,还会提供一个简单的示例代码来说明它的用法。 1.3.1 ChannelChannel是Java NIO的一个基本构造。 它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作[13]。 目前,可以把 1.3.2 回调一个回调其实就是一个方法,一个指向已经被提供给另外一个方法的方法的引用。这使得后者[14]可以在适当的时候调用前者。回调在广泛的编程场景中都有应用,而且也是在操作完成后通知相关方最常见的方式之一。 Netty在内部使用了回调来处理事件;当一个回调被触发时,相关的事件可以被一个 代码清单1-2 被回调触发的
1.3.3 Future
JDK预置了
每个Netty的出站I/O操作都将返回一个 代码清单1-3展示了一个 代码清单1-3 异步地建立连接
代码清单1-4显示了如何利用 代码清单1-4 回调实战
需要注意的是,对错误的处理完全取决于你、目标,当然也包括目前任何对于特定类型的错误加以的限制。例如,如果连接失败,你可以尝试重新连接或者建立一个到另一个远程节点的连接。 如果你把 1.3.4 事件和ChannelHandlerNetty使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。这些动作可能是:
Netty是一个网络编程框架,所以事件是按照它们与入站或出站数据流的相关性进行分类的。可能由入站数据或者相关的状态更改而触发的事件包括:
出站事件是未来将会触发的某个动作的操作结果,这些动作包括:
每个事件都可以被分发给 图1-3 流经ChannelHandler链的入站事件和出站事件 Netty的 Netty提供了大量预定义的可以开箱即用的 1.3.5 把它们放在一起在本章中,我们介绍了Netty实现高性能网络编程的方式,以及它的实现中的一些主要的组件。让我们大体回顾一下我们讨论过的内容吧。 1.Future、回调和ChannelHandlerNetty的异步编程模型是建立在 拦截操作以及高速地转换入站数据和出站数据,都只需要你提供回调或者利用操作所返回的 2.选择器、事件和EventLoopNetty通过触发事件将
1.4 小结在这一章中,我们介绍了Netty框架的背景知识,包括Java网络编程API的演变过程,阻塞和非阻塞网络操作之间的区别,以及异步I/O在高容量、高性能的网络编程中的优势。 然后,我们概述了Netty的特性、设计和优点,其中包括Netty异步模型的底层机制,包括回调、 在本书接下来的部分,我们将更加深入地探讨如何利用这些丰富的工具集来满足自己的应用程序的特定需求。 在下一章中,我们将要深入地探讨Netty的API以及编程模型的基础知识,而你则将编写你的第一款客户端和服务器应用程序。 [1] W. Richard Stevens的Advanced Programming in the UNIX Environment (Addison-Wesley, 1992)第364页“4.3BSD returned EWOULDBLOCK if an operation on a non-blocking descriptor could not complete without blocking”。 [2] 也称为I/O多路复用,该接口从最初的 [3] 这里指支撑更多的并发的客户端。——译者注 [4] 这里指熟悉这些底层的API的人员少。——译者注 [5] Spring框架大概是最出名的,并且实际上是一个完整的应用程序框架的生态系统,处理了对象的创建、批量处理、数据库编程等。 [6] Netty在2011年荣获了Duke’s Choice Award的殊荣,参见www.java.net/dukeschoice/2011。 [7] 最新的版本编译需要JDK 1.8+,参见https://github.com/netty/netty/pull/6392。——译者注 [8] 还包括炙手可热的大数据处理引擎Spark。——译者注 [9] 完整的已知采用者列表参见http:///wiki/adopters.html。 [10] 关于Finagle的更多信息参见https://twitter./finagle/。 [11] 第15章和第16章的案例研究描述了这里提到的公司中的一些是如何使用Netty来解决现实世界的问题的。 [12] André B. Bondi的Proceedings of the second international workshop on Software and performance— WOSP’00 (2000)第195页,“Characteristics of scalability and their impact on performance”。 [13] Java平台,标准版第8版API规范,java.nio.channels,Channel:http://docs.oracle.com/javase/8/docs/ api/java/nio/channels/package-summary.html。 [14] 指接受回调的方法。——译者注 [15] 如果在 |
|