返回介绍

4.5 Rack 与 Unicorn

发布于 2023-05-19 13:36:37 字数 20120 浏览 0 评论 0 收藏 0

Web 应用程序服务器主要由 HTTP 服务器与 Web 应用程序框架构成。说起 HTTP 服务器,Apache 是很有名的一个,但除此之外还有其他很多种,例如高性能的新型轻量级服务器 nginx、以纯 Ruby 实现并作为 Ruby 标准组件附带的 WEBrick,以及以高速著称的 Mongrel 和 Thin 等。

此外,Web 应用程序框架方面,除了鼎鼎大名的 Ruby on Rails 之外,还出现了如 Ramaze、Sinatra 等“后 Rails”框架。于是,对于 Web 框架来说,就必须要对所有的 HTTP 服务器以及应用程序服务器提供支持,这样的组合方式真可为多如牛毛。

为了解决如此多的组合,出现了一种叫 Rack 的东西(图 1)。Rack 是在 Python 的 WSGI 1的影响下开发的用于连接 HTTP 服务器与框架的库。HTTP 服务器一端,只需对 Rack 发送请求,然后接受响应并进行处理,就可以连接所有支持 Rack 的框架。同样地,在框架一端也只需要提供对 Rack 的支持,就可以支持大多数 HTTP 服务器了。最近以 Ruby 为基础的 Web 应用程序框架,包括 Rails 在内,基本上都已经支持 Rack 了。

1 WSGI:Web Server Gateway Interface(Web 服务器网关接口)的缩写,是 Python 中使用的一种 HTTP 服务器与 Web 应用程序框架之间的通用接口。(原书注)

图 1 Web 应用程序服务器架构

Rack 的基本原理,是将对 Rack 对象发送 HTTP 请求的“环境”作为参数来调用 call 方法,并将以返回值方式接收的请求组织成 HTTP 请求。Rack 的 Hello World 程序如图 2 所示。

class HelloApp
  def call(env)
    [200, {"Content-Type" => "text/plain"},
    ["Hello, World"]]
  end
end
图 2 Hello World Rack 应用程序

Rack 对象所需的要素包括下面两个:

· 带有一个参数的 call 方法。call 方法在调用时,其参数为表示请求环境的 Hash。

· call 方法返回带 3 个元素的数组。第 1 个元素为状态代码(整数),第 2 个元素为表示报头的 Hash,第 3 个元素为数据本体(字符串数组)。

像 Rack 专用类等特殊的数据结构是不需要的。

require 'rubygems'
require 'rack'
require 'hello'

run HelloApp.new
图 3 配置文件 hello.ru

好了,为了看看 Rack 应用程序实际是如何工作的,我们将图 2 的程序保存到“hello.rb”这个文件中,并另外准备一个名为“hello.ru”的配置文件(图 3)。hello.ru 虽然说是一个配置文件,但其实体只是一个单纯的 Ruby 程序而已。准备好 hello.ru 文件之后,我们就可以使用 Rack 应用程序的启动脚本“rackup”来启动应用程序了。

$ rackup hello.ru
然后,我们只要用 Web 浏览器访问 http://localhost:9292/,就会显示出 Hello World 了。这次我们都用了默认配置,端口号为“9292”,HTTP 服务器则是用了“WEBrick”,但通过配置文件是可以修改这些配置的。

Rack 中间件

Rack 的规则很简单,就是将 HTTP 请求作为环境对象进行 call 调用,然后再接收响应。因此,无论是 HTTP 服务器还是框架都可以很容易地提供支持。

应用这样的机制,只要在实际对框架进行调用之前补充相应的 call,就可以在不修改框架的前提下,对所有支持 Rack 的 Web 应用程序增加具备通用性的功能。这种方式被称为“Rack 中间件”。

Rack 库中默认自带的 Rack 中间件如表 1 所示。

表1 Rack中间件

中 间 件

内 容

