Есть у меня приложение на 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-го уровня, а каждая такая копия занимает порядка 10Мб в памяти. Вроде бы пустячок, но контроллер памяти - не резиновый, и не слишком рассчитан на то, чтобы копии абсолютно одних и тех же данных читались из 16-ти разных мест
- Если вдруг какой-то процесс не получит сообщение об обновлении или обработает его по каким-то причинам позже других - его «версия реальности» резко разойдётся с версиями всех остальных процессов - и в итоге мы получим на один запрос бэкенда свежие данные, а на следующий (при передаче управления залипшему процессу) - уже устаревшие данные.
Возможно, я что-то дурю и то, как оно есть - это уже вполне рабочий вариант (не могу сказать, что сейчас всё это хозяйство работает медленно - вроде даже вполне приемлемо)? Просто у варианта с кешом 2-го уровня в виде Cache::FastMmap есть как минимум один существенный недостаток: он работает в разы медленнее простого Perl-хеша...