Node.js微服务(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-07 10:00:37

点击下载

作者:大卫·冈萨雷斯 (David Gonzalez)

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Node.js微服务

Node.js微服务试读:

前言

作为一本微服务入门的实践指南,本书采用了Node.js和以Seneca、PM2为主的现代框架来进行阐述。在各章中,我们将分别介绍如何利用最佳实践去设计、构建、测试以及部署微服务。此外,我们还会讨论另外一个有价值的课题——如何在设计系统时做出合理的妥协,来避免过度设计和确保技术方案与实际业务需求相匹配。本书概述

第1章主要讲述微服务的基本概念,包括主要优点和一些缺点,本章内容是本书后续章节的基础。

第2章介绍了Node.js、Seneca和PM2。还讨论了Node.js应用的结构,以及如何通过PM2来运行应用。另外,我们还研究了一些Seneca与PM2的替代产品。

第3章主要讲述如何使用微服务来处理自然增长(计划之外的软件需求变更)。另外,我们还讨论了如何将单块应用分解成微服务。

第4章阐述了如何编写我们的第一个微服务程序。

第5章涉及了安全性与可追溯性,这是现代系统的两大重要特性,因为我们需要保证信息的安全与操作的可追溯性。在本章中,我们讨论了使用Seneca来保证安全性与可追溯性的方法。

第6章主要介绍了Node.js的两大主流测试框架——Mocha和Chai。同时使用Sinon来mock服务以及Swagger来为微服务进行文档化。

第7章使用PM2结合Keymetrics来监控微服务,使PM2的功能得到最大发挥。

第8章通过使用PM2,学习如何在不同环境下部署微服务,并通过单条命令管理应用的“生态系统”,从而减少微服务架构带来的开销。我们还将讨论Docker,它是一个应用容器引擎,可以部署包括Node应用在内的各种应用。阅读本书的准备工作

为了能够完成本书的实践案例,需要预先安装Node.js、PM2(可以通过npm来安装),以及MongoDB。此外还需要一个编辑器,我个人选用了Atom,但是一般通用的编辑器都能满足需求。本书的读者对象

本书适合具有一定Node.js经验,并且想要学习Seneca以及微服务知识的开发者。在本书中,有70%的内容是面向实践的(因此我们会编写大量代码),有30%是理论知识。基于编写的这些代码可以帮助读者将书中提到的模式应用到新的软件开发中去。约定惯例

本书将会使用不同的书写风格来区分不同种类的信息。以下是这些风格的例子和它们的意义。

正文中的文本代码、数据库表名、文件夹名、文件名、文件扩展名、路径名、URL、用户输入和推特用户定位(Twitter handles)将会用代码体书写,如“我们知道输入参数是一个PaymentRequest实例”。

代码块则将会是这样的风格: public interface PaymentService { PaymentResponse processPayment(PaymentRequest request) throws MyBusinessException; }

如果希望向你强调代码块中的一部分,那么它们将会以粗体展示: function() { sinon.stub(Math, "random"); rollDice(); console.log(Math.random.calledWith()); }); after(function(){ Math.random.restore(); });

任何命令行的输入和输出将是以下这样的: node index.js npm start警告和关键提醒将会在这样的图标后出现。小提示和小技巧将会在这样的图标后出现。下载示例代码

你可以从http://www.broadview.com.cn下载所有已购买的博文视点书籍的示例代码文件。勘误表

虽然我们已经尽力谨慎地确保内容的准确性,但错误仍然存在。如果你发现了书中的错误,包括正文和代码中的错误,请告诉我们,我们会非常感激你。这样,你不仅帮助了其他读者,也帮助我们改进后续的出版。如发现任何勘误,可以在博文视点网站相应图书的页面提交勘误信息。一旦你找到的错误被证实,你提交的信息就会被接受,我们的网站也会发布这些勘误信息。你可以随时浏览图书页面,查看已发布的勘误信息。1 微服务架构(1)

微服务日趋流行。时下,几乎每一个参与“绿地项目”的工程师都应该考虑采用微服务来提升所建系统的质量,他们应该熟悉涉及这类系统的架构原则。我们将会在本书中揭示微服务与面向服务架构(SOA)之间的区别。同时还会向大家介绍一款用以编写微服务的强大平台——Node.js。通过采用Node.js,我们可以轻易地创建出高性能的微服务。

本章将会带领你从架构的视角来审视微服务:● 什么是微服务● 面向微服务的架构● 关键的好处● SOA与微服务的比较● 为什么选择Node.js微服务应运而生

在过去40年里,软件开发的世界日新月异。在这一飞速发展的过程中,一个关键的增长点便是系统的规模。从古老的MS-DOS发展到当代的系统,规模有了数百倍的增长。这种飞跃式的增长迫使我们不得不去寻求更好的方式来组织代码及软件组件。通常,在一家公司随着业务需求的增长而逐步发展(即自然增长)的过程中,前期往往是以单块架构(monolithic architecture)的方式来组织系统的。因为对于软件的初期构建来说,单块架构的方式是最容易且最高效的。但是若干年(甚至是几个月)后,受限于前期既有单块软件系统内部的耦合性,向该系统添加新功能变得越来越艰难。单块软件