Rack::Auth::Basic

BASIC认证

Rack::Auth::Digest

Digest认证

Rack::Auth::OpenID

OpenID认证

Rack::Reloader

当Ruby脚本更新时重新加载

Rack::File

显示静态文件

Rack::Cascade

组合Web 应用,找不到文件时尝试下一个

Rack::Static

指定目录显示静态文件

Rack::Lint

检查应用是否符合Rack规范(开发用)

Rack::ShowExceptions

由应用产生的异常生成错误页面

Rack::ShowStatus

生成状态码400、500 用的错误页面

Rack::CommonLogger

生成Apache common log格式的日志

Rack::Recursive

用异常进行跳转

Rack::Session::Cookie

用cookie 进行会话管理

Rack::Session::Pool

用会话ID进行会话管理

中间件的使用可以通过在“.ru”文件中用“use”来进行指定。例如,如果要对 Web 应用添加显示详细日志、产生异常时声称生成错误页面以及显示错误状态页面的功能,可以将图 3 的 hello.ru 文件改写成图 4 这样。每个功能的添加只需要一行代码就可以完成,可见其表述力非常优秀。

require 'rubygems'
require 'rack'
require 'hello'

use Rack::CommonLogger
use Rack::ShowExceptions
use Rack::ShowStatus

run HelloApp.new
图 4 hello.ru(使用中间件)

应用程序服务器的问题

正如上面所讲到的,只要使用 Rack,HTTP 服务器与 Web 框架就可以进行自由组合了。这样一来,我们可以根据情况选择最合适的组合,但如果网站的流量达到一定的规模,更常见的做法是将 Apache 和 nginx 放在前端用作负载均衡,而实际的应用程序则通过 Thin 和 Mongrel 进行工作(图 5)。

图 5 Web 应用程序架构

其中,Apache(或者 nginx)负责接收来自客户端的请求,然后将请求按顺序转发给下属的 Thin 服务器,从而充分利用 I/O 等待等情况所产生的空闲时间。此外,最近的服务器大多都安装了多核 CPU,像这样用多个进程分担工作的架构则可以最大限度地利用多核的性能。

Thin 是一种十分快速的 HTTP 服务器,在大多数情况下,这样的架构已经足够了。但在某些情况下,这种架构也会发生下面这些问题。

· 响应缓慢

· 内存开销

· 分配不均衡

· 重启缓慢

· 部署缓慢

下面我们来具体看看这些问题的内容。

1. 响应缓慢

由于应用程序的 bug,或者数据库的瓶颈等原因,应用程序的响应有时候会变得缓慢。虽然这是应用方面的问题,HTTP 服务器是没有责任的,但这样的问题导致超时是成为引发更大问题的元凶。

为了避免这样的问题对其他的请求产生过大的负面影响,默认情况下 Thin 会停止 30 秒内没有响应的任务并产生超时。但不幸的是,不知道是不是 Ruby 在线程实现上的关系,这个超时的机制偶尔会失灵。

2. 内存开销

有些情况下,负责驱动 Web 应用程序的 Thin 等服务器程序的内存开销会变得非常大。这种内存开销的增加往往是由于数据库连接未释放,或者垃圾回收机制未能回收已经死亡的对象等各种原因引发的。

无论如何,服务器上的内存容量总归是有限的,如果内存开销产生过多的浪费,就会降低整体的性能。

和响应缓慢一样,内存开销问题也可能会引发其他的问题。内存不足会导致处理负担增加,处理负担增加会导致其他请求响应变慢,响应变慢又会导致用户不断尝试刷新页面,结果让情况变得更加糟糕。一旦某个环节出现了问题,就会像“多米诺骨牌”一样产生连锁反应,这样的情况不算少见。

3. 分配不均衡

用 Apache 或 nginx 作为反向代理2,对多个 Thin 服务器进行请求分配的情况下,请求会由前端服务器按顺序转发给下属的 Thin 服务器。这种形态很像是上层服务器对下层的“发号施令”,因此又被称为推模式(push model)。

