Skip to content

Latest commit

 

History

History
597 lines (433 loc) · 29.7 KB

File metadata and controls

597 lines (433 loc) · 29.7 KB

十六、应用扩展

扩展 Node.js 应用可能是一个挑战。JavaScript 的单线程特性使 Node 无法利用现代多核硬件。为了有效地伸缩,Node 应用必须找到一种方法来利用它们所能支配的所有资源。核心模块服务于这个目的,允许单个应用启动一组共享资源的 Node 进程,同时分配负载。

扩展 Node 应用的另一种方法是减少应用必须完成的工作量。一个很好的例子是同时提供静态和动态内容的 web 服务器。因为静态内容不会改变(或很少改变),所以可以使用单独的服务器,甚至是一个内容交付网络 (CDN)来处理静态请求,让 Node 只处理动态内容。这种方法的好处是双重的。首先,Node 单线程的负载明显减轻。第二,静态内容可以通过专为静态数据优化的 CDN 或服务器传输。在多个服务器之间分配负载的一种常见方式是使用反向代理服务器。

也许现代计算中应用扩展的最好例子是。云计算提供按需应用扩展,同时将应用分发到世界各地的多个位置。两个比较流行的 Node.js 云计算平台是 Heroku 和 Nodejitsu。这两个平台都允许您将 Node 应用部署到云中,同时指定用于处理流量的进程数量。

本章探讨了扩展 Node 应用的各种技术。本章首先检查了在单台机器上进行扩容的cluster模块。从这里开始,本章继续讨论通过使用反向代理服务器进行扩展。最后,本章最后展示了如何使用 Heroku 和 Nodejitsu 将应用部署到云中。

cluster模块

核心cluster模块允许单个应用被分成多个进程。这些进程彼此独立运行,但可以共享端口,以平衡传入连接的负载。为了演示cluster是如何工作的,让我们从一个简单的 HTTP 服务器开始,如清单 16-1 所示。对于任何请求,服务器在返回一个200状态代码和消息"Hello World!"之前显示其进程 ID 和请求的 URL。

清单 16-1 。一个非常简单的 Hello World HTTP 服务器

var http = require("http");

http.createServer(function(request, response) {
  console.log(process.pid + ":  request for " + request.url);
  response.writeHead(200);
  response.end("Hello World!");
}).listen(8000);

清单 16-1 中的服务器将总是在单个处理器内核上的单个进程中运行,无论如何。鉴于大多数现代机器至少有两个处理器,如果服务器的一个实例可以在每个可用的内核上运行就好了。请注意,我们不希望在一个内核上运行多个实例,因为这样做会因为需要不断的上下文切换而对性能产生负面影响。清单 16-2 展示了如何使用cluster模块来实现这一点。

清单 16-2 。清单 16-1 中的服务器使用cluster模块实现了

var http = require("http");
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;

if (cluster.isMaster) {
  for (var i = 0; i < numCPUs; i++) {
    console.log("Forking child");
    cluster.fork();
  }
} else {
  http.createServer(function(request, response) {
    console.log(process.pid + ":  request for " + request.url);
    response.writeHead(200);
    response.end("Hello World!");
  }).listen(8000);
}

清单 16-2 导入了clusteros核心模块,以及原始服务器中使用的http模块。os模块的cpus()方法 返回一个数组,包含当前机器上每个内核的详细信息。该数组的length属性决定了应用可用的内核数量。

后续的if语句检查cluster.isMaster的值,这是使用cluster模块时需要理解的最重要的事情。主流程用于派生子流程,也称为工作者。然后,子进程用于实现应用的真正功能。但是,每个分支的子进程都执行与原始主进程相同的代码。如果没有这个if语句,子进程将试图派生其他进程。通过添加if语句,主进程可以为每个内核派生一个子进程,而派生的进程(执行else分支)在共享端口 8000 上实现 HTTP 服务器。

image 注意正如cluster.isMaster标识主进程一样,cluster.isWorker标识子进程。