如今,对于像Amazon和Netflix这样的高新技术公司来说,采用微服务来构建新的软件系统已经是大势所趋。理想情况下,面向微服务的软件在帮助这些公司扩展新产品规模方面有着强大的优势(学习完本书后,你也将深谙此道)。现在的问题是,并不是所有的公司都能提前对软件系统做好规划。相比于提前规划,很多公司一直以来都是基于自然增长的方式来构建软件系统的,鲜有软件组件会根据业务关系的紧密度来对业务流进行分类。不难发现,大多数公司都只有两个大的软件组件:面向用户的网站和内部的管理系统。我们通常将这种架构方式称为单块软件架构。

一部分这类公司在尝试扩充工程团队的时候,面临了巨大的挑战。协调好多个团队来对单个软件组件进行构建、部署以及维护是一件相当艰巨的任务。系统发布与bug重复引入之间的冲突往往是家常便饭,这些问题耗费了团队大量的精力。解决该问题的一个方案(同时还会带来额外的好处)是将单块软件拆分成微服务。每个团队都专职从事某个相对较小模块的维护,且这些组件也都是自治且互相隔离的。这样一来,我们便可以独立地对这些组件进行版本化、更新以及部署,从而不会牵扯公司的其他系统。

将庞大的单块系统拆分成微服务可以让工程团队创造出互相隔离且独立自治的工作单元。这些工作单元从功能上来说都是高度专职化的,例如可以专职处理电子邮件的发送、支付卡交易等特定任务。现实世界中的微服务

微服务是指那些小型的软件组件,每个服务都专职处理某一类任务,将它们有机整合后便可用于处理更高级的复杂任务。让我们先忘掉软件这个概念,然后回过头来思考一下一家公司是如何运作的。当某个候选人申请公司的某份工作时,他应聘的是一个特定的职位,比如:软件工程师、系统管理员或者办公室经理。之所以这样的原因归根结底就是一个词:专职化(specialization)。如果你曾任职软件工程师,将会在软件开发方面具有更丰富的经验,并且能在这方面给公司带来更多的价值。事实上,你并不知道应该如何跟客户打好交道,但是这并不会影响你的绩效,因为这并不是你的专业领域,所以在这方面你很难在日常工作中产出更多的价值。专职化通常是提升效率的关键。做一件事并将它做对是软件开发的准则之一。

微服务是一个自治的工作单元,它可以执行某个任务且不干涉系统的其他部分,就好比公司中的某个专职的工作职位。这种方式可以带来很多好处,它有助于工程团队对公司的系统规模进行扩展。

如今,数以百计的系统都采用了面向微服务的架构来构建,我们来看看:● Netflix是时下最流行的流媒体服务商之一,它构建了囊括自身所

有应用的整个生态系统,这些应用互相协作,从而构建出能为全

球范围提供可靠和可伸缩服务的流媒体系统。● Spotify是世界领先的音乐流媒体服务商,它采用了微服务来构建

应用。它的应用(该应用是一个采用Chromium Embedded

Framework实现的以桌面方式呈现的网站)中的每个单独的小部

件都是一个可以独立升级的微服务。面向微服务的架构

面向微服务的架构拥有一些特质。任意规模的大中型公司如果想让自身IT系统保持弹性,并希望随时都可以对系统规模进行伸缩的话,必定会对这些特质趋之若鹜。为什么面向微服务的架构更好

面向微服务的架构并不是软件工程的圣杯。但是,对于那些依赖于技术而发展的公司而言,如果能将该架构运用恰当,那么将会是解决这些公司所面临的大部分重要问题的完美方法。

弹性、可组合性以及灵活性都是面向微服务架构设计的关键原则。将这些原则烂熟于胸是一件非常重要的事情,否则,你将错失一个完美的解决方案,并最终在将单块应用分拆到多台机器上时遇到大量的问题。不足之处

事无绝对,周遭也存在着一些针对面向微服务架构的诟病。大家都提到了一些需要处理的问题,例如:延迟、可追踪性以及在单块软件中并不存在的配置管理的问题。其中一些问题描述如下。● 网络延迟:微服务具有分布式的特性,从而无可避免地会存在网

络延迟。● 运维负担:更多的服务器也意味着需要更多的维护工作。● 最终一致性:在一个对事务性要求较高的系统中,考虑到实现的

局限性,各个节点在某一段时间内可能会出现数据不一致(我们

将会在本章的后续部分继续讨论这一点)。

总而言之,工程师应该尽可能对该方法的利弊做出评估,然后根据是否满足相应业务需求来做出是否采用微服务的决定。

面向微服务的架构存在着一些需要被考量的特质。当软件工程师在编写单块软件的时候,受限于所构建软件的特性,很多问题都被忽视了。

举个例子,想象一下某个软件正需要一个发送电子邮件的功能。在单块软件中,我们会在应用的核心部分加入该功能的代码。更有甚者会选择创建一个专门用于处理电子邮件的模块(听上去貌似是个好主意)。而现在,我们并不打算向这个庞大的软件组件添加功能,而是选择了创建一个微服务。这是一个可以独立部署且可以独立版本化的专用服务。在这个案例中,我们忽略了一件事,即:在请求该新的微服务的过程中会产生网络延迟。