2 反向代理(reverse proxy)是指将代理服务器配置在 Web 服务器一侧的网络中,实现 Web 服务器的缓存、安全性、负载均衡等功能的技术,也可以指配置了这种技术的服务器本身。(原书注)

一般来说,在推模式下,HTTP 服务器将请求推送给下属服务器时,并不知道目标服务器的状态。原则上说,HTTP 服务器只是按顺序依次将请求转发给下属各服务器而已。

然而,当请求转发目标的服务器由于某些原因没有完成对前一个请求的处理时,被分配给这个忙碌服务器的请求就只能等待,而且前一个请求也不知道什么时候才能处理完毕,只能自认倒霉了。

4. 重启缓慢

像上面所说的情况,一旦对请求的处理发生延迟,负面影响就会迅速波及到很大的范围。当由于某些原因导致处理消耗的时间过长时,必须迅速对服务器进行重启。虽然 Thin 自带超时机制,但对于内存开销,以及基于 CPU 时间进行服务器状态监控,需要通过 Monit、God 等监控程序来完成。

这些程序会监控服务器进程,当发现问题时将进程强制停止并重新启动,但即便如此,重启服务依然不是一件简单的事。当监控程序注意到请求处理的延迟时,马上重启服务器进程,这时,框架和应用程序的代码需要全部重新加载,恢复到可以进行请求处理的状态至少需要几秒钟的时间。而在这段时间中,如果有哪个倒霉的请求被分配到这个正在重启的服务器进程,就又不得不进行长时间的等待了。

5. 部署缓慢

当需要对 Web 应用程序进行升级时,就必须重启目前正在运行的所有应用程序服务器。正如刚才所讲到的,仅仅是重启多个服务器进程中的一个,就会殃及到一些不太走运的请求,如果重启所有的服务器进程的话,影响就会更大。哪怕 Web 应用整体仅仅停止响应 10 秒钟,对于访问量很大的网站来说,也会带来超乎想象的损失。

有一种说法指出,对于网站从开始访问到显示出网页之间的等待时间,一般用户平均可以接受的极限为 4 秒。由于这个时间是数据传输的时间和浏览器渲染 HTML 时间的总和,因此 Web 应用程序用于处理请求的时间应尽量控制在 3 秒以内。

如果上述说法成立的话,那么目前这种在前端配置一个反向代理,并将请求推送给下属服务器的架构,虽然平常没有问题,但一旦发生问题,其负面影响就很容易迅速扩大,这的确可以说是一个缺点。

于是我们下面要介绍的,就是一个面向 UNIX 的 Rack HTTP 服务器——Unicorn。Unicorn 是以解决上述问题为目标而开发的高速 HTTP 服务器。之所以说是“面向 UNIX”的,是因为 Unicorn 使用了 UNIX 系操作系统所提供的 fork 系统调用以及 UNIX 域套接字,因此无法在 Windows 上工作。

Unicorn 的架构

Unicorn 系统架构如图 6 所示。这张图上使用的是 Apache,换成 nginx 也是一样的。

图 6 Web 应用程序架构

Unicorn 和图 5 中采用的 Thin 在架构上的区别在于:Apache 只需要通过 UNIX 域套接字和单一的 Master 进行通信。

在采用 Thin 的架构中,Apache 负责负载均衡,为下属各服务器分配请求,而在采用 Unicorn 的架构中,Apache 只需要和一个称为 Master 的进程进行通信即可。这种通信是通过 UNIX 域套接字来完成的。

一般的套接字都是通过主机名和端口号来指定通信对象,而 UNIX 域套接字则是通过路径来指定的。服务器端(数据的接收方)通过指定一个路径来创建 UNIX 域套接字时,在指定的路径就会生成一个特殊的文件。开始通信的一方只要像一般文件一样写入数据,在接收方看来就像是在通过套接字来进行通信一样。