The fork() Method

实际的流程分叉是使用cluster模块的fork()方法完成的。在引擎盖下,来自第 9 章child_process.fork()方法被调用。这意味着主进程和工作进程可以通过内置的 IPC 通道进行通信。cluster.fork()方法只能从主进程中调用。虽然没有在清单 16-2 中显示,fork()将一个可选对象作为它唯一的参数;该对象代表子进程的环境。fork()也返回一个cluster.Worker对象,可以用来与子进程交互。

当主进程试图派生一个新的 worker 时,会发出一个fork事件。一旦 worker 被成功分叉,它就向主进程发送一个online消息。收到该消息后,主机发出一个online事件。清单 16-3 中的例子展示了forkonline事件是如何在cluster应用中处理的。请注意,事件处理程序仅被添加到主流程中。虽然也可以将处理程序添加到工作进程中,但是这是多余的,因为事件只在主进程中发出。在本章的后面,您将学习如何侦听工作进程中的类似事件。

清单 16-3 。包含一个fork事件处理程序的cluster示例

var http = require("http");
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;

if (cluster.isMaster) {
  cluster.on("fork", function(worker) {
    console.log("Attempting to fork worker");
  });

  cluster.on("online", function(worker) {
    console.log("Successfully forked worker");
  });

  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  // implement worker code
}

更改默认的fork()行为

默认情况下,调用fork()会导致当前应用被分叉。然而,这种行为可以使用cluster.setupMaster()方法 来改变。setupMaster()接受一个设置对象作为它的唯一参数。可能的设置在表 16-1 中描述。清单 16-4 中的显示了setupMaster()的一个例子。在这个例子中,传递给setupMaster()的值是默认值,因此仍然可以观察到默认行为。

表 16-1 。setupMaster()支持的各种设置

|

环境

|

描述

| | --- | --- | | exec | 表示要派生的工作文件的字符串。默认为__filename。 | | args | 传递给工作线程的字符串参数数组。默认为当前的process.argv变量,减去前两个参数(Node 应用和脚本)。 | | silent | 一个布尔值,默认为false。当false时,worker 的输出被发送到 master 的标准流。当true出现时,工人的输出被静音。 |

清单 16-4 。一个使用setupMaster()设置默认值的cluster示例

var http = require("http");
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;

if (cluster.isMaster) {
  cluster.setupMaster({
    exec: __filename,
    args: process.argv.slice(2),
    silent: false
  });

  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  // implement worker code
}

disconnect()

disconnect()方法导致所有工作进程优雅地终止它们自己。一旦所有工作线程都终止了,如果事件循环中没有其他事件,主进程也可以终止。disconnect()接受一个可选的回调函数作为它唯一的参数。它是在所有的工人都死了之后调用的。在清单 16-5 中显示了一个使用disconnect()分叉然后立即终止工人的例子。

清单 16-5 。使用disconnect()终止所有工人的cluster示例

var http = require("http");
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;

if (cluster.isMaster) {
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.disconnect(function() {
    console.log("All workers have disconnected");
  });
} else {
  // implement worker code
}

当子进程自行终止时,它将关闭其 IPC 通道。这导致在主进程中发出一个disconnect事件。一旦子进程完全终止,主进程中就会发出一个exit事件。清单 16-6 显示了这些事件在主进程中是如何处理的。两个事件处理程序都将有问题的工人作为参数。注意,exit处理程序也接受codesignal参数。这些是退出代码和终止进程的信号的名称。但是,如果工作线程异常退出,则可能不会设置这些值。因此,已经从worker对象本身获得了工人的退出代码。

清单 16-6 。处理disconnectexit事件的示例

var http = require("http");
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;

if (cluster.isMaster) {
  cluster.on("disconnect", function(worker) {
    console.log("Worker " + worker.id + " disconnected");
  });

  cluster.on("exit", function(worker, code, signal) {
    var exitCode = worker.process.exitCode;

    console.log("Worker " + worker.id + " exited with code " + exitCode);
  });

  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.disconnect();
} else {
  // implement worker code
}