在前面的例子中,不管你采用哪种方式(单块方式亦或是微服务)来构建软件,并不存在任何严重的问题;举个例子,就算丢失了一封邮件,也不会因此就到达世界末日。根据定义,电子邮件的传递是不可靠的。所以即便收到一些来自客户的投诉,你的应用也将会继续运作下去。关键设计原则

下面是一些在构建微服务时需要考虑的关键设计原则。由于微服务是一个新概念,所以目前并没有任何现成的“黄金法则”。因而在实践的过程中,往往会缺少一些可以遵照的共识。

一般来说,我们可以确定以下设计原则:● 微服务是一系列参考公司流程模型而分拆出来的业务单元。● 它们是一系列包含了业务逻辑并能采用简单信道和协议与之进行

通信的智能端点。● 根据定义,面向微服务的架构是去中心化的,从而可以构建出健

壮和具有弹性的软件。从组件到业务单元

在软件工程中,创建新项目是一件大家喜闻乐见的事情。因为在新项目中,你可以肆意发挥全部的创造性,并在其中尝试各种新的架构理念、框架以及方法论。然而不幸的是,对于一家成熟的公司来说这并不是一种常态。通常你需要做的就是在一个既有的软件系统中添加新的组件。在创建新组件的时候,有一个最佳的设计原则值得大家遵守:就是要尽可能保持组件与软件其他部分的低耦合,这样它才能作为一个独立的单元进行工作。保持较低的耦合度得以让我们毫不费力地将软件组件转换成微服务。

让我们来看一个实际的例子:公司的应用目前急需处理支付的功能。

通常我们会为此创建一个新的模块,该模块可以处理与所选支付厂商(例如信用卡、PayPal等)之间的相关事务,然后会将所有与支付相关的业务逻辑封装在该模块中。定义的接口代码如下: public interface PaymentService { PaymentResponse processPayment(PaymentRequest request) throws MyBusinessException; }

这是一个大家都能理解的简单接口,而它却是通往微服务世界的关键。我们已经将所有的业务知识封装在该接口背后,所以理论上可以任意切换支付厂商而不会影响应用的其他部分。对外面的世界来说,接口内部的实现细节是不可见的。

到目前为止我们掌握了如下信息:● 我们知道方法名,因此也知道如何调用服务。● 该方法会抛出MyBusinessException类型的异常,这强制了调用

代码要处理该异常。● 还了解到输入的参数是一个PaymentRequest类型的实例。● 响应也是一个已知的对象。

我们已经创建了一个高内聚、低耦合的业务单元。下面来对该论断进行证明。● 高内聚:所有支付模块内的代码只做了一件事,就是处理支付以

及调用第三方服务(比如信用卡处理器)相关的事项(连接处

理、响应代码等)。● 低耦合:如果出于某些原因,需要切换到一个新的支付处理器会

怎么样?接口本身是否会泄露出任何实现细节的信息?如果接口

的契约发生了变化,是否需要更改调用服务的代码?这些问题的

答案当然是否定的。支付服务接口的实现将永远是一个模块化的

工作单元。

下图展示了一个系统是如何将其内部的一个组件(支付服务)剥离成微服务的,该系统由多个组件组成:

一旦实现该模块,我们便拥有了处理支付的能力。而该单块应用的这项功能也已经具备了被提炼成微服务的能力。

现在,我们将揭开新版支付服务的面纱。只要接口不发生变化,与外界(系统或第三方的服务)交互的契约也将保持不变。这也是为什么保持实现与接口之间独立性如此重要的原因,即使你所使用的语言并不支持接口。

为了满足业务需求,我们同样也可以根据自身需要来部署更多的支付服务从而扩大其规模。而对于应用中的其余模块而言,由于暂无太大的访问压力,可暂不扩容。智能的服务,愚蠢的通信管道

超文本转移协议(HTTP)是伴随互联网而生的最棒的工具之一。人们将HTTP设计为无状态,但又通过巧妙地结合cookies,从而让客户端可以保持状态。这些事出现在Web 1.0时代,那时还没有人谈论REST API和移动应用。让我们来看一个HTTP请求的例子:

正如你所看到的,这是一个人类可读的协议,无须太多解释就能被人理解。

如今,大家都已经明白,HTTP并不局限于在Web中使用。正如它的作者们所设计的,它目前已经被当作通用协议来使用,可用于在两个端点之间传输数据。HTTP正是我们所需要的、可用于微服务之间进行通信的协议:它可以传输数据,并在传输出错的情况下尽可能完成自我修复。

在过去的若干年里,尤其是在企业内部,人们花费了大量的精力来创建智能的通信机制,例如BPEL。BPEL是业务流程执行语言(Business Process Execution Language)的简称,它专注围绕各业务环节之间的衔接行为,而非通信行为本身。

这样一来便在通信协议中引入了一定程度的复杂度,使得应用的业务逻辑从端点渗透到了通信协议之中,从而导致了端点之间出现了一定程度的耦合。

应该将业务逻辑限定在各个端点之内,而不是任由其渗透到通信信道之中,这样的系统更便于测试与扩展。多年来,我们认识到,通信层必须是一个能确保数据在端点(微服务)之间有效传输且相对扁平、简单的协议。我们应该将业务逻辑封装在端点的实现中,因为在实际情况中,一个服务并不能保证每时每刻都在线(应对这一状况的能力被称为弹性,会在本章的后续部分继续讨论),而网络也可能会时不时出现一些通信问题。