UNIX 域套接字具有一些方便的特性:①客户端可以像文件一样来进行读写操作;②进程之间不具备父子关系也可以进行通信。不过它也有缺点,由于通信对象的指定是采用路径的形式,因此只能用于同一台主机上的进程间通信。

然而,对于多台主机的分布式环境,也有通过 Unicorn 进行负载均衡的需求,这种情况下也可以用 TCP 套接字来代替 UNIX 域套接字,虽然性能会有一定的下降。

由 Apache 转发给 Unicorn-Master 的请求,会转发给由 Master 通过 fork 系统调用启动的 Slave,而实际的处理会在 Slave 中完成。然后,Master 会在 Slave 处理完成之后,将响应转发给 Apache。

Unicorn 的解决方案

不过,这样的机制如何解决 Thin 等所遇到的问题呢?对于上面提到的那 5 个问题,我们逐一来看一看。

1. 响应缓慢

Web 应用响应慢,本质上说还是应用自身的问题,因此无法保证一定能够避免。对于服务器来说,重要的是,当问题出现时如何避免波及到其他无关的请求。

在简单的推模式中,转发请求的时候,并不会向被分配到请求的服务器确认其是否已经完成对上一个请求的处理,因此导致对其他无关请求的处理发生延迟。

相对地,在 Unicorn 中,完成处理的 Slave 会主动去获取请求,即拉模式(pull model),因此从原理上说,不会发生某个请求被卡死在一个忙碌的服务器进程中的情况。

不过,即便是在拉模式下,也并非完全不存在请求等待的问题。当访问量超出了 Slave 的绝对处理能力时,由于没有空闲的 Slave 能够向 Master 索取请求,于是新来的请求便不得不在 Master 中进行等待了。

如果由于某种原因导致 Slave 完全停止运行的情况下,由于可用的 Slave 少了一个,整体的处理能力也就随之下降了。在 Unicorn 中,对于这样的情况所采取的措施是,当发现某个 Slave 的处理超出规定时间则强制重启该 Slave。

2. 内存开销

和响应缓慢的问题一样,内存的消耗也会影响到其他的请求。因此,当发生问题时,最重要的是如何在不影响其他请求的前提下完成重启。由于 Unicorn 可以快速完成对 Slave 的重启,因此在可以比较轻松地应对内存消耗的问题,理由我们稍后再介绍。

3. 分配不均

正如之前所讲过的,在采用拉模式的 Unicorn 中,不会发生将请求分配给不可用的服务器进程的问题。在 Unicorn 中,来自 Apache 的请求会通过 UNIX 域套接字传递给单一的 UnicornMaster,而下属的 Slave 会在自己的请求处理完成之后向 Master 索取下一个请求。综上所述,在 Unicorn 中不会发生分配不均的问题。

4. 重启缓慢

采用拉模式来避免分配不均是 Unicorn 的一大优点,而另一大优点就是它能够快速重启。

Unicorn 对 Slave 进行重启时有两个有利因素。第一,由于采用了拉模式,因此即便重启过程中某个 Slave 无法工作,也不用担心会有任何请求分配到该 Slave 上,这样一来,整体上来看就不会发生处理的停滞。

第二,Unicorn 在 Slave 的启动方法上很有讲究,使得实际重启所花费的时间因此得以大大缩短。

当由于超时、内存开销过大等原因被监控程序强制终止,或者由于其他原因导致 Slave 进程停止时,Master 会注意到 Slave 进程停止工作,并立即通过 fork 系统调用创建自身进程的副本,并使其作为新 Slave 进程来启动。由于 Unicorn-Master 在最开始启动时就已经载入了包括框架和应用程序在内的全部代码,因此在启动 Slave 时只需要调用 fork 系统调用,并将 Slave 用的处理切换到子进程中就可以了。

