Анемичный group php. Настраиваем php-fpm. Как вы попали в PHP Core Team, какой путь для этого прошли

  • 03.11.2019

1. GROUP BY one key

This function works as GROUP BY for array, but with one important limitation: Only one grouping "column" ($identifier) is possible.

Function arrayUniqueByIdentifier(array $array, string $identifier) { $ids = array_column($array, $identifier); $ids = array_unique($ids); $array = array_filter($array, function ($key, $value) use($ids) { return in_array($value, array_keys($ids)); }, ARRAY_FILTER_USE_BOTH); return $array; }

2. Detecting the unique rows for a table (twodimensional array)

This function is for filtering "rows". If we say, a twodimensional array is a table, then its each element is a row. So, we can remove the duplicated rows with this function. Two rows (elements of the first dimension) are equal, if all their columns (elements of the second dimension) are equal. To the comparsion of "column" values applies: If a value is of a simple type , the value itself will be use on comparing; otherwise its type (array , object , resource , unknown type) will be used.

The strategy is simple: Make from the original array a shallow array, where the elements are implode d "columns" of the original array; then apply array_unique(...) on it; and as last use the detected IDs for filtering of the original array.

Function arrayUniqueByRow(array $table = , string $implodeSeparator) { $elementStrings = ; foreach ($table as $row) { // To avoid notices like "Array to string conversion". $elementPreparedForImplode = array_map(function ($field) { $valueType = gettype($field); $simpleTypes = ["boolean", "integer", "double", "float", "string", "NULL"]; $field = in_array($valueType, $simpleTypes) ? $field: $valueType; return $field; }, $row); $elementStrings = implode($implodeSeparator, $elementPreparedForImplode); } $elementStringsUnique = array_unique($elementStrings); $table = array_intersect_key($table, $elementStringsUnique); return $table; }

It"s also possible to improve the comparing, detecting the "column" value"s class, if its type is object .

The $implodeSeparator should be more or less complex, z.B. spl_object_hash($this) .

3. Detecting the rows with unique identifier columns for a table (twodimensional array)

This solution relies on the 2nd one. Now the complete "row" doesn"t need to be unique. Two "rows" (elements of the first dimension) are equal now, if all relevant "fields" (elements of the second dimension) of the one "row" are equal to the according "fields" (elements with the same key).

The "relevant" "fields" are the "fields" (elements of the second dimension), which have key, that equals to one of the elements of the passed "identifiers".

Function arrayUniqueByMultipleIdentifiers(array $table, array $identifiers, string $implodeSeparator = null) { $arrayForMakingUniqueByRow = $removeArrayColumns($table, $identifiers, true); $arrayUniqueByRow = $arrayUniqueByRow($arrayForMakingUniqueByRow, $implodeSeparator); $arrayUniqueByMultipleIdentifiers = array_intersect_key($table, $arrayUniqueByRow); return $arrayUniqueByMultipleIdentifiers; } function removeArrayColumns(array $table, array $columnNames, bool $isWhitelist = false) { foreach ($table as $rowKey => $row) { if (is_array($row)) { if ($isWhitelist) { foreach ($row as $fieldName => $fieldValue) { if (!in_array($fieldName, $columnNames)) { unset($table[$rowKey][$fieldName]); } } } else { foreach ($row as $fieldName => $fieldValue) { if (in_array($fieldName, $columnNames)) { unset($table[$rowKey][$fieldName]); } } } } } return $table; }

Если вы занимались разработкой PHP последние несколько лет, то наверняка знаете о проблемах этого языка. Зачастую можно услышать, что это фрагментированный язык, инструмент для взломов, что он не имеет настоящей спецификации и т.д. Реальность же такова, что PHP сильно «вырос» в последнее время. Версия PHP 5.4 приблизила его к полной объектной модели и предоставила много новой функциональности.

И это все, конечно, хорошо, но что насчёт фрейморков? На PHP их существует огромное множество. Стоит только начать искать и вы поймете, что вам не хватит жизни изучить их все, потому что новые фрейморки появляются постоянно и в каждом изобретается что-то свое. Так как же превратить это в нечто, что не отталкивает разработчиков и позволяет легко переносить функционал из одного фреймворка в другой?

Что такое PHP-FIG

PHP-FIG (PHP Framework Interop Group) - организованная группа разработчиков, цель которой находить способы совместной работы нескольких фрейморков.

Только представьте: сейчас вы поддерживаете проект на Zend Framework, которому понадобился модуль корзины магазина. Вы уже писали такой модуль для предыдущего проекта, который был на Symphony. Не делать же его снова? К счастью и ZendF и Symphony являются частью PHP-FIG, так что можно импортировать модуль с одного фреймворка в другой. Разве не здорово?

Давайте узнаем, какие фреймворки входят в PHP-FIG

Участники PHP-FIG

Любой разработчик может внести свой фреймворк в список участников PHP-FIG. Тем не менее за это необходимо будет заплатить некую сумму, так что если у вас нет поддержки сообщества вы врядли согласитесь на это. Это сделано для того, чтобы предотвратить регистрацию миллионов микрофреймворков без какой-либо репутации.

Текущие участники:

Что такое PSR?

PSR (PHP Standarts Recomendations) - стандартные рекомендации, результат работы PHP-FIG. Одни члены Группы предлагают правила для каждого PSR, другие голосуют в поддержку этих правил или за их отмену. Обсуждение проходит в Google Groups, а наборы PSR доступны на официальном сайте PHP-FIG.

Давайте рассмотрим некоторые PSR:

Первый шаг на пути объединения фреймворков - наличие общей структуры директорий, поэтому и был принят общий стандарт автозагрузки.

  1. Пространство имен (namespace) и класс должны иметь структуру \\(\)*.
  2. Каждое пространство имен должно содержать пространство верхнего уровня («Vendor Name»).
  3. Каждое пространство имен может иметь сколько угодно уровней.
  4. Каждый разделитель пространства имен конвертируется в DIRECTORY_SEPARATOR при загрузке.
  5. Каждый символ «_» в CLASS NAME конвертируется в DIRECTORY_SEPARATOR.
  6. К полностью определённому пространству имен и классу добавляется «.php» при загрузке.

Пример функции автозагрузки:

PSR-1 - Basic Coding Standart

Эти PSR регулируют основные стандарты, главная идея которых - если все разработчики используют одни стандарты, то перенос кода можно производить без всяких проблем.

  1. В файлах должны использоваться только теги
  2. В файлах должна использоваться только кодировка UTF-8 without BOM.
  3. Имена пространств и классы должны следовать PSR-0.
  4. Имена классов должны быть объявлены в нотации StudlyCaps.
  5. Константы класса должны быть объявлены в верхнем регистре, разделенные подчеркиваниями.
  6. Методы должны быть объявлены в нотации camelCase.

PSR-2 - Coding Style Guide

Это расширенные инструкции для PSR-1, описывающие правила форматирования кода.

  1. Код должен соответствовать PSR-1.
  2. Вместо табуляции должны использоваться 4 пробела.
  3. Не должно быть строгого ограничения на длину строки, рекомендуемая длина - до 80 символов.
  4. Должна быть одна пустая строка после объявления пространства имен.
  5. Скобки для классов должны открываться на следующей строке после объявления и закрываться после тела класса (то же самое для методов).
  6. Видимость методов и свойств должна быть обязательно определена (public, private).
  7. Открывающие скобки для управляющих структур должны находиться на той же строке, закрывающие скобки должны быть на следующей строке после тела структуры.
  8. Пробелы не ставятся после открывающихся круглых скобок методов управляющих структур и перед закрывающимися скобками.

PCR-3 - Logger Interface

В PCR-3 регулируется логгинг, в частности основные девять методов.

  1. LoggerInterface предоставляет 8 методов для логирования восьми RFC 5424 уровней (debug, notice, warning, error, critical, alert, emergency).
  2. Девятый метод log() принимает на вход уровень предупреждения первым параметром. Вызов метода с параметром уровня предупреждения должен возвращать такой же результат, как и вызов метода определенного уровня лога (log(ALERT) == alert()). Вызов метода с неопределённым уровнем предупреждения должен генерировать Psr\Log\InvalidArgumentException.

Так же как и PSR-0, PSR-4 предоставляет улучшенные методы автозагрузки

  1. Термин «класс» относится к классам, интерфейсам, трейтам и другим похожим структурам
  2. Полностью определённое имя класса имеет следующую форму: \(\)*\
  3. При загрузке файла, соответствующему полностью определённому имени класса:
  • Непрерывная серия одного или более ведущих пространств имен, не считая ведущего разделителя пространства имен, в полностью определенном имени класса соответствует по крайней мере одной «корневой директории».
  • Имена директорий и поддиректорий должны соответствовать регистру пространства имен.
  • Окончание полного имени класса соответствует имени файла с окончанием.php. Регистр имени файла обязан соответствовать регистру окончания полного имени класса.
  • Реализация автозагрузчика не должна бросать исключения, генерировать ошибки любого уровня и не обязана возвращать значение.

Заключение

PHP-FIG изменяет способы написания фреймворков, но не то как они работают. Клиенты часто обязывают работать с существующим кодом внутри фреймворка или определяют с каким фреймворком вы должны работать над проектом. PSR рекомендации делают жизнь разработчиков на много легче в этом отношении и это здорово!

16.09.2016

Попробуем определить каким образом можно повысить производительность сервера приложений на базе php-fpm, а также сформировать чек-лист для проверки конфигурации fpm процесса.

Прежде всего стоит определить расположение файла-конфигурации пула. Если вы устанавливали php-fpm из системного репозитория, то конфигурация пула www будет расположена примерно тут /etc/php5/fpm/pool.d/www.conf . В случае если используется свой билд или другая ОС (не debian) следует поискать расположение файла в документации, или указывать его вручную.

Попробуем рассмотреть конфигурацию подробней.

Переходим на UNIX-сокеты

Наверное первое, на что следует обратить внимание, это то как проходят данные от веб-сервера к вашим php процессам. Это отражено в директиве listen:

listen = 127.0.0.1:9000

В случае если установлен адрес:порт, то данные идут через стек TCP, и это наверное не очень хорошо. Если же там путь к сокету, например:

listen = /var/run/php5-fpm.sock

то данные идут через unix-сокет, и можно пропустить этот раздел.

Почему все таки стоит перейти на unix-сокет? UDS (unix domain socket), в отличии от комуникции через стек TCP, имеют значительные преимущества:

  • не требуют переключение контекста, UDS используют netisr)
  • датаграмма UDS записываться напрямую в сокет назначения
  • отправка дейтаграммы UDS требует меньше операций (нет контрольных сумм, нет TCP-заголвоков, не производиться маршрутизация)

TCP средняя задержка: 6 us UDS средняя задержка: 2 us PIPE средняя задержка: 2 us TCP средняя пропускная способность: 253702 msg/s UDS средняя пропускная способность: 1733874 msg/s PIPE средняя пропускная способность: 1682796 msg/s

Таким образом, у UDS задержка на ~66% меньше и пропускная способность в 7 раз больше TCP. Поэтому, скорей всего стоит перейти на UDS. В моем случае сокет будет расположен по адресу /var/run/php5-fpm.sock .

; закоментируем это - listen = 127.0.0.1:9000 listen = /var/run/php5-fpm.sock

Также следует убедиться что веб-сервер (или любой другой процесс, которому необходима коммуникация) имеет доступ на чтение/запись в ваш сокет. Для этого существуют настройки listen.grup и listen.mode Проще всего - запускать оба процесса от одного пользователя или группы, в нашем случае php-fpm и веб-сервер будет запущен с группой www-data :

listen.owner = www-data listen.group = www-data listen.mode = 0660

Проверяем выбранный механизм обработки событий

Для работы с эффективной работы с I/O (вводом-выводом, дескрипторами файлов/устройств/сокетов) стоит проверить правильно ли указана настройка events.mechanism . В случае если php-fpm установлен из системного репозитория, скорей всего там все в порядке - он либо не указан (устанавливаться автоматически), либо указан корректно.

Его значение зависит от ОС, для чего есть подсказка в документации:

; - epoll (linux >= 2.5.44) ; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0) ; - /dev/poll (Solaris >= 7) ; - port (Solaris >= 10)