HTTP通常是用以构建面向微服务的软件的最广泛的协议,而另一个值得我们关注且需要进一步探索的选项是使用队列来简化端点之间的通信,比如Rabbit MQ和Kafka。

队列技术以缓冲的形式为我们提供了一种管理通信的全新方式,它对高度事务性系统中消息确认机制的复杂性进行了封装。去中心化

单块应用的一个主要缺点就是将所有的事物集中在单个(或少量)软件组件和数据库中。这样做多半会产生超大的数据存储以及对工作流的集中式控制。而该数据存储是需要根据公司业务成长的需求不断复制与扩容的。

微服务的目标是去中心化。相比于构建超大的数据库,它选择根据前文提到的业务单元来对数据进行拆分。

一些读者会将事务性作为主要的理由从而拒绝使用微服务。先来看看下面的场景:

1.一个客户在面向微服务的网店中购买了一件商品。

2.当进入支付环节时,系统发出了如下调用:(1)向公司的财务系统发起请求,创建了一个支付的事务。(2)向仓储系统发起请求,通知对客户购买的书籍进行发货操作。(3)向邮件系统发起请求,为客户订阅新闻邮件。

在一个单块软件中,所有的调用都被包装在一个事务中。因此,如果有某些原因导致任何一个调用失败,其他调用涉及的所有数据都不会持久化到数据库中。

当你开始学习数据库设计时,首先接触到的一个重要原则便是ACID,它是以下四个属性的缩写。● 原子性:每一个事务要么全部生效,要么全部回滚。只要有一部

分失败,不会有任何变更持久化到数据库中。● 一致性:事务中产生的数据变更会得到一致性保证。● 隔离性:事务并发执行产生的系统状态将与事务串行执行产生的

状态保持一致。● 持久性:一旦事务被提交,数据将被持久化。

在一个面向微服务的软件中,ACID原则是无法得到全局保证的。微服务将会在本地提交事务,但是并没有机制能保证100%完整的全局事务。在上面的场景中,可能会出现没有处理支付就已经发货的情况,除非我们提前制定了特定的规则来防止这样的情况发生。

在一个面向微服务的架构中,数据的事务性是无法保证的,所以我们要在实现中考虑到失败的因素。一种解决该问题的方式(或者称之为一种变通的方案更为合适)是将管理与数据存储去中心化。

在构建微服务的时候,我们需要对下面的因素进行考量并将应对方案纳入设计之中。即一个或多个组件可能会发生失败,而我们需要根据软件的可用性来对功能进行降级。

让我们来看看下面这幅图:

该图表示了单块软件中的一组执行序列。不管怎样,这一系列调用的执行都应当遵守ACID原则:要么所有调用(事务)都成功,要么什么都不做。

为此,框架和数据库引擎的设计者提出了事务的概念来保障数据的事务性。

当我们与微服务打交道时,不得不提及一个概念,工程师们称之为最终一致性。当事务的一部分发生失败后,每个微服务实例都应该将用于恢复事务的信息存储下来,以便于这些信息能达到最终一致性。根据前面提到的例子,如果我们对书籍执行发货操作而没有处理好支付,那么支付网关将会产生一个失败的事务,并需要有机制在事后进行补偿处理,从而再次达成数据的一致性。

解决该问题的最好的方式就是对事务管理去中心化。每一个端点应该能做出本地决策从而促成全局范围的事务。我们将在第3章讨论更多该主题的内容。技术对比

当开始构建一个新的软件时,每个开发者的脑海中都应该持有一个概念:标准。

标准能保障你的服务在技术上是独立的,以便于可以方便地将采用不同语言或技术开发的系统进行集成。

采用微服务对系统进行建模的一个优势是可以选择最合适的技术来做相对应的工作,这对于问题的处理来说是相当高效的。而当在构建单块软件的时候,很难像采用微服务那样来组合使用各种技术。通常,在一个单块软件中,我们已经被绑定在最初选择的技术上。

Java远程方法调用(RMI)就是非标准协议的一个例子,如果你希望系统对新技术是开放的,那么应该避免使用这样的技术。如果所有软件组件都是用Java编写的,那么采用这种技术将所有组件连接起来会是一件很棒的事情,但是当开发者使用Node.js来调用一个RMI方法时将会非常纠结(如果尝试没有失败的话)。这样做会将我们的架构绑死在一个特定的技术上,从微服务的视角来看,这等于扼杀了一个非常有意思的优势:技术多样性。多微才是足够的微

一旦决定将系统建模成一系列微服务,那么通常首先要回答一个问题:多微才是足够的微?

而答案往往很巧妙,甚至会让人失望:视情况而定。

对于一个特定系统来说,微服务的规模多大才合适取决于公司的组织结构,最好是所创建的软件组件的管理工作正好可以由一个小规模开发团队轻松胜任。这同样也取决于技术需求。

想象有这样一个系统,它专门接收和处理银行文件。你可能也了解,银行之间所有的支付都是以某种已知的特定格式(例如单一欧元支付区,即SEPA格式用于欧元支付)的文件来传递的。这类系统的特性之一就是要知道如何处理大量不同的文件。