在崩溃后,exit事件对于重启一个工作器非常有用。例如,在清单 16-7 的中,当发出一个exit事件时,主机试图确定是否发生了崩溃。在这个例子中,我们假设所有工人退出都是崩溃。当检测到崩溃时,fork()被再次调用来替换崩溃的工人。

清单 16-7 。重启崩溃的工作进程的示例

var http = require("http");
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;

if (cluster.isMaster) {
  cluster.on("exit", function(worker, code, signal) {
    // determine that a crash occurred
    var crash = true;

    if (crash) {
      console.log("Restarting worker");
      cluster.fork();
    }
  });

  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  // implement worker code
}

The workers Object

主进程可以通过遍历workers对象(模块cluster的一个属性)来遍历它的所有工作进程。清单 16-8 展示了如何使用for...in循环和cluster.workers对象循环所有分叉的工人。在这个例子中,通过调用每个工人的kill()方法,分叉的工人被立即终止。

清单 16-8 。一个循环并杀死所有分叉工人的例子

var http = require("http");
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;

if (cluster.isMaster) {
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  for (var id in cluster.workers) {
    console.log("Killing " + id);
    cluster.workers[id].kill();
  }
}

image cluster.workers仅在主流程中可用。然而,每个工作进程可以通过cluster.worker属性引用自己的worker对象。

The Worker Class

Worker类用于与分叉的进程交互。在主流程中,可以通过cluster.workers访问单个工人。对于单个工人来说,可以通过cluster.worker引用Worker类。每个工作进程被分配一个惟一的 ID(不同于它的进程 ID),这个 ID 可以通过Workerid属性获得。由child_process.fork()创建的ChildProcess对象也可以通过Workerprocess属性获得。有关ChildProcess类的更多信息,参见第 9 章Worker类还包含一个send()方法,用于进程间通信,与ChildProcess.send()相同(process.send()也可以在工作进程内部使用)。正如您已经在清单 16-8 中看到的,Worker类也包含一个kill()方法,用于向工作进程发送信号。默认情况下,信号名称被设置为字符串SIGTERM,但是任何其他信号名称都可以作为参数传入。

Worker类也包含一些与cluster模块相同的方法和事件。例如,disconnect()方法和几个事件如清单 16-9 所示。这个例子为每个工人附加了事件监听器,然后调用Workerdisconnect()方法。值得指出的是,在Worker级别与这些特性有一些细微的区别。例如,disconnect()方法只断开当前工作线程,而不是所有工作线程。此外,事件处理程序不像在cluster级别那样将Worker作为参数。

清单 16-9Worker-级事件和disconnect()方法

var http = require("http");
var cluster = require("cluster");
var numCPUs = require("os").cpus().length;
var worker;

if (cluster.isMaster) {
  for (var i = 0; i < numCPUs; i++) {
    worker = cluster.fork();

    worker.on("online", function() {
      console.log("Worker " + worker.id + " is online");
    });

    worker.on("disconnect", function() {
      console.log("Worker " + worker.id + " disconnected");
    });

    worker.on("exit", function(code, signal) {
      console.log("Worker " + worker.id + " exited");
    });

    worker.disconnect();
  }
} else {
  // implement worker code
}

跨机器扩展

使用cluster模块,您可以更有效地利用现代硬件。但是,你还是受限于单机的资源。如果您的应用接收到大量流量,最终您将需要扩展到多台机器。这可以使用一个反向代理服务器来完成,该服务器在多个服务器之间对传入的请求进行负载平衡。反向代理代表客户端从一个或多个服务器检索资源。通过使用反向代理和多个应用服务器,应用可以处理的流量增加了。有许多可用的反向代理,但是本节特别关注两个— http-proxynginx

http-proxy