К примеру если мы работаем на современном linux-дистрибутивe нам необходим epool:

events.mechanism = epoll

Выбор типа пула - dynamic / static / ondemand

Также, стоит обратить внимание на настройки менеджер процессов (pm). По сути это главный процесс (master process), который будет управлять всеми дочерними (которые выполняют код приложения) по определенной логике, которая собственно и описана в файле конфигурации.

Всего доступно 3 схемы управления процессами:

  • dynamic
  • static
  • ondemand

Наиболее простой - это static . Схема его работы заключается в следующем: запустить фиксированное количество дочерних процессов, и поддерживать их в рабочем состоянии. Данная схема работы не очень эффективна, так как количество запросов и их нагрузка может меняться время от времени, а количество дочерних процессов нет - они всегда занимают определенный объем ОЗУ и не могут обрабатывают пиковые нагрузки в порядке очереди.

dynamic пул позволят решить эту проблему, он регулирует количество дочерних процессов исходя из значений конфигурационного файла, изменяя их в большую или меньшую сторону, в зависимости от нагрузки. Данный пул больше всего подходит для сервера приложений, в котором необходима быстрая реакция на запрос, работа с пиковой нагрузкой, требуется экономия ресурсов (за счет уменьшения дочерних процессов при простое).

ondemand пул очень похож на static , но он не запускает дочерних процессов при старте главного процесса. Только когда придет первый запрос - будет создан первый дочерний процесс, и по истечении определенного времени ожидания (указанного в конфигурации) он будет уничтожен. Потому он актуален для серверов с ограниченными ресурсами, или той логики которая не требует быстрой реакции.

