Долгое время не было блога — в эти дни я очень занят поиском в Швейцарии, но сначала захожу, чтобы сказать поздравления команде 2-го издания, а во-вторых, за явное злорадство, представив позднюю реализацию PHP для захватывающего проекта Wide Finder Тима Брея.
Тим поставил простую, но очень сложную задачу; написать приложение, которое определяет топ-10 самых популярных блогов из его журнала доступа Apache. Он должен быть быстрым и читаемым, с подтекстом, иллюстрирующим, как «язык X» справляется с точки зрения параллельной обработки и использования «более широких» (многопроцессорных) систем.
Технически, PHP практически не имеет возможности выполнять вещи параллельно, кроме pcntl_fork — (не пытайтесь делать это на вашем живом веб-сервере — только CLI!), Если вы не учитываете тики , а я надеюсь, что это не так.
Но мы не позволим маленьким деталям остановить нас! Потому что у нас есть curl_multi_exec (), который позволяет нам общаться с несколькими URL-адресами и обрабатывать их ответы параллельно. И просто подумайте, сколько запросов Apache + mod_php может обслуживать одновременно. Идеально подходит для какой-то карты и уменьшить …
Итак, сначала картограф;
<?php
if ( isset($argv[1]) ) $_GET['f'] = $argv[1];
if ( !isset($_GET['f']) || !is_readable($_GET['f']) ) {
header("HTTP/1.0 404 Not Found");
die("Cant read file ".htmlspecialchars($_GET['f']));
}
$s = isset($_GET['s']) && ctype_digit($_GET['s']) ? $_GET['s'] : 0;
$e = isset($_GET['s']) && ctype_digit($_GET['e']) ? $_GET['e'] : filesize($_GET['f']);
$f = fopen($_GET['f'], 'r');
fseek($f, $s);
$counts = array();
while ( !feof($f) && ftell($f) < $e ) {
$pattern = '#GET /ongoing/When/dddx/(dddd/dd/dd/[^ .]+) #';
if ( preg_match($pattern, fgets($f), $m) ) {
isset($counts[$m[1]]) ? $counts[$m[1]]++ : $counts[$m[1]] = 1;
}
}
fclose($f);
$out = serialize($counts);
header("Content-Length: ".strlen($out));
echo $out;
Вы указываете на файл, к которому он может получить доступ через свою файловую систему, а также задаете диапазон байтов (начало и конец) для анализа. Он возвращает сериализованный хэш, ключами которого являются заголовки записей в блоге, а значениями — количество вхождений. URL-адрес для вызова может выглядеть как http: //localhost/~harry/wf/wf_map.php? F = / tmp / o10k.ap & s = 257664 & e = 524288. Также для бенчмаркинга он может быть вызван из командной строки для обработки всего файла в одном потоке, например, $ php ./wf_map.php /tmp/o10k.ap
Затем у нас есть редуктор, который фактически вызывает преобразователь (и) с помощью curl;
#!/usr/bin/php
<?php
function wf_reduce($urls) {
$counts = array();
$mh = curl_multi_init();
foreach ( $urls as $url ) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_multi_add_handle($mh, $ch);
}
do {
curl_multi_exec($mh,$running);
while ( FALSE !==($msg = curl_multi_info_read($mh)) ) {
if ( "200" != ( $status = curl_getinfo ( $msg['handle'], CURLINFO_HTTP_CODE ) ) ) {
die("HTTP Status: $status!");
}
// Here we can "reduce" as soon as we get a response...
foreach ( unserialize( curl_multi_getcontent ( $msg['handle'] ) ) as $page => $hits ) {
isset($counts[$page]) ? $counts[$page] += $hits : $counts[$page] = $hits;
}
}
} while ($running > 0);
return $counts;
}
$mapurl = "http://localhost/~harry/wf/wf_map.php";
$file = "/tmp/o10k.ap";
if (!file_exists($file) ) {
print "First run: downloading sample data!";
file_put_contents($file, file_get_contents("http://www.tbray.org/tmp/o10k.ap"));
die();
}
$partitions = isset($argv[1]) && ctype_digit($argv[1]) ? $argv[1] : 1;
$end = filesize($file);
$partition = floor($end / $partitions);
$s = 0;
$e = $partition;
$urls = array();
while ( $e <= $end ) {
$urls[] = sprintf("%s?f=%s&s=%s&e=%s", $mapurl, urlencode($file), $s, $e);
$s = $e;
$e += $partition;
}
$counts = wf_reduce($urls);
arsort($counts, SORT_NUMERIC);
$top10 = array_slice($counts, 0, 10, TRUE);
print_r( array_reverse($top10) );
Он предназначен исключительно для выполнения командной строки, принимая один аргумент, который представляет собой число параллельных «потоков», которые должны выполняться, например, $ ./wf_reducer.php 10
Вам нужно будет изменить $mapurl
Сомневаюсь, что это получит какие-либо награды за красоту, и тесты с образцом файла Тима не впечатляют — быстрее запустить однопоточный — возможно, вы начнете получать с гораздо большим файлом? Мое желание узнать уменьшается.
Но в любом случае — PHP тоже был там. Интересно, Тим сейчас скомпилирует его на этом T5120? 😉
Обновление: только что обнаруженный Рассел Битти первым пришел с однопоточным решением.