Пример использования Gearman

| Нет комментариев

Одна из задач, где Gearman позволяет получить удобство и скорость работы, — обработка текстовых поисковых запросов. Детально я хочу рассказать об этом на YAPC::Europe, а в этом посте покажу несколько фрагментов кода, который сейчас работает в продакшне.

Полученный поисковый запрос раздается на несколько сканеров, каждый из которых отвечает за свою узкую область знаний, например, за географический поиск. В свою очедерь, каждый сканер может уточнять задачу с помощью одного или нескольких парсеров, которые непосредственно и анализируют текст запроса. С точки зрения job-сервера, все это оформлено в несколько воркеров; всего их около двух десятков:

misc::e — поиск по кодам пищевых добавок,
misc::typo — проверка орфографии;

place::airport — поиск аэропортов,
place::country — поиск стран,
place::locality — поиск населенных пунктов;

whl::money — конвертер валют;

whl::area, whl::volume, whl::temperature, whl::speed, whl::pressure, whl::power, whl::mass, whl::length, whl::information, whl::force, whl::energy, whl::calendar — конвертеры единиц измерения;

whl::calculator — калькулятор.

Каждый воркер одновременно и разбирает текстовый запрос, и, если разбор оказался успешным, вычисляет результат.

Интересно, что каждая отдельная часть реализации довольно проста и уныла, примерно тот же эффект наблюдается, когда из бесконечных однострочных методов на Java выстраивается нетривиальное приложение, или как в ассемблере, где количество атомарных действий mov в итоге превращается в качество.

Сканеры подгружаются автоматически при старте. О том, как это делается, недавно упоминалось в рассылке Moscow.pm. Расстановка тасков — перебор по списку:

my $tasks = $client->new_task_set;
for(@{$self->{scanners}}) {
    $tasks->add_task(
        $_ => nfreeze([$query, $self->{locale}]),
        {
            on_complete => sub {
                push @{$self->{results}}, thaw(${$_[0]});
            },
        },
    );
}
$tasks->wait;

Типовой сканер содержит метод, принимающий строку запроса. В частности, сканер единиц измерения и валют раздает задачи своим парсерам:

sub scan {
    my $query = shift @args;

    for my $parser (@{$self->{parsers}}) {
        my $result =
           $parser->parse($query, $self->{locale}) // next;
        push @{$self->{results}}, $result;
    }
}

(Обатите внимание, кстати, на использование оператора defined-or для завершения итерации цикла.)

После того, как сканеры собрали результаты от парсеров, итоговый ответ попадает (в случайном порядке) в общий XML, и всю дальнейшую работу по визуализации данных выполняют с помощью XSLT.

Разумеется, в большинстве случаев ответ находят лишь некоторые из двух десятков воркеров. Более того, часть ответов игнорируется на уровне XSLT в зависимости от того, насколько полными были ответы от тех или иных воркеров.

Вот пример запроса: sin(pi/2) + cos(pi/2). Нефильтрованные данные, полученные от всех сканеров, выглядят следующим образом:

Те же данные с точки зрения XML-наблюдателя:

<pack for="sin(pi/2) + cos(pi/2)" scanner="whl::calculator">
    <item>
        <from expr="sin(π/2) + cos(π/2)"/>
        <to value="1"/>
    </item>
</pack>
<pack for="sin" scanner="place::locality">
    <item country-code="FR" geonameid="2974494">Sin-le-Noble</item>
    <item country-code="AF" geonameid="1124363">Sīn</item>
    . . .
</pack>
<pack for="pi" scanner="place::locality">
    <item country-code="FR" geonameid="2984891">Py</item>
    <item country-code="ES" geonameid="6424319">Pi</item>
    <item country-code="IN" geonameid="1259715">Pi</item>
</pack>
<pack for="cos" scanner="place::locality">
    <item country-code="FR" geonameid="3023491">Cos</item>
    <item country-code="ES" geonameid="3124419">Cos</item>
    . . .
</pack>

Парсер калькулятора вернул один результат (собственно, наиболее релевантный), а географический сканнер нашел города с названиями и Синус, и Косинус, и Пи — эти результаты при наличии первого результата, который поглотил всю строку, показывать, разумеется, не обязательно. Большинство возможностей, которые сегодня умеет делать упоминаемый здесь поиск, описаны на соответствующей странице.

Комментировать

Страницы

  • img

Об этой записи

Сообщение опубликовано 18.03.2010 19:50. Автор — ash.

Предыдущая запись — Perl 5.10 в 2010-м — вторая часть части III

Следующая запись — Еще раз об именованных сохраняющих скобках

Смотрите новые записи на главной странице или загляните в архив, где есть ссылки на все сообщения.