Утечки памяти и OOM killer

Следует обратить внимание на качество приложений которые будут выполняться дочерними процессами. Если качество приложения не очень высоко, или используются множество сторонних библиотек, то необходимо подумать о возможных утечках памяти, и установить значения таким переменным:

  • pm.max_requests
  • request_terminate_timeout

pm.max_requests это максимальное количество запросов, которое обработает дочерний процесс, прежде чем будет уничтожен. Принудительное уничтожение процесса позволяет избежать ситуации в которой память дочернего процесса “разбухнет” по причине утечек (т.к процесс продолжает работу после от запроса к запросу). С другой стороны, слишком маленькое значение приведет к частым перезапускам, что приведет к потерям в производительности. Стоит начать с значения в 1000, и далее уменьшить или увеличить это значение.

request_terminate_timeout устанавливает максимальное время выполнения дочернего процесса, прежде чем он будет уничтожен. Это позволяет избегать долгих запросов, если по какой-либо причине было изменено значение max_execution_time в настройках интерпретатора. Значение стоит установить исходя из логики обрабатываемых приложений, скажем 60s (1 минута).

Настройка dynamic пула

Для основного сервера приложения, ввиду явных преимуществ, часто выбирают dynamic пул. Его работа описана следующими настройками:

  • pm.max_children - максимальное количество дочерних процессов
  • pm.start_servers - количество процессов при старте
  • pm.min_spare_servers - минимальное количество процессов, ожидающих соединения (запросов для обработки)
  • pm.max_spare_servers - максимальное количество процессов, ожидающих соединения (запросов для обработки)