我们将在后面讨论的 Nodejitsu 开发了http-proxy,这是一个用于在 Node 应用中实现代理服务器和反向代理服务器的开源模块。http-proxy支持 WebSockets 和 HTTPS 等,并通过在nodejitsu.com的生产部署进行了全面测试。选择http-proxy还可以让您保持用 JavaScript 编写整个服务器堆栈,如果您愿意的话。

为了演示一个包含负载平衡反向代理的解决方案,我们必须首先创建应用服务器,如清单 16-10 所示。应用服务器负责为反向代理请求的内容提供服务。这与清单 16-1 中的基本 HTTP 服务器相同,适用于从命令行读取端口号。

清单 16-10 。一个简单的 Hello World Web 服务器,它从命令行读取端口

var http = require("http");
var port = ∼∼process.argv[2];

http.createServer(function(request, response) {
  console.log(process.pid + ":  request for " + request.url);
  response.writeHead(200);
  response.end("Hello World!");
}).listen(port);

运行 HTTP 服务器的两个独立实例,一个监听端口 8001,另一个监听端口 8002。接下来,创建反向代理,如清单 16-11 所示。从安装http-proxy模块开始。清单 16-11的第一行导入了http-proxy模块。第二行定义了请求可以代理到的服务器阵列。在实际的应用中,这些信息可能来自配置文件,而不是硬编码的。接下来,createServer()方法用于定义反向代理的行为,该方法应该熟悉 HTTP。示例服务器通过维护一组服务器以循环方式代理请求。当请求进来时,它们被代理到阵列中的第一个服务器。然后,该服务器被推到数组的末尾,以允许下一个服务器处理请求。

清单 16-11 。基于http-proxy模块的反向代理服务器

var proxyServer = require("http-proxy");
var servers = [
  {
    host: "localhost",
    port: 8001
  },
  {
    host: "localhost",
    port: 8002
  }
];

proxyServer.createServer(function (req, res, proxy) {
  var target = servers.shift();

  console.log("proxying to " + JSON.stringify(target));
  proxy.proxyRequest(req, res, target);
  servers.push(target);
}).listen(8000);

当然,前面的例子只使用了一台机器。但是,如果您可以访问多台机器,您可以在一台机器上运行反向代理,而一台或多台其他机器运行 HTTP 服务器。您可能还想在代理服务器中添加处理静态资源的代码,比如图像和样式表,或者甚至一起添加另一个服务器。

nginx

使用 Node 反向代理很好,因为它让你的软件栈保持相同的技术。然而,在生产系统中,更常见的是使用nginx来处理负载平衡和静态内容。nginx是一个开源的 HTTP 服务器和反向代理,非常擅长服务静态数据。因此,nginx可用于处理诸如缓存和服务静态文件等任务,同时将动态内容请求转发到 Node 服务器。

要实现负载平衡,只需安装nginx,然后在服务器配置文件中添加 Node 服务器作为上游资源。配置文件位于{nginx-root}/conf/nginx.conf,其中{nginx-root}nginx根安装目录。整个配置文件如清单 16-12 所示;然而,我们只对几个关键部分感兴趣。

清单 16-12 。一个将 Node 服务器列为上游资源的nginx配置文件

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    upstream node_app {
      server 127.0.0.1:8001;
      server 127.0.0.1:8002;
    }

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location /foo {
          proxy_redirect off;
          proxy_set_header   X-Real-IP            $remote_addr;
          proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
          proxy_set_header   X-Forwarded-Proto $scheme;
          proxy_set_header   Host                   $http_host;
          proxy_set_header   X-NginX-Proxy    true;
          proxy_set_header   Connection "";
          proxy_http_version 1.1;
          proxy_pass         http://node_app;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location  \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location  \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location  /\.ht {
        #    deny  all;
        #}
    }

    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

    # HTTPS server
    #
    #server {
    #    listen       443;
    #    server_name  localhost;

    #    ssl                  on;
    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_timeout  5m;

    #    ssl_protocols  SSLv2 SSLv3 TLSv1;
    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers   on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