从微服务的视角来看,解决该问题的首要办法就是将该部分功能从其他所有服务中剥离出来创建成单独的工作单元,并为每一种文件类型创建一个微服务。这样一来,我们便可以对既有的文件处理器进行修改,而不会妨碍到系统的其他功能。即使在其中某个服务出现问题时,我们仍然可以继续处理其他文件。

微服务的规模应该尽可能小,但是请记住,为了对新的服务进行管理,每个微服务都会给运维团队带来开销。我们来尝试回答一个问题,多微才是足够的微呢?应该从可管理性、可伸缩性以及专职化这几个方面来考虑。微服务应该小到一个人就能承担服务管理工作,并能快速对其扩容(缩容)而不影响系统的其他部分。同时,它应该只做一件事。常规而言,微服务应该小到足够在一个sprint之内完成开发。关键的好处

在前面的话题中,我们讨论了面向微服务的架构是什么。同时揭示了一些从实战经验中提炼出来的设计原则,并展示了一些这类架构的好处。

现在,我们来看几项关键的好处,并展示一下它们是如何帮助我们提升软件质量并适应新的业务需求的。弹性

维基百科将弹性定义为系统处理变化的能力。我对弹性的理解是在问题被解决后系统从异常状态(短暂的硬件故障以及意料之外的高网络延迟等)或压力期中优雅恢复,同时又不会影响系统性能的能力。

这虽然听上去很简单,但是在构建面向微服务软件的时候,问题源会由于系统的分布式特性而被放大,有时很难(甚至不可能)防止所有的异常情况。

弹性是从错误中优雅恢复的能力。但它同样也为系统带来了新的复杂度:如果一个微服务出现了问题,我们能否防止系统的常规故障?理想情况下,我们应该以这样一种方式来构建服务:仅对服务响应进行降级而非让系统出现常规故障,即使这样做也并不容易。可伸缩性

如今,各大公司的一个通病是系统存在可伸缩性问题。如果你之前曾与某个单块软件打过交道,我确信你在伴随公司的成长过程中,必定会在某些时刻遭遇到容量问题。

通常,这些问题并不涉及应用的每一层次或所有子系统。往往只有个别子系统或服务会明显慢于其余部分,一旦没有处理好容量问题就会导致整个应用发生故障。

下图描述了我们是如何对微服务进行扩展(扩展成两个邮件服务)的,同时又不牵扯系统的其余部分:让我们来看一个关于车险的场景,用于计算指定风险因素列表报价的服务便是该类问题的一个例子。通过扩展整个应用来满足对某个特定部分的需求是否有意义?如果你脑海中的答案是“否”,那么你离拥抱微服务更近了一步。微服务可以让你仅仅按需扩展系统的一部分,从而只加大系统特定部分的处理能力。

如果该保险系统是一个面向微服务的系统,那么我们只需要创建更多的微服务实例来负责计算就能解决报价计算需求过旺的问题。请记住,扩展服务会给运维团队增加开销。技术多样性

软件的世界每几个月就会更新换代。新语言进入业界成为某类系统事实标准的节奏片刻未停。几年前,Ruby on Rails 面世并在2013年成为在各种新项目中使用最多的Web框架。Golang(由Google创建的一门语言)因其结合了强大的性能与优雅简洁的语法而成为当前的一种趋势,任何只要拥有一门编程语言经验的人都可以在几天内学会它。

在过去,我也曾使用Python和Java成功编写过微服务。

尤其是Java,自从Spring Boot发布之后,它成为在编写敏捷微服务方面相当有吸引力的技术栈。

Django是一款强大且可用于编写微服务的Python框架,与Ruby on Rails非常相似。通过它我们可以自动化地进行数据库迁移,并可以非常轻松地完成创建CRUD(创建、读取、更新及删除)服务的工作。

Node.js利用了著名语言JavaScript的优势,创建了一个新的服务端技术栈,从而改变了工程师们编写新软件的方式。

那么,将这些技术都结合起来会有什么问题吗?平心而论,这是一个优势:我们可以选择合适的工具来做相对应的工作。

只要待集成的技术是标准化的,面向微服务的架构便可以帮你实现这一点。正如我们在上文中已经了解到的,一个微服务是非常小的,并且是一个自主运维的软件中的独立部分。

下图展示了微服务是如何隐藏数据的存取逻辑的,两个服务在存取数据方面共用同一个通信点,从而能很好地互相解耦(一个服务实现发生变化时并不涉及任何其他服务):

此前我们曾讨论到性能的问题。通常,系统的某些部分会比其他部分承受更多的压力。通过利用当代的多核CPU进行并行(并发)编程可以解决其中的一些性能问题。然而,Node.js并不是一门适合执行并行任务的语言。针对那些处于压力之下的微服务来说,我们可以选择一门更加适合的语言来进行开发,比如Erlang,从而可以以一种更加优雅的方式来管理并发。这样做,花不了你两周的时间。

在同一系统中使用多种技术存在着一个问题:开发人员和系统管理员需要知道所有的(或一部分)相关技能。拥抱微服务的公司通常可以秉持一门核心技术(在本书中,我们将会使用Node.js),并辅以一些其他技术(我们除了使用Docker来管理部署之外,还可以采用Capistrano或Fabricator来管理发布)。可替换性

可替换性是指替换系统中某个组件而不影响系统行为的一种能力。