在最近的 UNIX 系操作系统中,都具备了“Copy-on-Write”(写时复制)3功能,从而不需要在复制进程的同时复制内存数据。只需要在内核中重新分配一个表示进程的结构体,该进程所分配的内存空间就可以与调用 fork 系统调用的父进程实现共享。随着进程的执行,当实际发生对内存数据的改写时,仅将发生改写的内存页进行复制,也就是说,对内存的复制是随着进程执行的过程而循序渐进的,这样一来,基本上就可以避免因内存复制的延迟导致的 Slave 启动开销。

3 Copy-on-Write:是指在创建子进程时不复制父进程的内存空间,而是当内存数据发生写入时再进行复制的机制。(原书注)

Thin 等应用程序服务器的重启过程则更为复杂。首先,需要启动 Ruby 解释器,然后还要重新载入框架和应用程序代码。相比之下,运用了 Unicorn 系统中的 Slave 的重启时间要短得多,这样一来,就可以毫不犹豫地重启发生问题的 Slave。此外,由于恢复工作可以快速完成,也可以避免系统整体响应产生延迟。

5. 部署缓慢

Slave 重启的速度很快,也就意味着需要服务器整体重启的部署工作也可以快速完成。此外,在 Unicorn 中,针对缩短部署时间还进行了其他一些优化。当 Unicorn-Master 进程收到 USR2 信号(稍后详述)时,Master 会进行下述操作步骤:

1. 启动新 Master

Master 在收到 USR2 信号后,会启动 Ruby 解释器,运行一个新 Master 进程。

2. 重新加载

新 Master 载入框架和应用程序代码。这个过程和 Thin 的重启一样,需要消耗一定的时间。但在这个过程中,旧 Master 依然在工作,因此没有任何问题。

3. 启动 Slave

新 Master 通过 fork 系统调用启动 Slave,这样一来一个新版本的 Web 应用就准备完毕,可以提供服务了。

当新 Master 启动第一个 Slave 时,该 Slave 如果检测到存在旧 Master,则对其进程发送“QUIT”信号,命令旧 Master 结束进程。

然后,新 Master 开始运行新版本的应用程序。此时,旧 Master 依然存在,但服务的切换工作本身已经完成了。

4. 旧 Master 结束

收到 QUIT 信号的旧 Master 会停止接受请求,并对 Slave 发出停止命令。旧 Slave 继续处理现存的请求,并在处理完毕后结束运行。当确认所有 Slave 结束后,旧 Master 本身也结束运行。到此为止,Unicorn 整体重启过程就完成了,而服务停止的时间为零。

6. 信号

在 Unicorn 重启的部分我们提到了“信号”这个概念。对于 UNIX 系操作系统不太了解的读者可能看不明白,信号也是 UNIX 中进程间通信的手段之一,但信号只是用于传达某种事件发生的通知而已,并不能随附其他数据。

信号的种类如表 2 所示,用 kill 命令可以发送给进程。

$ kill -QUIT <进程ID>

表2 UNIX信号一览表(具有代表性的)

名 称

功 能

默认动作

HUP

挂起

Term

INT

键盘中断

Term

QUIT

键盘终止

Core

ILL

非法命令

Core

ABRT

程序的 abort

Core

FPE

浮点数异常

Core

KILL

强制结束(不可捕获)

Term

SEGV

非法内存访问

Core

BUS

总线错误

Core

PIPE

向另一端无连接的管道写入数据

Term

ALRM

计时器信号

Term

TERM

结束信号

Term

USR1

用户定义信号1

Term

USR2

用户定义信号2

Term

CHLD

子进程暂停或结束

Ign

STOP

进程暂停(不可捕获)

Stop

CONT

进程恢复

Cont

TSTP

来自 tty 的 stop

Stop

TTIN

后台 tty 输入

Stop

TTOU

后台 tty 输出

Stop

当 kill 命令中没有指定信号名时,则默认发送 INT 信号。在程序中则可以使用 kill 系统调用来发送信号,Ruby 中也有用于调用 kill 系统调用的 Process.kill 方法。

