Элегантная реализация многопроцессорного управления PHP от 0 до 1

задняя часть PHP Командная строка Google
                       _        
                      | |       
_ __   __ _ _ __ _   _| |_ ___  
| '_ \ / _` | '__| | | | __/ _ \ 
| | | | (_| | |  | |_| | || (_) |
|_| |_|\__,_|_|   \__,_|\__\___/ .TIGERB.cn
			
An object-oriented multi process manager for PHP

Деловая сцена

В нашем реальном бизнес-сценарии (технологический стек PHP) нам может потребоваться выполнять некоторую бизнес-логику регулярно или почти в реальном времени.Проще говоря, мы можем использовать crontab, который поставляется с системой unix, для реализации запланированных задач, но для некоторых задач в реальном времени. требования выше Бизнес больше не применим, поэтому нам нужен инструмент управления задачами, который находится в памяти.Чтобы обеспечить производительность в реальном времени, с одной стороны, мы позволяем ему выполнять задачи все время (соответствующий сон, чтобы гарантировать, что процессор не занят на 100%), с другой стороны, мы реализуем многопроцессорность для обеспечения одновременного выполнения задач.

Цель

Подводя итог, моя цель - реализовать инструмент управления несколькими процессами master-worker на основе режима php-cli. Во-вторых, «у меня есть такая цель, как я ее анализирую, планирую и шаг за шагом реализую», что и является целью данной статьи.

Примечание. Далее родительский процесс совместно именуется главным, а дочерний процесс — рабочим.

анализировать

мы ставим этотРазделите большую цель на несколько маленьких целейЧтобы реализовать один за другим, следующим образом:

  • мультипрогресс
    • Назначение: одна основная вилка для нескольких рабочих
    • Явление: идентификатор родительского процесса ppid всех рабочих процессов — это pid текущего мастера.
  • мастер контролирует работника
    • Цель: мастер уведомляет работника, и работник получает сообщение от мастера
  • мастер получает сигнал
    • Назначение: Мастер получает и настраивает обработку сигналов от терминала

мультипрогресс

Как разветвить процесс в PHPpcntl_fork, Об этом должен знать каждый. Если вы не знаете, эту функцию будет легко найти, просто погуглив/бинг. Тогда FTM, посмотримpcntl_forkИспользование этой функции примерно так:

$pid = pcntl_fork(); // pcntl_fork 的返回值是一个int值
                     // 如果$pid=-1 fork进程失败
                     // 如果$pid=0 当前的上下文环境为worker
                     // 如果$pid>0 当前的上下文环境为master,这个pid就是fork的worker的pid

Затем посмотрите на код:

$pid = pcntl_fork();	
switch ($pid) {
  case -1:
    // fatal error 致命错误 所有进程crash掉
    break;

  case 0:
    // worker context
    exit; // 这里exit掉,避免worker继续执行下面的代码而造成一些问题
    break;

  default:
    // master context
    pcntl_wait($status); // pcntl_wait会阻塞,例如直到一个子进程exit
    // 或者 pcntl_waitpid($pid, $status, WNOHANG); // WNOHANG:即使没有子进程exit,也会立即返回
    break;
}

Мы видим, что у мастера есть вызовpcntl_waitилиpcntl_waitpidфункция, почему? Прежде всего, мы должны упомянуть здесь два понятия, а именно:

  • Процесс-сирота: родительский процесс зависает, а дочерний процесс берет на себя процесс инициализации с pid=1 (wait/waitpid), пока собственный жизненный цикл дочернего процесса не закончится, система не вернет ресурсы, а родительский процесс не возьмет на себя связанное восстановление. операции
  • Процесс-зомби: дочерний процесс завершается, родительский процесс не получает статус дочернего процесса через wait/waitpid, а дескрипторы, такие как идентификатор процесса, занятый дочерним процессом, все еще существуют, причиняя вред: например, идентификатор процесса ограничен, а невозможность разблокировать идентификатор процесса приводит к возможным проблемам в будущем.

так,pcntl_waitилиpcntl_waitpidЦель состоит в том, чтобы предотвратить превращение рабочих процессов в процессы-зомби.

Вдобавок к этому нам также нужно приостановить мастер и воркер, я использую цикл while, а затемusleep(200000)Предотвращает загрузку процессора на 100%.

Наконец, мы кратко суммируем и опишем процесс этой многопроцессной реализации с помощью следующего рисунка (1-1):

мастер контролирует работника

Вышеприведенное реализует многопроцессорную и многопроцессорную резидентную память, так как же мастер управляет рабочими процессами? Ответ: Многопроцессорная связь. Не так много, чтобы сказать о google/bing, вот несколько способов:

  • Именованные трубы: Заинтересованы
  • Очередь: личное ощущение и идея использования Redis в качестве очереди сообщений в бизнесе должны быть согласованы
  • Общая память: нарушает принцип «не общайтесь через общую память, общайтесь через общение».
  • Сигнал: нести меньше информации
  • розетка: незнакомая

Поэтому я выбрал путь «именованных каналов». Поток коммуникаций, который я разработал, выглядит примерно следующим образом:

  • шаг 1: создайте рабочий конвейер
  • шаг 2: мастер записывает сообщение в рабочий конвейер
  • шаг 3: рабочий читает сообщение из рабочего конвейера

Затем я разобью его по одному, конечно, я погуглю/погуглю, если мне особо нечего сказать.posix_mkfifoсоздавать именованные каналы,fopenОткрытые файлы (каналы существуют как файлы),freadчитать трубу,fcloseЗакройте трубу и выдохните, ха-ха, чтобы мы могли легко реализовать наши идеи выше. Теперь давайте поговорим о проблеме, с которой я столкнулся:fopenОн заблокирован, чтобы бизнес-код не мог выполняться циклически, думать об этом неправильно, обычноfopenОбычные файлы не имеют блокирующего поведения.В настоящее время поиск FTM не упоминается.fopen, ctrl+f поиск по странице "заблокировать", ключевой момент:

fopen() will block if the file to be opened is a fifo. This is true whether it's opened in "r" or "w" mode. (See man 7 fifo: this is the correct, default behaviour; although Linux supports non-blocking fopen() of a fifo, PHP doesn't).

