LINUX.ORG.RU

Можно ли в Mojo-приложениях использовать главный процесс?

 , ,


0

1

Есть у меня приложение на Mojo (работает под hypnotoad), его список процессов выглядит следующим образом:

$ pstree -p $(</opt/WebServices/druid2/run/hypnotoad.pid)
/opt/WebService(7038)─┬─/opt/WebService(7039)
                      ├─/opt/WebService(7040)
                      ├─/opt/WebService(7041)
                      ├─/opt/WebService(7042)
                      ├─/opt/WebService(7043)
                      ├─/opt/WebService(7044)
                      ├─/opt/WebService(7045)
                      ├─/opt/WebService(7046)
                      ├─/opt/WebService(7047)
                      ├─/opt/WebService(7048)
                      ├─/opt/WebService(7049)
                      ├─/opt/WebService(7050)
                      ├─/opt/WebService(7051)
                      ├─/opt/WebService(7052)
                      ├─/opt/WebService(7053)
                      └─/opt/WebService(7054)
$ sudo netstat -pna | fgrep :8097
tcp        0      0 0.0.0.0:8097            0.0.0.0:*               LISTEN      7038/druid          

Мне очень хотелось бы сделать следующее: после всех форков процесса 7038, нужных для создания воркеров, сделать ещё один форк, но особый - этот форк создаст процесс для выполнения «служебных» задач, в данном конкретном случае - для обновления кеша. Т.е., условно будет форкнут процесс №7055, который будет подогревать кеш для чтения его в процессах №№7039-7054.

Кто-нибудь пробовал делать подобное в рамках одного приложения или единственный вариант - обновление кеша вообще в отдельном демоне, просто шарящем один файл через mmap с процессами Mojo-приложения?

Попытаюсь реализовать с помощью Mojo::IOLoop::ForkCall, но возможно есть варианты лучше.

Также есть более фундаментальная проблема: в данный момент кеш существует для каждого процесса отдельно в виде самого обычного perl-хеша. Реализован следующим образом:

  • Есть обновляемый и перезагружаемый извне кеш 1-го уровня, это Redis;
  • Каждый процесс делает подписку (subscription) в Redis для получения JSON-сообщений вида: «у объектов (ключи хеша) изменились атрибуты (значения хеша)» и на сообщения «кеш 1-го уровня перезагружен целиком»;
  • При старте, т.е. в Mojo::IOLoop->next_tick, каждый процесс читает весь кеш 1-го уровня, подписывается на сообщения об изменениях этого кеша. При получении сообщений об изменениях - каждый процесс-воркер обновляет свой perl'овый хеш-массив для того, чтобы тот соответствовал текущему состоянию Redis.

Чем плоха описанная схема?

  1. При получении сообщений о полной перезагрузке кеша 1-го уровня - все процессы дружно лезут читать его целиком. А кеш довольно большой по объёму. В этот момент, ясное дело, процессы не могут отвечать на запросы пользователей - т.е. не могут все одновременно :) Теоретически это можно обойти, существенно реже обновляя кеш 1-го уровня целиком.
  2. Каждый процесс хранит у себя хеш-полную копию кеша 1-го уровня, а каждая такая копия занимает порядка 10Мб в памяти. Вроде бы пустячок, но контроллер памяти - не резиновый, и не слишком рассчитан на то, чтобы копии абсолютно одних и тех же данных читались из 16-ти разных мест
  3. Если вдруг какой-то процесс не получит сообщение об обновлении или обработает его по каким-то причинам позже других - его «версия реальности» резко разойдётся с версиями всех остальных процессов - и в итоге мы получим на один запрос бэкенда свежие данные, а на следующий (при передаче управления залипшему процессу) - уже устаревшие данные.

Возможно, я что-то дурю и то, как оно есть - это уже вполне рабочий вариант (не могу сказать, что сейчас всё это хозяйство работает медленно - вроде даже вполне приемлемо)? Просто у варианта с кешом 2-го уровня в виде Cache::FastMmap есть как минимум один существенный недостаток: он работает в разы медленнее простого Perl-хеша...

★★★★★

Последнее исправление: DRVTiny (всего исправлений: 4)
Ответ на: комментарий от bvn13

Оно и на Perl'е мутится - Mojo::IOLoop:ForkCall, а уж тем более AnyEvent::Fork. Я так подумал, AnyEvent::Fork даже интереснее.

Вопросов 2:

  1. Можно ли в принципе реализовать служебный процесс в рамках Mojo::Server::Prefork/hypnotoad, т.е. НЕ используя какие-то сторонние решения (читай - костыльные, усложняющие и без того не самую прозрачную архитектуру приложения)
  2. Конкретно в части кеширования - учитывая то, что Perl по крайней мере не умеет читать из shared memory со скоростью, сопоставимой со скоростью работы с внутренними хеш-массивами, да плюс удобство работы сильно различается - в принципе имеет ли смысл использовать прямое чтение из такого тормозного кеша?
DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от bvn13

Cache::FastMmap - это тоже кеш в ОЗУ. Мало того, для того, чтобы содержимое этого кеша попало наконец в реальный файл - нужно предпринимать дополнительные нетривиальные телодвижения, особенно если нужно, чтобы данные попадали туда гарантированно.

Cache::FastMmap написан почти полностью на C и, насколько мне известно из бенчмарков, на сегодняшний день сопоставим по скорости только с BerkeleyDB. Memcached - интерфейс для Perl'а медленнее.

Проблема же главная в том, что мне нужно читать структуру данных в кеше как «древовидный граф». У этого графа каждая нода представлена как ключ-уникальный индентификатор объекта (пример: «s90468») и значение - некая запись вида {«parents»: [«s41701»,«s40892»], «dependencies»: [«s318901»,«s189200»,«s101003»],«name»: «some node»}.

Клиенты хотят от меня, например «все объекты ветви дерева, начинающейся в ноде s90648». Если собирать это дерево из кеша, который нужно сериализовать/десериализовать - скорость всего это в любом случае будет оставлять желать лучшего. Скорость сборки с использованием внутренней структуры Perl - вполне приличная.

Относительного полного обновления кеша первого уровня - я придумал вариант решения через AnyEvent::Fork - это во всяком случае избавит от неприятных моментов, когда раз в 3 часа воркеры всем своим клиентам говорят «обождите-обождите, мы тут данные из Redis'а будем 7 секунд тянуть, а вы пока поковыряйтесь в носу».

DRVTiny ★★★★★
() автор топика

Если вдруг какой-то процесс не получит сообщение об обновлении или обработает его по каким-то причинам позже других - его «версия реальности» резко разойдётся с версиями всех остальных процессов - и в итоге мы получим на один запрос бэкенда свежие данные, а на следующий (при передаче управления залипшему процессу) - уже устаревшие данные.

Кажется что от такого поворота избавиться сложно, и не нужно. Надо чтобы система заранее умела жить в условиях что у кого-то кеш протух. Подддерживать консистентность кеша очень сложно. Воркеры могут быть на разных машинах, в разных странах ;)

Возможно самый надежный вариант - не использовать кеш ;)

OxiD ★★★★
()
Ответ на: комментарий от DRVTiny

кажется пробелма перла в том что он сериализует/десериализует целиком структуру при чтении-записи из памяти. Если найдете библиотеку которая умеет этого не делать, то скорость будет в разы выше!

Вооще с шареной памятью у перла все плохо. Не нашел нормальных модулей для этого. У всех есть куча недостатков.

И еще мне кажется что самым надежным и быстрым вариантом будет использование какой-нибудь sql бд. В которой будут правильные индексы. И каждый воркер будет ходить туда за данными. Оттуда можно по идее одним запросом прочитать «все объекты ветви дерева, начинающейся в ноде s90648»

OxiD ★★★★
()
Ответ на: комментарий от OxiD

Другой вариант - перед каждым походом в кеш воркер идет в нормальную БД и смотрит там версию данных. Если версия в бд новее чем в кеше воркера, он обновляет кеш ( ну или еще что-то делает, падает там в 500, отдает запрос другому воркеру ;)

Пробелма в том что это не защищает. Веб может слать паралелльно ajax запросы, и какойто может вдруг вернуть старые данные, а остальные вернут новые. Вообщем это все будет очень ненадежно.

Я бы стал использовать кеш только для данных которые достаточно редко меняются. По крайней мере в вебе. Для всего остального - читать из мастера.

OxiD ★★★★
()
Ответ на: комментарий от OxiD

Возможно надо подумать над тем как данные в БД хранятся, чтобы их быстрее оттуда читать.

OxiD ★★★★
()
Ответ на: комментарий от OxiD

Кстати, я посмотрел, что будет, если сделать форк внутри метода startup: получается процесс-потомок init'а. Это значит, что после вызова startup исходный процесс завершается, а перед этим от него форкается «мастер воркеров», т.е. процесс, который становится " родоначальником" всей ветки воркеров. В принципе, форкаться от startup неудобно, но на крайняк - почему бы и нет. Тогда этот «потомок Инита» запустит event loop, подпишется на канал в Редис и будет слушать fullReload'ы. При получении - только он скачает целиком кеш, засунет его в mmap'леный файл и опубликует в Redis: replaceCache. Каждый воркер при получении такого сообщения откроет mmap-файл, считает целиком, сделает decode_cbor - voila! У каждого воркера есть стартовое состояние кеша в простой и удобной структуре данных. Надо только придумать, как начальную инициализацию сделать, не создавая ложного fullReload-сообщения...

DRVTiny ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.