当我们在讨论软件的时候,可替换性往往是与低耦合密不可分的。在编写微服务的时候不能将内部逻辑暴露给调用服务,服务实现对客户端来说是透明的,客户端了解的只有接口。让我们来看看下面的例子,该接口是用Java编写的,仅需通过观察接口就能识别出它存在着什么问题。 public interface GeoIpService { /** * 检查IP是否属于指定ISO代码所对应的国家 **/ boolean isIn(String ip, String isoCode) throws SOAPFaultException; }

初看该接口可以发现它是自描述的。它将检查特定IP是否属于特定的国家,一旦服务出现重大问题会抛出SOAPFaultException。

如果我们构建客户端来消费该接口,需要考虑到服务的上述逻辑,捕获并处理SoapFaultException。这等同于将服务内部实现的细节暴露给了外部世界,从而很难再替换掉GeoIpService接口。同样的,事实上我们创建的某个服务如果关联了应用逻辑的某个部分则表明创建了一个限界上下文:即一个高内聚的服务或服务集(通过集合所辖服务的协同工作可以达成一个目标)。独立性

不管我们怎么努力,人类的大脑都不擅长解决复杂问题。人类大脑最有效的运作模式是同一时间只做一件事情,所以我们可以将复杂问题拆解成更小的问题。面向微服务的架构应该也遵从这一方式:所有服务应该都是独立的,它们通过接口进行交互。除了协定确认接口这一环节之外,不同的工程师团队可以在无须交流的情况下完成对服务的开发。一家采用了微服务的公司可以根据业务的需求来调整工程师团队的规模,从而能敏捷地响应业务的高峰期或静默期。为什么可替换性如此重要

在前面的一个小节中,我们讨论了该如何确定微服务的合理规模。按照普遍的经验而言,一个团队应该能在一个sprint内完成一个微服务的重写和部署。这样做的背后的根本原因就是技术债务。

我会将技术债务定义为在一个既定计划的周期内,初始技术设计与预期交付功能之间的偏差。某些方面的牺牲或错误假设会导致编写的软件非常糟糕,这样的软件需要全盘重构或重写。

在前面的例子中,接口在暴露给外部世界时明确表明必须使用SOAP来调用Web服务。一旦需要将客户端代码改造成REST客户端,REST客户端根本无法处理SOAP异常。易于部署

微服务应当易于部署。

作为软件开发者,我们知道在软件的部署过程中很多事情都可能会出现问题。

正如前面所提到的,微服务是非常易于部署的,原因如下:● 少量的业务逻辑(从经验上来说是只需两周即可完成从无到有的

编写)导致更易于部署。● 微服务是自治的工作单元,所以升级一个服务对于复杂系统来说

是一个局部可控的问题。无须重新部署整个系统。● 微服务架构中的基础设施和配置应该尽可能自动化。在本书的后

续部分中,我们将学习如何使用Docker来部署微服务,以及这样

做相比于传统部署技术会有怎样的优势。SOA与微服务的比较

面向服务架构(SOA)已经存在有些年头了,这是一种用于设计软件的伟大原则。在SOA中,所有组件都是独立自主的,并能为其他组件提供服务。要替换掉系统中的某些部分而不对整个系统造成较大的影响本是个难题,然而只要维护好系统各模块之间的低耦合,该难题便能迎刃而解,这也是我们之前谈及微服务时所认可的。

大体上,SOA与微服务架构是非常相像的。那么它们之间的区别到底是什么呢?

微服务是细粒度的SOA组件。换句话说,某单个SOA组件可以被拆成多个微服务,而这些微服务通过分工协作,可以提供与原SOA组件相同级别的功能,如下图所示。微服务是细粒度的SOA组件,它们是关注点更窄的轻量级服务。

微服务与SOA之间的另一个不同之处是服务互联和编写服务时所使用的技术。

J2EE是一个遵守企业级标准的用于编写SOA架构的技术栈。Java命名与目录接口(JNDI)、企业级Java Bean(EJB)以及企业服务总线(ESB)都是SOA应用赖以构建和维护的生态土壤。即便ESB是标准,在2005年之后毕业的工程师却鲜有听说过ESB的,至于用过ESB的那就更少了。而当代的,例如Ruby on Rails这样的框架甚至不会去考虑如此复杂的软件部件。

而另一方面,微服务推崇执行的标准(例如HTTP)却是人们广泛了解并共同使用的。我们可以通过选择合适的语言或工具来构建某个组件(微服务),进而获得本章“技术多样性”小节所提到的关键好处。

除了技术栈与服务规模之外,在SOA与微服务之间还有一个更大的区别:领域模型。在本章前面的内容中,我们曾讨论过去中心化。有管理的去中心化,也有数据的去中心化。在一个基于微服务的软件中,每个微服务应该在本地存储自身管理的数据,并将领域模型分别隔离到单个服务中。而在面向SOA的软件中,数据往往存储在单个大型的数据库中,服务之间会共享领域模型。为什么选择Node.js

几年前,我对Node.js还不太感兴趣。于我而言,它仅仅还只是一个趋势,离作为解决问题的实际工具还欠点火候……在服务端运行JavaScript?这并不见得是一个明智的选择。平心而论,我甚至不喜欢JavaScript——直到像jQuery或Angular.js这样的当代框架横空出世。它们解决了一个重要的问题,即跨浏览器的兼容性。之前,我们曾需要为兼容至少三个浏览器而苦恼。而在jQuery出现之后,所有兼容性处理的逻辑都被封装到一个库里,我们只需要遵照jQuery文档编写代码而无须再操心兼容性问题。

