Back-End

15 jul, 2015

PHP IPC com Daemon Service usando filas de mensagens, memória compartilhada e semáforos

100 visualizações
Publicidade

Em um artigo anterior, aprendemos como criar um serviço daemon simples em PHP para monitorar e processar uma atividade importante em uma máquina no background.

Agora vamos seguir com um tópico mais avançado, que é como os processos daemon podem se comunicar com outros programas, ou com outras instâncias do mesmo processo daemon.

Leia este artigo para aprender a executar comunicação entre processos ou IPC (Inter-Process Communication) em PHP para enviar e receber dados utilizando filas de mensagens, bem como a transmissão de grandes volumes de dados usando memória compartilhada, e usando semáforos para evitar problemas causados por acessos simultâneos.

Introdução

Em um artigo anterior, aprendemos a criar um serviço daemon em PHP. Agora vamos aprender a usar os métodos para executar IPC – Inter-Process Communication – para se comunicar com os processos de daemon.

Filas de mensagens

No mundo do UNIX, existe uma variedade incrível de formas de enviar uma mensagem ou um comando para um script daemon e vice-versa. Mas primeiro quero falar só sobre as filas de mensagens – “System V IPC Messages Queues”.

Há muito tempo, eu aprendi que uma fila pode estar tanto na implementação do System V IPC, quanto na implementação do POSIX. Eu quero comentar apenas sobre a implementação do System V, já que sei mais sobre isso.

Vamos começar. No nível “normal” do sistema operacional, as filas são armazenados na memória. As estruturas de dados de fila estão disponíveis para todos os programas do sistema. Assim como no sistema de arquivos, é possível configurar os direitos de acesso das filas e os tamanho da mensagem. Geralmente, o tamanho da mensagem da fila é pequeno, menos de 8 KB.

Essa parte introdutória terminou. Vamos passar para a prática com os mesmos exemplos de scripts.

queue-send.php

// Convert a path name and a project identifier to a System V IPC key
$key = ftok(__FILE__, 'A'); // 555 for example

// Creating a message queue with a key, we need to use an integer value.
$queue = msg_get_queue($key);

// Send a message. Note that all required fields are already filled,
// but sometimes you want to serialize an object and put on a message or a lock.
// Note that we specify a different type. Type - is a certain group in the queue.
msg_send($queue, 1, 'message, type 1');
msg_send($queue, 2, 'message, type 2');
msg_send($queue, 3, 'message, type 3');
msg_send($queue, 1, 'message, type 1');

echo "send 4 messages\n";

queue-receive.php

$key = ftok('queue-send.php', 'A'); // 555 for example
$queue = msg_get_queue($key);

// Loop through all types of messages.
for ($i = 1; $i <= 3; $i++) {
    echo "type: {$i}\n";

// Loop through all, read messages are removed from the queue.
// Here we find a constant MSG_IPC_NOWAIT, without it all will hang forever.
    while ( msg_receive($queue, $i, $msgtype, 4096, $message, false, MSG_IPC_NOWAIT) ) {
        echo "type: {$i}, msgtype: {$msgtype}, message: {$message}\n";
    }
}

Vamos rodar na primeira etapa do arquivo queue-send.php e, em seguida, queue-receive.php.

u% php queue-send.php
send 4 messages
u% php queue-receive.php
type: 1
type: 1, msgtype: 1, message: s:15:"message, type 1";
type: 1, msgtype: 1, message: s:15:"message, type 1";
type: 2
type: 2, msgtype: 2, message: s:15:"message, type 2";
type: 3
type: 3, msgtype: 3, message: s:15:"message, type 3";

Você pode notar que as mensagens foram agrupadas. O primeiro grupo reuniu duas mensagens do primeiro tipo, e depois as mensagens restantes.

Se nós tivéssemos indicado para receber mensagens do tipo 0, você teria todas as mensagens, independentemente do tipo.

