您可能想要将工作扩散到一个大型机器群体中,或者想要在不同语言和环境之间共享功能,那么开放源码的 Gearman 服务可以让您轻松地将工作分布到网络中的其他机器。本文将介绍 Gearman 的一些典型应用,以及它如何解决现代应用程序中的各种问题。还将学习 Gearman 如何与其他工具(比如说 memcached)结合使用,帮助加速您的应用程序和处理需求。
简介
现代计算环境最大的挑战之一就是工作分布和计算资源的有效利用。目前的一般趋势是,便宜且轻松地安 装一台强大的机器来执行比较直观和简单的任务,但是这可能得不到最佳的总体性能和机器的最佳利用。相反,很多应用程序现在发现自己需要执行一些相当小的操 作,但是可能要执行成千上万次,不需要一台强大的机器。
随着计算场(farm)的增大,有时候需要跨所有机器同时执行一个动作或操作,或者有选择地执行管理或安装任务。
有很多解决方案可用。例如,虚拟化是其中一种方式,采用此方式时,计算机会被分片,以得到最佳的性能,尽管这些方式也有其各自的问题和局限性。memcached 工具(它采用备用内存)和类似于网格的解决方案 —— IBM 云也支持计算能力的灵活分配,使计算能力可以被随处利用。
Gearman 采用与之不同的方法,它为跨一组机器分布离散的任务提供一种灵活的机制。利用此方法可以扩展及更加有效地利用系统,同时大大降低维护和支持此类系统的复杂 性。这衍生了很多解决方案,包括网格环境,甚至还有 web 服务基础,但是都利用一种实用的分布方法。这使得 Gearman 在网格环境或 Web 环境中很有用 —— 在这样环境中,您想要共享和分布工作及请求。它也可用于从 web、数据库和 Amazon 环境卸载任务和处理信息,这使得它非常适合于支持 IBM WebSphere® 或 Amazon 部署。
Gearman 基础
Gearman 服务有很多要素使得它不仅仅是一种提交和共享工作的方式,但是主要的系统只由三个组件组成:gearmand 守护进程(或作业服务器);用于向 Gearman 服务提交请求的客户机;执行实际工作的工人(参见图 1)。
图 1. Gearman 服务概要
Gearmand 服务器执行一个简单的功能,即从客户机收集工作请求并充当一个注册器(工人可以在此提交关于它们支持的工作和操作类型的信息)。在传统的网格服务中,网格 节点由管理服务器指派给特定的任务和给定的工作,在 Gearman 中与此不同,是由工人请求做工作。
这一重要区别意味着,关于哪个工人做哪项工作的决定不是必需的。在分布工作时,没有配置、优先次序或者需要的其他组织。相反,系统的特点是,自运行的和自管理的,对于很多安装,这里最初要描述的基本结构都不复杂。
Gearman 样例
了解 Gearman 如何工作最容易的方式是,尝试一个使用 Gearman 服务的简单应用程序。您将为本例中的工人和客户机要素使用 Perl(从 参考资料 下载),但是有必要指出来,您也可以混用及搭配多种语言以充分发挥特定环境的优势。
首先,必须让 gearmand 服务器运行起来。为此,您可以下载源代码、运行配置脚本并运行服务器;需要用到 libevent 库。很多 Linux® 发行版也具有执行安装的 Gearman 组件,无需手工处理(参见 参考资料,了解 libevent 和 Gearman)。
安装完成后,利用 -vv
命令行选项阅读服务,了解您开始使用时服务器在做什么。
对于客户机和工人组件,需要安装 Gearman CPAN 模块,其中包括 Gearman::Client
和 Gearman::Worker
模块,它们是您创建客户机和工人时需要用到的模块。
对于一个简单的工人,我们提供一个基本的单词计数功能。
清单 1. 简单的 Gearman Worker
use Gearman::Worker;
my $worker = Gearman::Worker->new;
$worker->job_servers('192.168.0.2:4730');
$worker->register_function('wordcount' => \&wordcount);
$worker->work while 1; sub wordcount
{
my ($input) = @_; my @words = split /\s+/,$input->arg; return (scalar @words);
}
第一行加载模块,然后您创建一个新的 Worker
对象。job_servers
方法然后注册一个或多个作业服务器,以后客户机会联系这些服务器以请求可用的工作。您已经指定了 IP 地址(也可以使用主机名)和端口号。由于用于 Gearman 的默认端口号改变了,所以这一点特别重要,这会导致系统不能工作的问题。明确地指定端口号可以避免此类问题。您可以指定多个作业服务器,从而具备弹性(不 止一个作业服务器意味着存在备用服务器,可以在初始服务器出现故障时派上用场),或者跨不同的服务器组分布具有相同任务的不同工作。
register_function()
方法与脚本中一个名字为任务名的本地函数相关联。任务名将由客户用来请求特定的操作。一个工人可以执行任意数量的任务,更为重要的是,任何工人可以执行不 同范围的任务。这使得您可以在已经注册为能够执行该任务的机器支持一个可分布执行的任务。由于是由工人请求其能够执行的任务中的工作,所以不会遇到任务被 委派给不能执行该任务的工人的问题。
最后一部分的 work()
方法打开工人,使之可以开始处理请求。本例中的 loop
条件意味着,工人将一直处理请求,直到程序终止。
脚本的其余部分是函数本身。在本例中,您采用客户机提供的输入作为第一个参数(使用 arg()
方法得到它的值),用空格隔开该值,然后返回数组中条目的个数。实际操作比描述起来要稍微麻烦一点。
在客户端,也有一些与此相同的基本步骤。您创建一个 Gearman Client
对象,指定服务器,调用您想要执行的任务。清单 2 展示得比较清楚。
清单 2. 创建一个 Gearman Client
对象
use Gearman::Client;
my $client = Gearman::Client->new;
$client->job_servers('192.168.0.2:4730'); my $result = $client->do_task('wordcount','the quick brown fox jumps over the lazy dog'); print "Words $$result\n";
do_task()
是 main
方法。它运行指定的任务(wordcount
)和参数数据。结果值被返回为一个引用,所以您必须对它解除引用,以得到您所请求的单词计数。
现在可以通过启动 gearmand 对之进行测试。如果使用三个 -v
选项,会得到关于注册和连接的信息,如清单 3 所示,其中展示了 gearmand 已启动,工人脚本已注册。
清单 3. 启动 gearmand
$ gearmand -vvv
INFO Starting up
INFO Listening on 0.0.0.0:4730 (6)
INFO Creating wakeup pipe
INFO Creating IO thread wakeup pipe
INFO Adding event for listening socket (6)
INFO Adding event for wakeup pipe
INFO Entering main event loop
INFO Accepted connection from 192.168.0.2:47158
INFO [ 0] 192.168.0.2:47158 Connected
INFO [ 0] 192.168.0.2:47158 Disconnected
INFO Accepted connection from 192.168.0.2:47159
INFO [ 0] 192.168.0.2:47159 Connected
INFO [ 0] 192.168.0.2:47159 Disconnected
INFO Accepted connection from 192.168.0.2:47160
INFO [ 0] 192.168.0.2:47160 Connected
通过用 Perl 运行脚本而启动工人,但是工人不产生任何输出。另一方面,客户机返回句子中的单词数量,如清单 4 中所示。
清单 4. 运行 Perl 脚本返回句子中的单词数量
$ perl client.pl
Words 9
这似乎就是得到一个简单的单词计数。但是请记住,处理工人的机器和发送请求的客户机可能在世界的不同位置,或者说在同一云的不同部分。
清单 5 展示了相同的输出,这次使用另外的机器作为客户机。要运行另外的脚本,您所需要的只有 Gearman 库、脚本模块和脚本本身。
清单 5. 使用另外的机器作为客户机时的相同输出
$ gearmand -vvv
INFO Starting up
INFO Listening on 0.0.0.0:4730 (6)
INFO Creating wakeup pipe
INFO Creating IO thread wakeup pipe
INFO Adding event for listening socket (6)
INFO Adding event for wakeup pipe
INFO Entering main event loop
INFO Accepted connection from 192.168.0.2:47158
INFO [ 0] 192.168.0.2:47158 Connected
INFO [ 0] 192.168.0.2:47158 Disconnected
INFO Accepted connection from 192.168.0.2:47159
INFO [ 0] 192.168.0.2:47159 Connected
INFO [ 0] 192.168.0.2:47159 Disconnected
INFO Accepted connection from 192.168.0.2:47160
INFO [ 0] 192.168.0.2:47160 Connected
INFO Accepted connection from 192.168.0.2:56545
INFO [ 0] 192.168.0.2:56545 Connected
INFO [ 0] 192.168.0.2:56545 Disconnected
INFO Accepted connection from 192.168.0.111:54307
INFO [ 0] 192.168.0.111:54307 Connected
INFO [ 0] 192.168.0.111:54307 Disconnected
要理解这有多么有用,可以考虑处理背后的灵活性。它似乎类似于另一种 web 服务功能一样的解决方案。但是,您应该有 20 个已注册的工人和一个客户机,客户机只请求其中一个工人来完成工作。不需要您选择哪个工人,也不需要使用复杂的负载平衡系统来替您进行决策。
跨环境使用 Gearman
gearmand 服务器是用 C 编写的,这使得它非常容易在很多 UNIX® 和 Linux 环境之间移植,并且它基于常见的开放源码工具(比如 GNU 配置和自动构建系统),这使得它在很多环境中可用和可部署。
客 户机和工人接口在大量语言(包括 Perl、PHP、Python 和 Java™ 编程语言)甚至 UNIX/Linux shell 中可用。此外,还有针对 Drizzle、MySQL 和 PostgreSQL 的 UDF,允许您利用 Gearman 在这些数据库中直接联系。
然而,您不仅仅局限于在相同工人和客户机接口之间通信。例如,您可以调用 Python 工人支持的任务,并从前面的 Perl 脚本请求信息。在清单 6 中可以看到用 Python 编写的相同单词计数脚本。
清单 6. 用 Python 编写的单词计数脚本
from gearman import GearmanWorker def wordcount(input):
words = input.arg.split(None)
print words
return len(words) worker = GearmanWorker(["127.0.0.1:4730"])
worker.register_function('wordcount', wordcount)
worker.work()
运行工人和客户机将得到相同的单词计数。
清单 7. 再次运行工人和客户机
$ perl client.pl
Words 9
优点是您可以从各种工具使用 Gearman 并将之集成到自己现有的应用程序中,不管使用的是何种环境。如果您的 Web 应用程序是用 PHP 编写的,但是想使用现有 WebSphere 环境中的应用程序功能,那么您可以通过 Gearman 来达此目的,方法是利用工人扩展 Java 技术,从 PHP 前端调用适当的任务。
需 要小心的一件事情是数据的共享。Gearman 不进行所交换数据的任何转换或操作。对于这里使用的简单字符串和整数没有问题,但是不能共享 PHP 中的数组值并期望能在 Java 语言中被理解。对于这种类型的交互,可以使用很多结构化数据标准中的一种,比如 JavaScript Object Notation (JSON) 或 XML。另外,如果您在处理来自数据库的信息,只要共享 ID 或者找到需要处理的数据时要用到的信息即可,或者使用 memcached 这样的透明方法(尽管可能仍然需要 JSON 或等价物)。
下面我们来看其他一些 Gearman 部署的例子。
在动态环境中部署 Gearman
当然,具有这样一个灵活的环境对于使用它是一个理想的环境,此时具有灵活的服务器云(例如 Amazon 的 EC2)或者现有的大规模基础设施(比如 Web 服务器场,这里有未被充分利用或者能够处理小型离散任务的机器)。
在云环境中使用时,您利用 Gearman 的灵活特点来运行可用的工人。将工人添加到 Gearman 系统所需要做的就是让工人脚本在引导时间执行。
所以请考虑 图 2 中的布局。您具有一个标准的 Gearman 环境,使用标准工人组来满足客户机的需求。当环境中的负载突然增加时,您可以引导 EC2 实例,运行工人脚本来注册它们完成工作的能力,并挑选和处理信息。因此,EC2 实例是临时的,您可以根据需要注册和注销实例。
图 2. 使用标准工人组的标准 Gearman 环境
如果有明确表达信息时涉及到的处理和准备,这才会真正有用。在本例中,您可以启动 EC2 实例,通过 Gearman 执行工作,比较响应,并关闭 EC2 实例。当您真正需要成百上千的机器来进行处理时,这可以节省资金。
在数据环境使用 Gearman
通常有这么一些情况,您会想要对一些信息执行某些操作,但是处理在时间上不是很紧急,或者数据源和目的地之间存在一些距离(在网络上,不是地理位置上)。
到目前为止,已经了解在想要或需要即时响应的时候使用 Gearman 的方式。Gearman 也能够初始化 background
进程。当前景客户机要求做工作而不担心何时返回响应时,就会发生这种情况。
考 虑这样一个 web 应用程序,它在注册过程中会发送一个注册 e-mail。当用户点击 web 窗体上的 Submit 按钮时实时发送这样的 e-mail,会有很多潜在的问题。问题到达邮件服务器或者只是在忙时提交 e-mail,都会延迟 web 应用程序。有了 Gearman,您可以将任务提交到队列,让您的一个工人完成 e-mail 的实际处理和格式化并发送,这可以让 web 接口做出即时响应。下面是一个很好的例子,演示了在前端不需要等待响应时分派一个 background 进程。
同样的原理也可适用于其他 时间不敏感的要素。例如,如果您在提供或使用 Twitter 集成,那么可以使用 Gearman 来处理到 Twitter 帐户的发布。在本例中,不需要内容是即时的,因而不会出现因执行内容发布而让应用程序的其余方面变慢的问题。Gearman 服务允许您返回一个失败状态,确保任务将被重新排队和重试。
您也可以使用 Gearman 来处理数据库和其他更新(更新的即时特点不是很重要,所以信息不必即时地写入数据库)。在这种情况下,可以充分利用现代 web 应用程序工具组中其他组件(比如 memcached)的优势。
有一个应用程序是很好的例子,它处理诸如此类的内容,比如说文档归档器,您想要从内容构建索引和其他信息。尽管已经有用于此类操作的传统队列可用,但是 Gearman 让在一组机器中扩散和分布信息变得更容易,提高了索引过程的性能。
有助于这些情况的一个要素是,与 memcached 组合使用 Gearman 的处理,以允许您提交数据,解析和处理数据,然后自动更新该信息的高速缓存版本。
在 博客或其他内容管理系统中,可以使用该方式来允许更新和信息的即时发布,方法是更新内容的 memcached 版本并在后台更新数据库,或者更新数据库并在写操作完成后马上更新 memcached 客户端版本以用于显示。通过减少同步写操作的次数,两种方案都有助于减少向数据库写入内容,同时提高前端应用程序的响应性。
在 清单 8 和 清单 9 中客户机和工人脚本的修改版本中,可以看到一个使用 memcached 的例子。客户机把要被计数的字符串写入 memcached,工人使用客户机提供的 ID 读取字符串,统计单词数量,然后将信息写回 memcached。本例中使用了一个硬编码的 ID,但是您也可以使用来自数据库的 ID 或 UUID。
清单 8. 基于 memcached 的客户机
use Gearman::Client;
use Cache::Memcached; # Set up memcached my $cache = new Cache::Memcached {
'servers' => [
'192.168.0.2:11211',
],
}; # Set up Gearman my $client = Gearman::Client->new;
$client->job_servers('192.168.0.2:4730'); #
# Obtain the information you want to process
# and generate a unique key my $id = 9334; # Write some metadata $cache->set(sprintf('doc-%d-srctxt',$id),
'The quick brown fox jumps over the lazy dog'); my $result = $client->dispatch_background('wordcount',$id);
清单 9 展示了修改后的工人脚本。
清单 9. 基于 memcached 的工人
use Cache::Memcached;
use Gearman::Worker; my $cache = new Cache::Memcached {
'servers' => [
'192.168.0.2:11211',
],
}; my $worker = Gearman::Worker->new;
$worker->job_servers('192.168.0.2:4730');
$worker->register_function('wordcount' => \&wordcount);
$worker->work while 1; sub wordcount
{
my ($arg) = @_; my $id = $arg->arg; print STDERR "Providing word count for ",$id,"\n"; my $string = $cache->get(sprintf('doc-%d-srctxt',$id)); if (!defined($string))
{
$cache->set(sprintf('doc-%d-status',$id),
'Error: Source text not found');
return;
} my @words = split /\s+/,$string;
$cache->set(sprintf('doc-%d-status',$id),
'Complete');
$cache->set(sprintf('doc-%d-result',$id),
scalar @words);
}
注意,在工人中,脚本使用 status 和 tagged memcached 项来保留信息;失败可以被写入 status。这允许客户机在发生暂时的失败时重新提交工作请求。
由于客户机不期待收到响应,所以需要一个单独的脚本在 memached 就绪时从它取得信息。清单 10 展示了一个用于此目的的简单客户机脚本,它识别是否出错(并报告错误)或报告结果。
清单 10. 从 memcached 取得结果
use Cache::Memcached; my $cache = new Cache::Memcached {
'servers' => [
'192.168.0.2:11211',
],
}; my $id = 9334; if ((my $result = $cache->get(sprintf('doc-%d-status',$id))) =~ m/Complete/)
{
print "Count is ",$cache->get(sprintf('doc-%d-result',$id),),"\n";
}
else
{
print "Result not ready: $result\n";
}
要使用该脚本,可运行工人脚本:$ perl workermemc.pl
。然后运行客户机脚本,将请求提交到队列中:$ perl clientmemc.pl
。
可以通过使用检索脚本,了解请求是否已完成。
清单 11. 检索脚本
$ perl getresult.pl
Result not ready:
一旦结果最终可用,您就可以看到。
清单 12. 最终结果
$ perl client.pl
Words 9
您可以对所有类型的数据和环境重复该脚本,并在该过程中包括数据库写入和恢复。
结束语
Gearman 是一种简单的思想,它容易使用,却能提供丰富的功能,从容易地分布和共享工作负载,到允许在不同的语言和部署环境之间进行互操作。排队系统允许您利用该功 能,并通过排队或降低通常在前端导致性能问题的并发性,使用它来提高应用程序性能及减少数据库和其他服务器组件上的负载。