Back-End

25 jul, 2011

Processamento distribuído em PHP utilizando Gearman – Parte 2

Publicidade

Este artigo é a continuação do primeiro sobre Gearman, um framework genérico para distribuir o processamento de diferentes tarefas na
mesma máquina ou em outras máquinas de um cluster.

Iniciando
um servidor de trabalho Gearman com uma fila de job persistente

Iniciar um servidor de trabalho Gearman é fácil. Mas se
você precisar que ele use uma fila de job persistente, serão necessários alguns
passos adicionais.

Primeiro, você precisa configurar o armazenamento de fila. Por exemplo, se você deseja
usar MySQL como armazenamento de fila, você precisará criar uma tabela de fila como segue: 

CREATE TABLE gearman_queue(
`unique_key` VARCHAR(64) PRIMARY KEY,
`function_name` VARCHAR(255),
`priority` INT,
`data` LONGBLOB
);

Você precisa então rodar o programa gerenciador do
Gearman, passando diversos parâmetros
para configurar a conexão ao servidor MySQL com o banco de dados que tem a tabela de fila:

./gearmand -q libdrizzle --libdrizzle-host=10.1.1.1 --libdrizzle-user=gearman --libdrizzle-password=secret --libdrizzle-db=some_db --libdrizzle-table=gearman_queue --libdrizzle-mysql

O Gearman também suporta armazenar filas persistentes em
servidores Memcached e em bancos de dado SQLite.

Distribuindo aplicativos PHP ao configurar processos do PHP worker e do client

A primeira coisa que você deve
fazer é criar funções PHP que serão chamadas para executar os trabalhos do
worker. Será necessário então registrar as funções para lidar com o trabalho em um servidor de trabalho Gearman. Finalmente será necessário começar a aceitar solicitações para quaisquer requisições de trabalho.

Aqui segue um simples
exemplo de um script de processo PHP worker:

<?php

$worker = new GearmanWorker();
$worker->addServer('localhost');
$worker->addServer('10.1.1.1');

$worker->addFunction("reverse", "reverse_fn");

while($worker->work())
{

if ($worker->returnCode() != GEARMAN_SUCCESS)
{

syslog(LOG_ERR, "return_code: " . $worker->returnCode());
break;

}

}

function reverse_fn($job)
{

$param = $job->workload();
return strrev($param);

}
?>

Um script de trabalho do lado do cliente pode ser assim:

<?php

$client = new GearmanClient();
$client->addServer('localhost');
$client->addServer('10.1.1.1');

$return = $client->do('reverse', 'Hello World!');
var_dump($return);

?>

Como se pode ver, é fácil implementar workers e clients
com o Gearman.  Naturalmente o exemplo
acima é bastante simplório. Vamos então ver um exemplo mais interessante: um código de varredura que é executado em segundo plano e oferece um relatório de status.

<?php

$worker = new GearmanWorker();
$worker->addServer('localhost');
$worker->addServer('10.1.1.1');

$worker->addFunction("crawler", "crawler_fn");

while($worker->work())
{

if ($worker->returnCode() != GEARMAN_SUCCESS)
{

syslog(LOG_ERR, "return_code: " . $worker->returnCode());
break;

}

}

function crawler_fn($job)
{

$param = $job->workload();
$urls = unserialize($param);
$count = count($urls);
for($i=0; $i < $count; $i++)
{

$url = $urls[$i];
$content = file_get_contents($url);

/* this function might save the retrieved content into a database */

save_content($url, $content);
$job->sendStatus($i, $count);
sleep(1); /* pause a little while */

}

}
?>

Como se pode notar, a função worker não retorna nenhum
valor. Isso porque ela roda em segundo plano, e não há client esperando por um
valor de retorno.

O script
client poderia ser assim:

<?php

session_start();
$client = new GearmanClient();
$client->addServer('localhost');
$client->addServer('10.1.1.1');

$urls = array(

"http://www.google.com/",
"http://www.phpclasses.org/",
"http://crodas.org/"

);

$jobid = $client->doBackground('crawl', serialize($urls));

$_SESSION['jobid'] = $jobid;
$_SESSION['urls'] = $urls;

print "Your pages were queued for downloadingn";

?>