Для того чтобы корректно установить эти значения, необходимо учитывать:

  • сколько памяти в среднем потребляет дочерний процесс
  • объем доступного ОЗУ

Выяснить среднее значение памяти на один php-fpm процесс на уже работающем приложении можно с помощью планировщика:

# ps -ylC php-fpm --sort:rss S UID PID PPID C PRI NI RSS SZ WCHAN TTY TIME CMD S 0 1445 1 0 80 0 9552 42588 ep_pol ? 00:00:00 php5-fpm

Нам необходимо среднее значение в колонке RSS (размер резидентной памяти в килобайтах). В моем случае это ~20Мб. В случае, если нагрузки на приложения нет, можно использовать Apache Benchmark, для создания простейшей нагрузки на php-fpm.

Объем общей / доступной / используемой памяти можно посмотреть с помощью free :

# free -m total used free ... Memory: 4096 600 3496

Total Max Processes = (Total Ram - (Used Ram + Buffer)) / (Memory per php process) Всего ОЗУ: 4Гб Используется ОЗУ: 1000Мб Буфер безопасности: 400Мб Память на один дочерний php-fpm процесс (в среднем): 30Мб Максимально возможное кол-во процессов = (4096 - (1000 + 400)) / 30 = 89 Четное количество: 89 округлили в меньшую сторону до 80

Значение остальных директив можно установить исходя из ожидаемой нагрузки на приложение а также учесть чем еще занимается сервер кроме работы php-fpm (скажем СУБД также требует ресурсов). В случае наличия множества задач на сервере - стоит снизить к-во как начальных / максимальных процессов.

К примеру учтем что на сервере находиться 2 пула www1 и www2 (к примеру 2 веб-ресурса), тогда конфигурация каждого из них может выглядеть как:

pm.max_children = 40 ; 80 / 2 pm.start_servers = 15 pm.min_spare_servers = 15 pm.max_spare_servers = 25