从此,JavaScript日趋流行。一夜之间,几乎所有的内部系统都采用了单页应用(SPA)框架来编写,而这些框架更是重度使用了JavaScript。因此,如今的大部分开发者几乎都成了JavaScript的熟练手。

于是,有些人打算将JavaScript的用途拓展到浏览器之外,这是一个很棒的想法。Rhino、Node.js和Nashorn都是可以运行独立JavaScript程序的运行时案例。它们中的一些运行时还可以与Java代码交互,开发者可以向JavaScript程序导入Java类,从而可以直接复用无数由Java编写的框架。

让我们来重点看看Node.js。Node.js是用来构建面向微服务的架构的绝佳选择,原因如下:● 学习门槛低(但是如果想要精通还是有一定门槛的)● 易于扩展● 对测试友好● 易于部署● 可以通过npm进行依赖管理● 有着大量与主流标准协议相集成的库

基于以上这些原因,结合我们将在后续章节中揭示的其他原因,Node.js成为构建可靠的微服务的最佳选择。API聚合

我选择Seneca供后续章节开发之用。Seneca的一个最吸引人的特性就是API聚合。

API聚合是一项用于将不同功能(插件、方法等)组合成一个接口的高级技术。

让我们来看看下面的例子: var express = require('express'); var app = express(); app.get('/sayhello', function (req, res) { res.send('Hello World!'); }); app.get('/saygoodbye', function(req, res) { res.send('Bye bye!'); }); var server = app.listen(3000, function () { var host = server.address().address; var port = server.address().port; console.log('App listening at http://%s:%s', host, port); });

前面的例子使用了Express,这是一个在Node.js技术栈中广为流行的Web框架。该框架同样也是围绕API聚合技术来构建的。让我们来看看第4行和第7行。在这些代码里,开发者注册了两个方法。当某人以GET请求的方式分别单击URL:/sayhello和/saygoodbye时,这两个对应的方法将会被执行。换句话说,该应用由不同的独立且更细粒度的实现组成,然后在单个接口上将这些实现暴露给外部世界。在该例子中,该接口就是一个在3000端口上监听的app。

在下面的章节中,我将解释为什么API聚合这一特性是如此重要,而我们又是如何利用它来构建(扩展)微服务的。展望Node.js

JavaScript的设计初衷是成为一门在Web浏览器中执行的语言。对于那些在工作或学习中使用C/C++的人来说,JavaScript的语法分外亲切。这也是为什么JavaScript在Web 2.0时代被作为文档动态处理的标准而采用的关键因素。异步JavaScript与XML技术(AJAX)成为JavaScript快速成长的催化剂。然而,不同的浏览器对于AJAX请求对象有着各自不同的实现,以至于开发者需要花费大量的时间来编写跨浏览器的代码。

标准的缺失导致出现了大量用于封装AJAX背后逻辑的框架,从而帮助我们更容易地编写跨浏览器的脚本。

JavaScript是一门脚本语言,它的设计初衷并不是成为一门面向对象语言,同时也不是编写大型应用的最佳选择。往往大型应用的代码会变得越来越混乱,而且很难统一不同公司的代码标准。每个公司各自拥有属于自己的最佳实践,而其中个别公司之间的做法又甚至可能是互相冲突的。

欧洲计算机制造协会(ECMA)开始出来主持大局。ECMAScript 6是ECMA语言(JavaScript、ActionScript、Rhino等)的下一代标准,它引入了类、继承、集合及其他一些有助于简化JavaScript软件开发的有趣特性,相比于V8规范显得更加标准。

在这些特性中,我认为最有趣的是引入了class关键词,该关键词使我们可以采用对象来构建JavaScript软件。

当前,大多数浏览器都支持了大部分这些新特性,但是反观Node.js,默认只实现了标准的一小部分,而其中一部分的实现还需要我们向解释器传入特殊的标识(harmony flag)才能启用。

在本书中,我将尽可能避免使用ECMAScript 6的新特性,并继续使用大部分开发者已经广泛接受的V8规范中的内容。一旦你熟悉了JavaScript V8,将应用迁移到ECMAScript 6将是一件轻而易举的事情。小结

在本章中,我们学习了围绕微服务的一些关键概念,同时也学习了一些设计高质量软件组件的最佳实践。通过遵守这些最佳实践,我们可以构建出健壮及弹性的软件架构,从而能快速响应业务的需求。

你同时也了解了一些面向微服务的架构的关键好处,比如可以为相应的服务选择合适的语言(技术多样性);以及一些可能会加重我们负担的误区,比如由面向微服务的架构的分布式特性而带来的运维方面的开销。

最后,我们讨论了为什么Node.js是用来构建微服务的强大工具,以及如何通过利用像API聚合这样的技术来从JavaScript获益,从而构建出高质量的软件组件。

在后面的章节中,我们将通过代码示例以及更深入的话题(都是我研究多年的话题)讨论来逐步揭示本章中讨论的这些概念。