O client não espera que o trabalho termine porque
rodará em segundo plano. O identificador do trabalho é armazenado em uma sessão
variável, de forma que pode ser recuperado mais tarde, quando for necessário
checar o status do trabalho, por exemplo enviando uma solicitação AJAX  a um script PHP, que pode ser assim:

<?php

session_start();

if (!isset($_SESSION['jobid']))
exit;

$client = new GearmanClient();
$client->addServer('localhost');
$client->addServer('10.1.1.1');

$status = $client->jobStatus($_SESSION['jobid']);
ob_start();

if ($status[0] == true
&& $status[1] == false)
{

echo json_encode( array('status' => 'The job is still on the queue.'));

}
elseif($status[0] == $status[1] && $status[1] == true)
{

$percent = $status[2] / $status[3] * 100;
echo json_encode( array('status' => 'Done $percent %'));

}
else
{

echo json_encode( array("status" => "Done"));
foreach ($_SESSION['urls']) as $url)
{

/* Do something with the crawled pages */

}

}

echo json_encode( array('results' => ob_get_clean()) );

?>

É isso aí, muito simples! Há uma página que pode
iniciar jobs assíncronos. Outra página pode mostrar o status de qualquer
job a qualquer tempo.

Até recentemente, faríamos uma coisa assim usando o crontab para agendar
trabalhos e outros truques não muito legais.

Ideias de aplicativos

O Gearman poderia ser realmente
útil em aplicativos do mundo real. Seguem abaixo algumas ideias de possíveis
aplicações:


Entrega
de newsletters

O envio de newsletters para
muitos assinantes é uma tarefa complicada. Consome uma quantidade de tempo
considerável, proporcional ao número a ser enviado. Dessa forma, a entrega precisa ser feita em segundo plano, sempre que houver
muitos assinantes.

Como acima demonstrado, o Gearman serve bem a esse propósito, uma vez que
permite processamento em segundo plano, e é possível acompanhar como andam os
encaminhamentos.

Primeiro é necessário criar e registrar um script worker job que enviará essa
newsletter para uma lista de assinantes.

Poderíamos criar um processo principal, que poderia ser uma tarefa em segundo plano também
criada pelo Gearman, que geraria um conjunto de tarefas, atribuindo a elas um
conjunto de e-mails para enviar.

Ele poderia detectar tentativas de entregas falhas, reenviando mensagens somente
para os endereços que registraram falhas, já que mensagens não devem ser enviadas mais de uma
vez para cada assinante.

Isso pode ser obtido enviando uma lista de identificadores de assinantes para o
processo worker. O worker precisa buscar a informação em um banco de dados e
atualizar o status do assinante que recebeu a mensagem.


Servidor de aplicação

O Gearman é muito rápido, uma vez que é escrito em C, e é
multi-segmentado. É também tão seguro quanto o Apache, já que os workers rodam
em processos separados, como no Apache no modo pre-fork. Acho que o Gearman
pode eventualmente ser usado como substituto do Apache, já que pode atuar como
um servidor HTTP.

Comecei a trabalhar em algo similar, até agora somente por prazer. É basicamente
um módulo Nginx que conversa diretamente com o Gearman. Tenciono implantá-lo
como um servidor de serviço web e, se funcionar bem, por que não substituir o
Apache um dia desses?

Estou também escrevendo um módulo para servidor Kannel Open Source SMS para uma
companhia privada na qual trabalho. Esse módulo submete qualquer SMS que chega
a um worker Gearman.

http://www.kannel.org/

Map/Reduce

Um outro uso interessante para o
Gearman é escrever sua própria implementação do algoritmo Map/Reduce. Ele pode
ser usado para distribuir o processamento de grandes conjuntos de dados em um
cluster de servidores.

Poderia ser interessante e até mesmo rápido, uma vez que ele poderia usar o
Gearman com um banco de dados local em vez de um sistema de arquivos distribuídos.

Para fazer isso, poderíamos configurar um processo master job worker que
distribuiria uma lista de valores de dados e o código-fonte para as funções Map
e Reduce a serem aplicadas a todos os dados.

Isso é bastante simples de fazer,
uma vez que o  PHP é uma linguagem
dinâmica, e há a função eval() para executar o o código passado dinamicamente. 😉

?

Texto original disponível em http://www.phpclasses.org/blog/post/108-Distributing-PHP-processing-with-Gearman.html