如前所述,我们只对配置文件的一小部分感兴趣。第一个有趣的部分,您必须添加到您的配置文件中,如清单 16-13 中的所示,它定义了一个名为node_app的上游服务器,它在两个 IP 地址之间保持平衡。当然,这些 IP 地址会根据服务器的位置而有所不同。

清单 16-13 。名为node_app的上游资源在两个服务器之间保持平衡

upstream node_app {
  server 127.0.0.1:8001;
  server 127.0.0.1:8002;
}

简单地定义上游服务器并不能告诉nginx如何使用资源。因此,我们必须使用清单 16-14 中所示的指令定义一条路线。使用这个路由,对/foo的任何请求都被代理到上游的一个 Node 服务器。

清单 16-14 。定义反向代理到上游服务器的路由

location /foo {
  proxy_redirect off;
  proxy_set_header   X-Real-IP            $remote_addr;
  proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
  proxy_set_header   X-Forwarded-Proto $scheme;
  proxy_set_header   Host                   $http_host;
  proxy_set_header   X-NginX-Proxy    true;
  proxy_set_header   Connection "";
  proxy_http_version 1.1;
  proxy_pass         http://node_app;
}

安装和配置nginx已经超出了本书的范围。事实上,有整本书都是献给nginx的。这个非常简短的介绍只是为了给你指明正确的方向。你可以在www.nginx.org的项目主页上找到更多关于nginx的信息。

在云中扩展

计算资源越来越被视为商品。云计算提供商允许服务器在几秒钟内启动和关闭,以适应流量高峰。这些服务器可以在地理上分布在世界各地,最好的是,您通常只需为您实际使用的计算时间付费。有许多公共云提供商可供选择,但是本节特别关注 Nodejitsu 和 Heroku。本节介绍使用这些平台部署 Node 应用的基础知识。

Nodejitsu

Nodejitsu 成立于 2010 年 4 月,是一家总部位于纽约市的平台即服务(PaaS)公司。Nodejitsu 提供了一组命令行工具,用于将应用部署到他们的云中。要开始使用 Nodejitsu,您必须首先在www.nodejitsu.com注册一个帐户。尽管注册是免费的,但部署应用却不是。Nodejitsu 将为您提供 30 天的免费试用,但之后您必须每月支付至少 9 美元(在撰写本文时)来托管您的应用。

注册后,您需要安装 Nodejitsu 的命令行工具,可以使用命令npm install -g jitsu安装jitsu. jitsu。在帐户创建过程中,您将收到一封电子邮件,其中包含创建jitsu帐户的说明。这些指令包括一个类似于清单 16-15 中所示的命令。输入通过电子邮件发送给您的命令后,将创建您的帐户,并提示您创建帐户密码。

清单 16-15 。确认jitsu账户的通用命令

$ jitsu users confirm username confirmation_code

接下来,像平常一样创建一个 Node 应用。出于这个例子的目的,简单地使用来自清单 16-1 的 HTTP 服务器。要将项目部署到 Nodejitsu,它必须包含一个package.json文件。如果您需要复习package.json文件,请参见第 2 章。接下来,从你的应用目录中发出清单 16-16 所示的命令。

清单 16-16 。使用jitsu部署项目

$ jitsu deploy

如果您的项目不包含package.json文件,jitsu将通过一个简短的向导为您创建一个文件。package.json文件应该包括nameversionscriptsenginessubdomain字段。engines字段应该包含一个node字段来指定所需的 Node 版本。类似地,scripts字段应该包含一个start脚本,以便 Nodejitsu 知道如何初始化您的应用。subdomain将在您的应用的 URL 中使用,并且必须是唯一的。适用于jitsu部署的示例package.json文件如清单 16-17 所示。请注意,本例中显示的subdomain包括一个用户名(cjihrig)来帮助确保字符串是惟一的。

清单 16-17 。适合 Nodejitsu 部署的示例文件package.json