正如前面已经说明的,我们将主要专注于JavaScript的V8版本,但是我也会就如何轻松地编写出可升级的组件从而能拥抱ECMAScript 6给出一些提示。

(1) 绿地项目(green field)意指那些没有太多前置约束的项目。类比自在一片绿地上开发项目,没有既有建筑与基础设施的约束。2 基于Seneca和PM2构建Node.js微服务

本章主要介绍两大框架:Seneca和PM2,以及它们对于构建微服务的重要性。同时,为了让读者对Node.js生态圈有个大体的了解,我们还将介绍一些其他备选方案。本章分为以下三小节。● 选择Node.js的理由:在本节中,我们将证明选择Node.js来构建

微服务的正确性。并且,我们还将介绍使用Node.js时涉及的软

件栈。● 微服务框架Seneca:在本节中,你将学到关于Seneca的基本知

识,以及它能够使整个软件系统变得易于管理的原因。为了遵循

业界标准,我们将教会读者如何整合Seneca与Express(Node.js

平台下最流行的Web开发框架)。● PM2:PM2是运行Node.js应用的最好选择。无论你想如何部署

应用系统,PM2都能够提供很好的解决方案。选择Node.js的理由

在之前的章节中,我提到过自己曾经不是一个Node.js的追捧者,因为我并不想忍受JavaScript标准化程度太低带来的困扰。

最初,JavaScript只能在浏览器中运行,这是一段痛苦的回忆。在我们使用JavaScript时,经常会出现跨浏览器的兼容问题,而且并没有一个统一的标准来解决这个问题。

接着,Node.js出现了。它具有非阻塞特性(在后续章节中将会进一步介绍),使得我们能够很容易地创建具有高可伸缩性的应用。而且,由于它是基于JavaScript这一风靡已久的语言,因此学习起来也非常容易。

如今,Node.js已经成为国际上许多科技公司的首选方案。特别的,对于在服务器端需要非阻塞特性(例如Web Sockets)的场景,Node.js俨然成了最好的选择。

在本书中,我们将主要使用(但不仅限于)Seneca和PM2作为构建、运行微服务的框架。虽然我们选择了Seneca和PM2,但并不意味着其他框架不好。

业界还存在一些其他备选方案,例如restify或Express可用于构建应用,forever或nodemon可用于运行应用。然而,我发现Seneca和PM2是构建微服务的最佳组合。主要原因如下:● PM2在应用部署方面有着异常强大的功能。● Seneca不仅仅是一个构建微服务的框架,它还是一个范例,能

够重塑我们对于面向对象软件的认知。

另外,在本书的一些章节中,我们会结合实例讲述如何将Seneca作为中间件与Express结合使用。

在讨论这些框架之前,我们准备先介绍一些Node.js的概念,这将有助于对框架的理解。安装Node.js、npm、Seneca和PM2

Node.js的安装非常容易,根据操作系统的不同,要选择不同的安装包。然后,只需双击安装包,按照指示进行安装即可,安装包会同时安装Node.js和npm(Node Package Manager)。在本书编写时,已经有用于Windows和OS X系统的安装包。

当然,高级用户(特别是开发运维工程师)可以通过源码或者二进制包来安装Node.js和npm。可以在Node.js的官网上下载到不同平台的安装包,安装包中包括了Node.js和npm(同时也提供各版本的源码、二进制包下载),下载地址:https://nodejs.org/en/download/。

Chef是一款用于搭建服务器环境的配置管理软件,在它的众多特性中,最流行的是recipe,recipe可以看作是基于Chef进行软件安装、配置的脚本文件。更多说明可以参考以下网址: https://github.com/redguide/nodejs。

在本书编写时,已经有Linux平台下的二进制包。学习npm

npm伴随着Node.js的出现而产生,它使你可以从互联网上获取依赖包,并且不需要关心这些依赖包的管理问题。通过npm,你还能够轻易地维护、更新依赖包。同时,也可以通过它来创建全新的工程。

每个node应用的根目录下面都有一个package.json文件,定义了这个项目所需要的各个模块,以及项目的配置信息(比如依赖、版本和常见命令等)。我们看看下面这个例子: { "name": "test-project", "version": "1.0.0", "description": "test project", "main": "index.js", "scripts": { "test": "grunt validate --verbose" }, "author": "David Gonzalez", "license": "ISC" }

这是一个自描述文件,文件中值得注意的是scripts。

在本节中,我们可以指定一些特定的命令来执行不同的动作。对于上述例子,如果我们在终端中输入npm test,npm将执行grunt validate --verbose。

Node应用一般都是通过执行一条简单的命令来运行的: node index.js

不过,你得确保在项目的根节点下存在index.js文件。一般情况下,最好的方案是在package.json文件的scripts中添加如下信息: "scripts": { "test": "grunt validate --verbose" "start": "node index.js" },

正如你所见,现在我们可以执行两个不同的命令来运行同一个程序: node index.js npm start

使用npm start的好处是一致性。无论你的应用多么复杂,都可以使用npm start来运行它(只要你的scripts配置正确)。

让我们开始在一个全新的工程里安装Seneca和PM2。

首先,登录终端,在安装完Node.js之后创建一个新的文件夹,执行npm init。将会得到与下图类似的提示:

npm要求输入一些参数来配置工程,完成这些输入后,它会按照

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载