while (msg_receive($queue, $i, $msgtype, 4096, $message, false, MSG_IPC_NOWAIT)) {
// ...

Vale ressaltar aqui outra característica das filas: se não usarmos a constante MSG_IPC_NOWAIT no script e executarmos o script queue-receive.php a partir de um terminal, e depois executarmos periodicamente o arquivo queue-send.php, veremos como um daemon pode, efetivamente, usar isso para esperar trabalhos.

queue-receive-wait.php

$key = ftok('queue-send.php', 'A'); // 555 for example
$queue = msg_get_queue($key);

// Loop through all types of messages.
// Loop through all, read messages are removed from the queue.
while ( msg_receive($queue, 0, $msgtype, 4096, $message) ) {
    echo "msgtype: {$msgtype}, message: {$message}\n";
}

Na verdade, essa é a informação mais interessante de tudo o que eu disse. Existem, também, as funções para obter as estatísticas, a eliminação e a verificação da existência de filas.

Vamos tentar agora escrever um daemon escutando uma fila:

queue-daemon.php

// Fork process
$pid = pcntl_fork();
$key = ftok('queue-send.php', 'A');
$queue = msg_get_queue($key);

if ($pid == -1) {
    exit;
} elseif ($pid) {
    exit;
} else {
    while ( msg_receive($queue, 0, $msgtype, 4096, $message) ) {
        echo "msgtype: {$msgtype}, message: {$message}\n";
    }
}

// Disengaged from the terminal
posix_setsid();

Memória compartilhada

Aprendemos a trabalhar com as filas, com as quais você pode enviar mensagens de sistema de pequeno porte. Mas, depois, podemos certamente ser confrontados com a tarefa de transmitir grandes quantidades de dados. O meu tipo favorito de sistema, o System V, resolveu o problema da transmissão e da preservação rápidas de grandes volumes de dados em memória usando um mecanismo chamado memória compartilhada.

Em suma, os dados na memória compartilhada vivem até que o sistema seja reinicializado. Como os dados na memória, ele funciona muito mais rápido do que se tivesse sido armazenado em um banco de dados em algum lugar em um arquivo, ou, Deus me perdoe, em um compartilhamento de rede.

Vamos tentar escrever um exemplo simples de armazenamento de dados.

shared-memory-write-base.php

// This is the correct and recommended way to obtain a unique identifier.
// Based on this approach, the system uses the inode table of the file system
// and for greater uniqueness converts this number based on the second parameter.
// The second parameter always goes one letter
$id = ftok(__FILE__, 'A');

// Create or open the memory block
// Here you can specify additional parameters, in particular the size of the block
// or access rights for other users to access this memory block.

// We can simply specify the id instead of any integer value
$shmId = shm_attach($id);

// As we have shared variables (any integer value)
$var = 1;

// Check if we have the requested variables.
if (shm_has_var($shmId, $var)) {
    // If so, read the data
    $data = (array) shm_get_var($shmId, $var);
} else {
    // If the data was not there.
    $data = array();
}

// Save the in the resulting array value of this file.
$data[time()] = file_get_contents(__FILE__);

// And writes the array in memory, specify where to save the variable.
shm_put_var($shmId, $var, $data);

// Easy?

Execute esse script várias vezes para salvar o valor na memória. Agora vamos escrever um script apenas para ler a partir da memória.

shared-memory-read-base.php

// Read data from memory.
$id = ftok(__DIR__ . '/shared-memory-write-base.php', 'A');
$shmId = shm_attach($id);
$var = 1; 

// Check if we have the requested variables.
if (shm_has_var($shmId, $var)) {
    $data = (array) shm_get_var($shmId, $var);
} else {
    $data = array();
}

// Iterate received and save them to files.
foreach ($data as $key => $value) {
    // A simple example, create a file from the data that we have saved.
    $path = "/tmp/$key.php";
    file_put_contents($path, $value);

    echo $path . PHP_EOL;
}

Semáforos

Assim, em termos gerais, é preciso estar claro para você agora como trabalhar com a memória compartilhada. Os únicos problemas a serem descobertos são algumas nuances, tais como: “O que fazer se dois processos desejarem gravar um bloco de memória?” ou “Como armazenar arquivos binários de qualquer tamanho?”.

Para evitar os acessos simultâneos, vamos utilizar os semáforos. Semáforos nos permitem marcar que queremos ter acesso exclusivo a algum recurso, por exemplo um bloco de memória compartilhada. Enquanto isso acontece, outros processos irão esperar pela sua vez no semáforo.

Neste código, isso é explicado claramente:

shared-memory-semaphors.php

// Let's try to save a binary file, the size of a couple of megabytes.
// This script does the following:
// If there is input, it reads it, otherwise it writes data into memory
// In this case, when writing to the memory we put a sign lock - semaphore

// Everything is as usual, read the previous comments 
$id = ftok(__FILE__, 'A');

// Obtain a resource semaphore - lock feature. There is nothing wrong if we
// use the same id that is used to obtain a resource shared memory 
$semId = sem_get($id);

// Put a lock. There's a caveat. If another process will encounter this lock,
// it will wait until the lock is removed 
sem_acquire($semId);

// Specify your like picture 
$data = file_get_contents(__DIR__.'/06050396.JPG', FILE_BINARY);

// These can be large, so precaution is necessary to allocate such a way that would be enough 
$shmId = shm_attach($id, strlen($data)+4096);
$var = 1;
 
if (shm_has_var($shmId, $var)) {
    // Obtain data from the memory 
         $data = shm_get_var($shmId, $var);

    // Save our file somewhere
    $filename = '/tmp/' . time();
    file_put_contents($filename, $data, FILE_BINARY);

    // Remove the memory block that started it all over again.
    shm_remove($shmId);
} else {
    shm_put_var($shmId, $var, $data); 
}

// Releases the lock. 
sem_release($semId);

Agora você pode usar o utilitário de linha de comando md5sum para comparar dois arquivos: o original e o arquivo salvo. Ou você pode abrir o arquivo no editor de imagens, ou onde quer que prefira, para comparar as imagens.

Com isso, a gente encerra com a memória compartilhada e os semáforos . Como seu dever de casa, gostaria de pedir que você escreva um código no qual um demon usará semáforos para acessar a memória compartilhada.

Conclusão

A troca de dados entre daemons é muito simples. Este artigo descreveu duas opções para a isso: as filas de mensagens e a memória compartilhada.

Deixe um comentário aqui se você tiver dúvidas ou comentários sobre como funciona a troca de dados com os serviços daemon em PHP.

***

Dmitry Mamontov faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://www.phpclasses.org/blog/post/276-PHP-IPC-with-Daemon-Service-using-Message-Queues-Shared-Memory-and-Semaphores.html