这些信号根据各自的目的都设有默认的动作,默认动作根据不同的信号分为 Term(进程结束)、Ign(忽略信号)、Core(内核转储)、Stop(进程暂停)、Cont(进程恢复)5 种。如果程序对于信号配置了专用的处理过程(handler),则可以对这些信号进行特殊处置。不过,KILL 信号和 STOP 信号是无法改变处理过程的,因此即便因某个软件中配置了特殊的处理过程而无法通过 TERM 信号来结束,也可以通过发送 KILL 信号来强制结束。

信号原本是为特定状况下对操作系统和进程进行通知而设计的。例如,在终端窗口中按下 Ctrl+C,则会对当前运行中的进程发送一个 SIGINT。

然而,信号不光可以用来发送系统通知,也经常用来从外部向进程发送命令。在这些信号中,已经为用户准备了像 USR1 和 USR2 这两种可自定义的信号。

Unicorn 中也充分运用了信号机制。刚才我们已经讲到过,向 Slave 发送 QUIT 信号可以使其结束。Master/Slave 对于各个信号的响应方式如表 3 所示,其中有一些信号的功能看起来被修改得面目全非(比如 TTIN),这也算是“UNIX 流派”所特有的风格吧。

表3 Unicorn的信号响应

Master

信 号

动 作

HUP

重新读取配置文件,重新载入程序

INT/TERM

立刻停止所有Slave

QUIT

向所有 Slave 发送QUIT信号,等待请求处理完毕后结束

USR1

重新打开日志

USR2

系统重启。重启完成后当前Master会收到QUIT信号

TTIN

增加一个Slave

TTOU

减少一个Slave

Slave

信 号

动 作

INT/TERM

立即停止

QUIT

当前请求处理完毕后结束

USR1

重新打开日志

信号还可以通过 Shell 或者其他程序来发送,因此,编写一个用于从外部重启 Unicorn 的脚本也是很容易的。

性能

http://github.com/blog/517-unicorn 专栏中,对 Unicorn 的性能与 Mongrel 进行了比较。根据这篇评测,无调优默认状态下的 Unicorn,性能已经稍优于 Mongrel 了。尽管 Thin 比 Mongrel 的性能更好一些,但 Unicorn 决不会甘拜下风的。

考虑到 Unicorn 几乎全部是用 Ruby 编写的(除 HTTP 报头解析器外)这一点,可以说是实现了非常优秀的性能。此外,从刚才所介绍的 Unicorn 的特点来看,在大多数情况下,用 Unicorn 来替代 Mongrel 和 Thin 还是有一定好处的。

不过,Unicorn 也并非万能。Unicorn 只适合每个请求处理时间很短的应用,而对于应用程序本身来说,在外部(如数据库查询等)消耗更多时间的情况,则不是很适合。

对于 Unicorn 来说,最棘手的莫过于像 Comet 4这种,服务器端基本处于待机状态,根据状况变化推送响应的应用了。在 Unicorn 中,由于每个请求需要由一个进程来处理,这样会造成 Slave 数量不足,无法满足请求的处理,最终导致应用程序整体卡死。对于这样的应用程序,应该使用其他的一些技术,使得通过少量的资源就能够接受大量的连接。

4 Comet:一种从 Web 服务器向 Web 客户端发送数据的推送(push)技术。(原书注)

为了弥补 Unicorn 的这些缺点,又出现了一个名叫“Rainbows!”5的项目。在 Rainbows! 中,可以对 N 个进程分配 M 个请求,从而缓和大量的连接数和有限的进程数之间的落差。

5 Rainbows! 项目官方网站:http://rainbows.rubyforge.org/。(原书注)

策略

