Web 2.0 和移动客户端的巨大发展改变了我们思考应用程序架构的方式。Node.js 是第一种试图应对这种挑战的技术,它充分利用了基于 JavaScript 针对服务端软件的非阻塞、异步运行时环境。vert.x 是在去年发布的,它是一种与 Node.js 类似的运行时,但是在 Java 虚拟机中实现的。与 Node.js 不同,vert.x 使用了一种真正的多语言方法,让开发者可以使用 JavaScript、Groovy、Java 以及其他语言来构建他们的系统。甚至可以在一种应用程序中混合多种语言。
InfoQ 采访了 Eberhard Wolff,讨论了这两种技术之间的区别、基于它们创建架构出现的挑战以及这些架构所提供的好处。
Eberhard 之前是 SpringSource 的首席顾问,现在是 adessoAG 的架构和技术经理。他曾经从事分析和设计企业系统多年,并在过去几个月中对 vert.x 进行了研究。
InfoQ:Node.js 现在已经出现有四年多了,vert.x 出现了大约两年,很显然,对于这些技术和概念有很确定的需求。你对 Node.js 和 vert.x 持何种观点? 在哪里应用这些技术会很有用呢? 它们是为何种用例创建的?
Eberhard Wolff:我认为,对于在高性能的环境中处理大量客户端,有越来越大的挑战。异步的模型会对此有所帮助——它可以使用较少线程来处理很多客户端。但 vert.x 不仅限于此。在 JVM 上,你通常在一个应用程序中只能使用一种编程语言。Java 还占主流,是大家比较喜欢的编程语言。然后,还有 Scala,它有自己的框架,比方说 Clojure。这和.NET 上的情况有所区别。从最开始,.NET 就定位于多语言——即便是在同一个应用程序中。vert.x 让不同的语言可以更容易地在 JVM 中协作。在每种语言中,还有使用了各种框架的包装器。你可以基于 vertx. 的事件总线(event bus)在 JVM 上运行 JavaScript 代码,并传递 JSON 对象,你可以把它与不同的语言——像 Java 和 Scala——集成。这是一种很有趣的变革,因为那是我所知的在 JVM 上唯一真正实现多语言的方法。然后,还有关于 JVM 上与模块化相关的挑战:你如何让单独的模块可以分开部署? 一种可能是使用 OSGi。然而,它还没有得到广泛的应用。另一种方式是在一台服务器上部署不同的 WAR 文件,并让它们通过 SOAP 通信,但那是一种非常复杂的解决方案,而且会带来很大的性能负载。vert.x 提供了一种非常棒的模块化方法。所以 vert.x 是一种非常有趣的框架,因为它是异步的、多语言的和模块化的。
InfoQ:说到 Node.js,我真的在编写一个应用程序,它会运行在 Node.js 服务器上。它对请求以单线程的方式做出响应,并能够把那些请求取到线程池中, 在需要的时候做后续处理。那意味着我不需要任何模块化或者不同的组件作为高层次的概念,它们相互之间就可以通信。而 vert.x 在此是如何工作的呢?
Eberhard:在 vert.x 中,模块——各种不同的部署单元——会使用事件总线来相互发送事件。那些事件可以包含 JSON 之类的对象。vert.x 中的模块拥有分离的类载入程序(classloader),所以它们可以真正被视为单独的组件。所以你可以把功能放到不同的模块中。例如,在 Github 上针对与 MongoDB 交互提供了一个模块, 它会使用来自于事件总线合适的事件,并把数据存储到数据库中。与异步编程相比,乍一看,它像是一种很小的特性,但是我认为它非常重要。它解决了你在企业应用程序中经常需要面对的问题。
InfoQ:你是否可以更详细地介绍一下事件总线? 我是否可以认为它是一种带有主题(topic)和队列的 Java 消息系统?
Eberhard:它不仅提供了点到点的通信,还提供了发布 / 订阅机制。它还可以分布在多个节点之上。然而,事件是短暂的。它们没有存储在任何一种稳定的存储介质上。因此,事件可能会丢失。这与 JMS 不同,后者把重点放在可靠性上,而 vert.x 的事件总线把重点放在性能上。
InfoQ:那些模块彼此之间的独立性如何? 你能否真的选择一种组件,并在运行的同时进行置换或升级? 针对我的模块的消息是否会一直排队,直到得到支持并运行?
Eberhard:模块可以在运行同时被置换或升级。但事件并不是持久的。所以,只要接收程序不存在,那么事件就会被抛弃。
InfoQ:在这种环境中使用的系统看起来会是什么样子? 毕竟,我不能说,一方面我想要高性能和高可伸缩性,另一方面我要集成无法符合这些概念的系统。
Eberhard:这正式 vert.x 的另一种优势:你可以定义一种叫做“垂直工件(worker verticles)”的东西。垂直工件会使用一个线程池,这样你可以集成需要阻塞 IO 的遗留代码。线程池与主事件循环(primary event loop)彼此分离。所以事件循环不会被垂直工件所阻塞。这样工件可以做它需要做的任何任务——同步的、阻塞 IO 的或者任何类型。这种特性真的非常重要,因为在 Java 领域有很多库,并没有被设计为非阻塞的。
InfoQ:当你设计一个新系统的时候,提前对这进行评估会带来多大好处? 我曾经读过对 Node.js 的创造者 Ryan Dahl 的采访,在其中他声称选择 JavaScript 是个好主意,因为它只有少数几个模块。而“只有少数几个模块”也意味着,从概念的角度来看,不会有太多错误的模块——也就是没有设计为异步使用的模块。我想,如果你选择一种任意的 Java 库,可能很快就会遇到麻烦,对吗?
Eberhard:没错。但那正是垂直工件起作用的地方。我理解 Node.js 的用意。只是因为 JavaScript 程序员习惯于使用异步概念。他们已经使用 AJAX 进行异步编程很长时间了。在 Java 社区中,还有——至少曾经有过——这样的声音,说“我们理解如何构建大型系统,而 JavaScript 程序员只是构建简单的前端程序”。但有了 AJAX,JavaScript 程序员们总是需要处理回调和异步的概念,那对他们来说很自然。因此,基于 JavaScript 来构建类似于 Node.js 之类的东西就非常自然了,况且我们拥有高性能的 JavaScript 环境。vert.x 也显示了这一点:看一下 vert.x 的例子,在 Java 中,有匿名的内部类,而不像在 JavaScript 中是函数。那只会导致需要编写更多代码。依我所见,那是 Java 语言中一种经典的设计错误。匿名内部类的想法很不好。但 Java 8 会解决这个问题。
InfoQ:你提到,Java 社区告诉 JavaScript 程序员“去构建前端程序”。在 Node.js 方面是否有少许劣势,因为有很多人能够使用 JavaScript 编程,但能够在 Node.js 上构建的系统确实需要不同的概念背景知识? 我们会有这样的风险,因为这些进入到企业市场中的新语言,而看到人们设计系统时不具备合适的企业知识?
Eberhard:让我来这么说吧:Java 社区传统上是构建后端系统,并且用于大型系统上。大量设计大型系统的想法都来自于 Java 社区。但是我不会通过开发者碰巧使用的语言来对其加以判断。至于说异步的概念,它们已经深入到 JavaScript 开发者的血液之中了。对于大型系统来说,模块化是一个重要的话题。vert.x 在此提供了一种有意思的替换方案。使用合适的模块化方法,你可以把大型系统分解为更小的部分。更有意思的是,模块化的概念基于消息,而消息又提供了更多优势。你可以达到更松的耦合,因为系统仅仅依赖于消息,而不是依赖于调用特定的方法。
InfoQ:是的。并且通过使用 JSON 会更方便,那样我们就拥有了一种消息格式,它本身就非常“松”,有助于避免兼容性的问题。
Eberhard:的确如此。但 JSON 只是一种技术可能性。JSON 并没有提供任何模式的定义。实际上,有人说基模(schemata)不好,因为它让事情变得不够灵活。他们抱怨最终不得不使用瀑布模型,其中第一步你要设计接口,然后基于它们来构建你的系统。不管怎样,我认为那会很好,如果你能够检查 JSON 消息的结构,就会发现它正如我所愿。而那正是我意识到 JSON 的问题的地方。通常,你想要某种契约,其中定义了期望的数据结构。我认为,在这样的情况下,你会从 XML 基模受益。
InfoQ:实际上,几个月之前我在动态数据类型方面有过有趣的经历。我举办了一场培训,其中我们编写了复制 Twitter 的代码,基于 PhoneGap、Node.js 和 MongoDB。忽然,前端人员有了个主意,要向他们的“tweets”里面增加图片。那些图片会通过 Node 服务器传输到数据库,稍后再回到前端。当前端团队展示这个特性的时候,节点和数据库团队都非常惊奇,因为他们没有意识到现在处理的是图片。在这种情况下,那非常酷,但我认为,这也会导致严重的问题。
Eberhard:那正是灵活的基模的优势所在。但如果两个以上团队参与到开发中,那么你通常会想要拥有某种接口契约。为了执行那种契约,你需要某种形式的模式(schema)。你所描述的情形表现出另一个有趣的问题:你需要在哪里解释数据? 在你提到的栈中,在大多数系统中,你只是对检查或者验证数据不感兴趣。你只是想要存储 JSON 文档,就是那样。文档中有什么并没有关系,因为你不需要解释数据的语法。可能在企业系统中有些不同。如果你说“那是客户”,那么你就需要知道,客户的数据看起来会是什么样子。
InfoQ:好吧,那背后有一些 JavaScript 和动态类型的观念。当它像鸭子一样走路、游泳、嘎嘎叫,那么我们就把它叫做鸭子。如果它看起来像是我们期望的类型,并且包含所有我需要的数据,那么我就不关心其他内容。
Eberhard:的确如此。但还有问题。当你获得一个 customer 对象,期望它拥有出生日期,那么你就可以在 schema 中定义。在那里 XML 基模要更好一些,因为它们拥有高度复杂的类型系统。所以你甚至可以用正则表达式的方式来定义一个订单号码看起来是什么样子。如果有人违反了 schema,就会遇到问题。schema 会提前告诉他的数据非法。它可以使用这作为早期的警告。
InfoQ:在 vert.x 事件总线中,一直使用的是 JSON 吗?
Eberhard:可以使用任意对象。然而,如果你使用 Java 对象,那么你就会遇到序列化和载入类的问题。那就是为什么在这种情况下 JSON 会更好一些,而那也是经常采用的方式。
InfoQ:关于像 Node.js 和 vert.x 这样的技术,你会说大型应用的架构基于这些技术,或者你会说一种架构只是在特定的领域使用这些技术? 据我所知,LinkedIn 使用 Node.js 来处理移动设备的流量,但应用程序本身是“正常的”企业应用。
Eberhard:现在,我想还需要一段时间之后才能回答这个问题,直到那些技术在经典的企业系统中得到广泛应用。当前我能够看到它与 Spring 的区别。大家都知道 Spring 适合于企业系统。迁移路线很清晰。对于 vert.x 有些不同。vert.x 可以嵌入在其他应用中。但是想要完全发挥它的能量,你需要使用它的运行时环境。这种环境与传统的企业级 Java 截然不同。只有一个 Java 进程,而没有 servlet 或者应用程序容器。然而,有一种使用异步系统的趋势。有 Erlang、Scala 和 Akka 语言,以及向 Spring Integration 和 Apache Camel 之类的框架——每一种都有不同的方法和不同的关注点。例如,Sring Integration 和 Apache Came 提供了各种适配器,用来发送异步消息和处理数据。所以它们提供了集成解决方案——正如《企业集成模式》一书中所显示的。Erlang 的思想是要实现高性能和可靠性。vert.x 也类似。所以异步工作是构建系统的一种特殊方式,那会变得越来越重要。
InfoQ:一个人不应该试图使用这样的技术来实现,你是怎么想的? 这会遇到什么样的陷阱?
Eberhard:问题是,在何种情境中这些解决方案会特别有用。我认为甜蜜点是构建高性能的系统,特别是有非常大量的客户端的系统。或者是集成场景和其他能够受益于松耦合的场景。换句话说:如果你想要构建一个平常的 web 应用程序,那么它可能不是最佳的解决方案。
InfoQ:我如何才能在 vert.x 中使用现存框架? 我能否使用所有现存的框架,比方说使用某些前端框架,像 Apache Wicket,来提交网页?
Eberhard:不幸的是,那并不容易,因为大多数前端框架都基于 Servlet API。servlet API 是阻塞性的,所以与 vert.x 不兼容。这样,你会使用一种模板引擎来设计解决方案,并自己来提交 HTML 页面。但更典型的用例是使用 JSON 和 REST 接口为 JavaScript 前端构建后端系统。你最后会在前端拥有更多逻辑,而在后端就会少一些。不一定是更少业务逻辑,可能是用于渲染 HTML 页面的逻辑。
InfoQ:你提到了生产环境,你是否认为在这种情境下会对云平台有更多应用? 或者不会有太大差别,但我们的运维团队需要采用新的范式?
Eberhard:那取决于在这里你所指的“云”是什么。大多数 PaaS 云的问题在于,它们只是提供了 Servlet API,那样你就会面对之前讨论的问题。在 IaaS 上部署 vert.x 应用没有任何问题,但我并不认为 vert.x 在这个领域比其他 Java 技术有很大的优势。
InfoQ:让我们再多说一些与在开发期间提供支持的内容。如果我们知道 Node.js,那么你就可以找到更好或者更差支持 JavaScript 的 IDE,但不管怎样,看起来和我们开发 Java 软件时所使用的相差甚远。关于质量保证,看起来也是一样:有一些改进,但不是太大。对于 vert.x 情况如何?
Eberhard:vert.x 最棒的地方在于,你可以使用热重部署(hot redeploy)机制。那会让开发更简单。而你还拥有 vert.x 能够编译和执行任何你提供的源文件的优势。然而,还没有任何 Eclipse 插件。
InfoQ:除此之外,我假设你可以使用所有语言表现出来的特性。例如,用于测试的基础架构,或者你决定对 Java 所使用的自动代码审查?
Eberhard:没错,你可以这么做。但正如我之前所说的,模块化和部署的概念是不同的,所以在此你会面临一些挑战。
InfoQ:关于在 vert.x 中调试会怎么样? 我们是否可以依附于运行中的进程?
Eberhard:没问题,那种特性是 JVM 所提供的。使用 JVM 还是带来不少好处的。代码会在执行比特码的高度优化的虚拟机上执行。我认为,为比特码创建优化的 VM 要比 JavaScript 容易得多。V8 团队所构建的产品非常令人惊奇,且非常非常让人激动。但还存在大量在 JVM 上投入巨大的引擎。我认为依靠并使用它是个好主意。有些数据表明,vert.x 在各种指标上都比 Node.js 快,但和其他评测数据一样,那很难解释。
InfoQ:的确如此。但听起来很符合逻辑。另外,V8 团队在过去几年中所创造的东西非常棒。但另一方面,JVM 开发已经有 20 多年的历史了。你不得不说,V8 引擎也依赖于它的一些概念,从而达到高性能。但是,如果我不知道那些概念,也会编写出语法上正确的代码,那本身并没有问题,但那些内部概念会导致低性能。我认为在 Java 中并不会以同样的方式出现 。我可定会编写带有愚蠢的循环的丑陋代码,但那是另一种错误了。
Eberhard:但那就像是比较苹果和桔子。实际上,我宁愿与整个栈比较——Rhino 和 JVM 在 vert.x 上 JavaScript 的表现,以及 V8 在 Node.js 上的表现。在 JVM 中,一开始就没有对动态语言的支持。另一件事是,你可以在 vert.x 上使用多事件循环,通常是每个 CPU 核心一个。那看起来 V8 是做不到的。因此,如果我拥有一台八核的服务器,那么就需要启动多态 Node 服务器,而对于 vert.x 来说只需要启动带有八个事件循环的 JVM。那是不同的,可能会更有效率。但正如我之前所说,评测是一个复杂的话题。
InfoQ:在最后我再提一个问题,vert.x 的主要好处是什么? 你不能说对这个话题不感兴趣。
Eberhard:好吧,有很多原因让我认为这是一种令人激动的技术,而且我认为它非常重要。首先,在将来对于 JVM 来说,是否有异步 IO 会很关键。然后,它拥有更好的模块化概念,而那正是我认为企业级 Java 所缺乏的。这对很多项目都是问题,很难得到好的解决方案。最后,我认为它至关重要,因为依我所见,JVM 作为多语言的虚拟机会变得更重要。我不认为 Java 语言自身足以支撑 JVM 的未来,而 vert.x 会帮助它,因为它是真正多语言的。挑战是,对于 vert.x 运行时,全功能的 servlet 和应用程序容器需要交换。
Eberhard Wolff 是 Java Champions 的创始成员,多篇文章和多本图书的作者,并且经常会在国际会议上发表演讲。他的背景在于企业 Java、Spring、云和 NoSQL。它在德国柏林为 adesso AG 工作,职位是机构和技术经理。
查看英文原文: High-Volume / Scalable Architectures with vert.x - interview with Eberhard Wolff
评论