Под переводом, вероятно, имеется в виду "при открытии fifo-файла с использованием режима r или w fopen он всегда будет блокироваться; хотя linux поддерживает неблокирующее открытие fifo, но php не поддерживает его.", решения нет, нет поддержки, я чувствую, что должен сдаться, я думаю, что такая сцена не должна быть без поддержки, пойдем и посмотримposix_mkfifo, результатом был вне себя от радости:

<?php
  $fh=fopen($fifo, "r+"); // ensures at least one writer (us) so will be non-blocking
  stream_set_blocking($fh, false); // prevent fread / fwrite blocking
?>

The "r+" allows fopen to return immediately regardless of external  writer channel.

В заключении используется "r+", и в то же время мы знаем использованиеstream_set_blockingпредотвратить следующийfreadблокировать. Затем мы используем следующий рисунок (1-2), чтобы кратко обобщить и описать способ общения мастер-рабочий.

мастер получает сигнал

Наконец, проблема, которую нам нужно решить, заключается в том, как мастер принимает сигнал от клиента.

master接收信号 -> pcntl_signal注册对应信号的handler方法 -> pcntl_signal_dispatch() 派发信号到handler

Как показано на рисунке (1-3) ниже,

разное

Затем нам нужно только реализовать стратегию master&worker при различных сигналах, таких как перезапуск рабочего. Здесь следует отметить, что когда мастер получает сигнал перезапуска, рабочий процесс не завершается немедленно, а ждет, пока не будет выполнена бизнес-логика рабочего процесса, чтобы выйти. Конкретный способ:

master接收reload信号 -> master把reload信号写worker管道 -> worker读取到reload信号 -> worker添加重启标志位 -> worker执行完业务逻辑后且检测到重启的标志位后exit

моделирование

Разобравшись с нашей реализацией выше, мы приступаем к кодированию. Перед кодом выполняется простое моделирование следующим образом:

Менеджер класса управления процессами

- attributes
  + master: master对象
  + workers: worker进程对象池
  + waitSignalProcessPool: 等待信号的worker池
  + startNum: 启动进程数量
  + userPasswd: linux用户密码
  + pipeDir: 管道存放路径
  + signalSupport: 支持的信号
  + hangupLoopMicrotime: 挂起间隔睡眠时间
- method
  + welcome: 欢迎于
  + configure: 初始化配置
  + fork: forkworker方法
  + execFork: 执行forkworker方法
  + defineSigHandler: 定义信号handler
  + registerSigHandler: 注册信号handler
  + hangup: 挂起主进程

Процесс абстрактного класса Процесс

- attributes
  + type: 进程类型 master/worker
  + pid: 进程ID
  + pipeName: 管道名称 
  + pipeMode: 管道模式
  + pipeDir: 管道存放路径
  + pipeNamePrefix: 管道名称前缀
  + pipePath: 管道生成路径
  + readPipeType: 读取管道数据的字节数
  + workerExitFlag: 进程退出标志位
  + signal: 当前接受到的信号
  + hangupLoopMicrotime: 挂起间隔睡眠时间
- method
  + hangup: 挂起进程(抽象方法)
  + pipeMake: 创建管道
  + pipeWrite: 写管道
  + pipeRead: 读管道
  + clearPipe: 清理管道文件
  + stop: 进程exit

основной класс сущности MasterProcess

- attributes
  + 
- method
  + hangup: 挂起进程

рабочий класс сущности MasterProcess

- attributes
  + 
- method
  + dispatchSig: 定义worker信号处理方式

Последнее, что нам нужно сделать, это изящно заполнить наш код.

Наконец

адрес проектаGitHub.com/tiger B/ включает…

Недостатков в личных знаниях еще много.Если что-то не так в написании, надеюсь со временем исправите.

THX~