综上所述,Unicorn 的关键是,不是由 HTTP 服务器来主动进行负载均衡,而是采用了完成工作的 Slave 主动获取请求的拉模式。对于 Slave 之间的任务分配则通过操作系统的任务切换来完成。这个案例表明,在大多数情况下,与其让身为用户应用的 HTTP 服务器来进行拙劣的任务分配,还不如将这种工作交给内核这个资源管理的第一负责人来完成。

另一个关键是对 UNIX 系操作系统功能的充分利用。例如,通过 fork 系统调用以及背后的 Copy-on-Write 技术加速 Slave 的启动。UNIX 中最近才加入了线程功能,Unicorn 则选择不依赖线程,而是对已经“过气”的进程技术加以最大限度的充分利用。线程由于可以共享内存空间,从性能上来说比进程要更加有利一些。但反过来说,正是因为内存空间的共享,使得它容易引发各种问题。因此 Unicorn 很干脆地放弃了使用线程的方法。

如此,Unicorn 充分利用了 UNIX 系操作系统长年积累下来的智慧,在保持简洁的同时,提供了充分的性能和易管理性。

近年来,由于考虑到 C10K 问题(客户端超过一万台的问题)而采用事件驱动模型等,Web 应用程序也在用户应用程序的水平上变得越来越复杂。但 Unicorn 却通过将复杂的工作交给操作系统来完成,从而实现了简洁的架构。因为事件处理、任务切换等等本来就是操作系统所具备的功能。当然,仅凭 Unicorn 在客户端并发连接的处理上还是存在极限,如果请求数量过大也有可能处理不过来,但我们可以使用反向代理,将多个 Unicorn 系统捆绑起来,从而实现横向扩展(图 7)。

图 7 Unicorn 的横向扩展

小结

Unicorn 最大限度利用了 UNIX 的优点,同时实现了高性能和易管理性。此外,它采用了进程模式而非线程模式、拉模式而非推模式,通过追求实现的简洁,实现了优秀的特性,对于这一点我非常喜欢。今后,随着服务器端多任务处理的需求不断增加,像 Unicorn 这样简洁的方式会越来越体现其价值。

“云计算时代的编程”后记

首先,关于 HashFold 我想做一些补充。HashFold 从首次出现在我的专题连载中,到现在已经过了两年半的时间,它不但没有引起广泛关注,反倒是完全消亡了。对于使用 Hash 而非流(stream)的这个主意我觉得很有趣,但仅凭有趣还是无法推动潮流的吧。只不过,文章的内容本身,作为使用线程和进程来进行数据处理的实例来说,还是有足够的价值的,因此我还是决定将它放在这本书中。

现在反观 HashFold,在大量数据的处理上,比起运用 Hash 这样“容器”型数据结构的模型来说,我感觉“流”处理的方式在印象上要更好一些。此外,HashFold 真的要普及的话,最重要的是需要像 Hadoop 这样对 MapReduce 的高性能实现,而仅凭纸上谈兵恐怕是不会有什么结果的。

在思考今后“云计算时代的编程”这个话题的时候,本章中介绍的内容应该还会作为基础的技术继续存在下去,但程序员所看到的“表面”的抽象程度应该会越来越高。

今后,随着云计算的普及,节点的数量也会不断增加,对每个节点进行管理也几乎会变成一件不可能完成的事情。于是,节点就成了性能实现的一个“单位”,而作为一个单个硬件的节点概念则会逐步被忽略。在这样的环境中,恐怕不会再进行以显式指定节点的方式通信这样的程序设计了吧。说不定,在 Linda 这个系统中所提供的“黑板模型”会再次引起大家的关注。

这种模型是利用一块共享的黑板(称为 tuple space),先在上面写入信息,需要的人读取上面的信息并完成工作,再将结果写到黑板上。在 Ruby 中也利用 dRuby 提供了一个叫做 Rinda 的系统。

虽然 Linda 是 20 世纪 80 年代的一项古老的技术,但借着云计算的潮流,在这个领域中也不断要求我们温故而知新吧。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文