{
  "name": "simple-server",
  "subdomain": "simpleserver.cjihrig",
  "scripts": {
    "start": "simple-server.js"
  },
  "version": "0.0.1",
  "engines": {
    "node": "0.10.x"
  }
}

如果一切配置正确,并且您想要的子域可用,您的应用将被部署到 Nodejitsu 的云中。要访问您的应用,请访问http://subdomain.jit.su,其中subdomain是在package.json文件中找到的值。

Heroku

Heroku 是一家 PaaS 公司,成立于 2007 年,2010 年被Salesforce.com收购。与 Nodejitsu 不同,Heroku 并不严格地专用于 Node。它支持 Ruby、Java、Scala 和 Python 等语言。为了将 Node 应用部署到 Heroku,您需要一个 Heroku 用户帐户。注册 Heroku 是免费的,与 Nodejitsu 不同,Heroku 为小型单核应用提供免费托管。

首先在本地机器上安装 Heroku Toolbelt。你可以从 Heroku 的网站www.heroku.com下载工具箱。一旦安装好工具带,使用清单 16-18 中的命令登录 Heroku。输入登录命令后,系统会提示您输入 Heroku 凭证和 SSH 密钥。

清单 16-18 。从命令行登录 Heroku

$ heroku login

接下来,像平常一样编写应用。与 Nodejitsu 一样,您的应用将需要一个package.json文件,因为 Heroku 将使用它来安装您的应用。需要注意的一点是,Heroku 将为您的应用分配一个端口号,不管您在代码中指定了什么。端口号将从命令行传入,您必须考虑这一点。清单 16-19 展示了这是如何完成的。注意,如果环境中没有指定端口,那么使用||操作符来选择端口。这使得代码既可以在本地运行,也可以在 Heroku 上运行。

清单 16-19 。通过环境变量选择端口号

var port = process.env.PORT || 8000;

接下来,创建一个ProcfileProcfile是一个位于应用根目录下的文本文件,其中包含用于启动程序的命令。假设你的程序存储在一个名为app.js的文件中,清单 16-20 显示了一个例子ProcfileProcfileweb部分表示应用将连接到 Heroku 的 HTTP 路由堆栈并接收 web 流量。

清单 16-20 。一个 Heroku Procfile的例子

web: node app.js

接下来,将您的应用文件、package.jsonProcfile和任何其他需要的文件添加到git存储库中。这是必需的,因为 Heroku 使用git进行部署。使用清单 16-21 中的命令可以创建一个新的git库。这假设您已经在本地安装了git

清单 16-21 。为您的应用创建一个git库的命令

$ git init
$ git add .
$ git commit -m "init"

下一步是创建 Heroku 应用。这是使用清单 16-22 中的命令完成的。您可能想要用您想要的应用名称替换app_name

清单 16-22 。用于创建 Heroku 应用的命令

$ heroku apps:create app_name

最后一步是使用清单 16-23 中的命令部署您的应用。该命令将您的代码推送到 Heroku 进行部署。一旦你的代码被部署,你可以在http://app_name.herokuapp.com访问你的应用,这里app_name是你的应用的名字。

清单 16-23 。用于部署 Heroku 应用的命令

$ git push heroku master

摘要

本章介绍了扩展 Node.js 应用的各种技术。我们从探索cluster模块开始,尽管 JavaScript 是单线程的,它允许应用利用现代机器提供的所有内核。接下来,我们转向反向代理服务器,它允许应用跨多台机器伸缩。本章讨论的反向代理可以与cluster模块结合使用,以利用多个内核和多台机器。最后,本章最后探讨了云中的 Node.js。我们研究了两个流行的 PaaS 提供商——node jitsu 和 Heroku。

本章总结了我们对 Node.js 生态系统的探索。我们真诚地希望你通过阅读这本书学到了很多东西。我们知道通过写它我们学到了很多。不过,这本书还没有完全完成。请继续阅读关于 JavaScript 对象符号(JSON) 的入门/复习资料。