Пять простых способов оптимизации вашего LAMP-приложения

Архитектурная связка Linux, Apache, MySQL и PHP (LAMP) является на сегодня одним из самых распространенных вариантов архитектуры Web-сервера. В этой статье Джон Мертик расскажет о пяти рекомендациях по настройке оптимальной производительности LAMP-приложении.

Джон Мертик, инженер-программист, SugarCRM

Джон Мертик (John Mertic) получил диплом инженера по вычислительной технике в университете Kent State University и в настоящее время работает инженером-программистом в компании SugarCRM. Он участвовал во многих проектах open source, главным образом в РНР-проектах; является создателем и хранителем PHP Windows Installer.



07.12.2011

Введение

Архитектура LAMP лежит в основе многих Web-порталов, обрабатывающих миллионы запросов в день, таких как Wikipedia, Facebook и Yahoo! Эту архитектуру использует множество программных пакетов, таких как Wordpress, Joomla, Drupal и SugarCRM, позволяющих организациям с легкостью разворачивать Web-приложения.

Сила архитектуры LAMP заключается в ее простоте. В то время как стеки на основе технологии .NET или Java™ могут требовать мощного аппаратного обеспечения, дорогих программных стеков и сложной настройки производительности, стек LAMP может работать на рядовом «железе» и использовать программные стеки с открытым кодом. Так как программный стек не является монолитным, а представляет собой гибкий набор компонентов, то настройка производительности может стать непростой задачей, ведь каждый компонент требует индивидуального анализа и настройки.

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


Используйте кэширование опкодов

Простейший способ повышения производительности любого PHP-приложения (конечно же, буква "P" в аббревиатуре LAMP) - включить кэш опкодов. Я включаю его для всех Web-сайтов, над которыми я работаю, так как это дает большой выигрыш в производительности (часто время отклика сокращается в два раза). Многие люди, начинающие работать с PHP, задают вопрос, что обеспечивает такое серьезное улучшение. Ответ кроется в том, как PHP обрабатывает Web-запросы. На рисунке 1 изображена схема выполнения запроса в PHP.

Рисунок 1. PHP-запрос
Diagram shows the flow of a PHP request from PHP script to parse to compile to execute and finally to output

Поскольку PHP является интерпретируемым языком, а не компилируемым, как, например, C или Java, то вся процедура разбора-компиляции-выполнения сценария выполняется для каждого запроса. На это тратится немало времени и ресурсов, даже если сценарии редко изменяются между запросами. Разобранный и скомпилированный сценарий представляет собой последовательность опкодов. Именно здесь свою роль может сыграть кэш опкодов. В этом кэше скомпилированные сценарии хранятся в виде последовательностей опкодов, что позволяет устранить этапы разбора и компиляции сценария для каждого запроса. На рисунке 2 показана работа данной схемы.

Рисунок 2. PHP-запрос, использующий кэш опкодов
Flow diagram shows how the logic flow checks for cached opcodes and skips the parsing and compiling steps if they are present

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

Кэши опкодов для PHP стали популярными уже давно; первые из них появились еще в PHP V4. На сегодняшний день имеется несколько широко используемых решений, находящихся в активной разработке:

  • Alternative PHP Cache (APC) является, возможно, самым популярным механизмом кэширования опкодов для PHP (см. Ресурсы). Он разрабатывался несколькими ведущими разработчиками PHP; также в него внесли весомый вклад инженеры Facebook и Yahoo!, что улучшило его скорость и стабильность. Он предоставляет и некоторые другие возможности улучшения производительности обработки запросов в PHP, в том числе компонент для кэширования пользовательских данных, которое мы рассмотрим далее в этой статье.
  • Wincache – это механизм кэширования опкодов, который наиболее активно разрабатывается программистами команды Internet Information Services (IIS) компании Microsoft®. Он предназначен для использования исключительно на системах Windows® с Web-сервером IIS (см. Ресурсы). Он разрабатывался с целью сделать PHP первоклассной платформой разработки на стеке Windows-IIS-PHP, так как было известно, что APC на этом стеке работает не очень хорошо. По функциональности этот стек очень похож на APC; в нем имеется компонент для кэширования пользовательских данных, а также встроенный обработчик сессий.
  • eAccelerator – это ответвление одного из первоначальных кэшей PHP - Turck MMCache (см. Ресурсы).В отличие от APC и Wincache, он представляет собой только кэш опкодов и оптимизатор; в нем нет компонентов для пользовательского кэша. Он полностью совместим со стеками UNIX® и Windows и довольно популярен на сайтах, в которых не используется дополнительная функциональность, предоставляемая APC или Wincache. Он актуален в ситуациях, когда для пользовательского кэширования используется отдельный сервер кэширования, такой как memcache.

Без сомнения, кэширование опкодов является первым шагом к ускорению работы PHP за счет устранения необходимости разбора и компиляции сценария при выполнении каждого запроса. Выполнив этот шаг, вы должны заметить сокращение времени оклика и снижение нагрузки на сервер. Однако есть и другие способы оптимизации работы PHP, которые мы рассмотрим далее.


Оптимизируем конфигурацию PHP

Включение кэширования опкодов является важным средством улучшения производительности, однако есть и немало других возможностей оптимизации работы PHP, основанных на настройке параметров в файле php.ini. Эти настройки больше подходят для рабочего сервера, так как их применение в среде разработки или тестирования может усложнить отладку приложений.

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

Что нужно выключить

В файле php.ini стоит отключить несколько параметров, которые обычно используются для обеспечения обратной совместимости:

  • register_globals - по умолчанию до PHP V4.2 переменные входящего запроса автоматически помещались в обычные переменные PHP. Помимо серьезных проблем безопасности (связанных со смешиванием непроверенных данных входящего запроса с обычным содержимым PHP-переменной) это также создает дополнительные издержки при обработке каждого запроса. Поэтому выключение этого параметра повысит безопасность и улучшит производительность вашего приложения.
  • magic_quotes_* - этот параметр также является наследием PHP V4, он обеспечивает автоматическое экранирование данных, поступающих из форм. Эта функциональность задумывалась для целей безопасности, чтобы очистить входящие данные перед их отправкой в базу данных, однако ее нельзя назвать эффективной, так как она не защищает пользователей от наиболее типичных видов SQL-инъекций. Большинство баз данных поддерживают подготовленные инструкции, которые устраняют этот риск намного лучше. Поэтому для улучшения производительности этот параметр можно выключить.
  • always_populate_raw_post_data - этот параметр нужен только в случае, если вам по какой-то причине нужно видеть всю рабочую нагрузку входящего POST-запроса в нефильтрованном виде. В противном случае дубликат данных POST-запроса будет просто храниться в памяти, поэтому этот параметр можно выключить.

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

Параметры, которые нужно включить или изменить

В файле php.ini есть несколько параметров, включение которых может немного увеличить скорость работы ваших сценариев:

  • output_buffering - убедитесь, что этот параметр включен, так как благодаря ему данные будут отдаваться браузеру крупными фрагментами, а не при выполнении каждой инструкции echo или print. В противном случае время отклика может быть существенно больше.
  • variables_order - эта директива определяет порядок разбора переменных EGPCS- (Environment, Get, Post, Cookie и Server) входящего запроса. Если вы не используете суперглобальные переменные (например, переменные среды), вы можете, ничем не рискуя, их убрать, чтобы не разбирать их для каждого запроса, и получить небольшой выигрыш производительности.
  • date.timezone - эта директива, появившаяся в PHP V5.1, позволяет задать часовой пояс по умолчанию, который будет использоваться в функциях класса DateTime. Если она не задана в файле php.ini, PHP сделает несколько системных запросов, чтобы выяснить ее значение, а в PHP V5.3 при обработке каждого запроса будет выводиться предупреждение.

Эти параметры можно назвать "низко висящими фруктами", так как их надо настроить в рабочей среде в первую очередь. Также следует обратить внимание еще на одну вещь, касающуюся PHP. Это использование в вашем приложении инструкций require() и include() (а также родственных им require_once() и include_once()). Правильное использование этих инструкций позволяет предотвратить ненужные проверки статуса файлов при каждом запросе, которые могут увеличивать время отклика.


Управляем инструкциями require()s и include()s

Вызовы проверки статуса файлов (т.е. вызовы к файловой системе, проверяющие наличие того или иного файла) могут быть довольно «дорогими» для производительности приложения. Чаще всего проверки статуса файлов выполняются в инструкциях require() и include(), которые используются для включения кода в ваш сценарий. Родственные им вызовы require_once() и include_once() могут быть более сложными, так как в них нужно не только убедиться в существовании файла, но и проверить, что он не был включен ранее.

Итак, как наилучшим образом с ними работать? Есть несколько нюансов, с помощью которых можно ускорить работу с ними.

  • Используйте абсолютные пути для всех вызовов require() и include(). Благодаря этому PHP будет точно знать, какой именно файл вы хотите включить, и не нужно будет искать этот файл по всему include_path.
  • Храните в include_path небольшое количество записей. Это поможет в ситуациях, когда сложно предоставить абсолютный путь для каждого вызова require() и include() (что часто встречается в крупных старых приложениях), так как PHP не придется искать файл там, где его заведомо нет.

APC и Wincache также предоставляют механизмы кэширования результатов проверки статуса файлов, выполняемых PHP, что устраняет необходимость повторных обращений к файловой системе. Они наиболее эффективно работают, когда имена подключаемых файлов задаются статически, а не определяются во время выполнения, поэтому важно стараться по возможности задавать имена именно так.


Оптимизируем базу данных

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

Поместите базу данных на отдельную машину

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

Правильно проектируйте и индексируйте таблицы

Возможно, самые большие проблемы производительности базы данных вызваны неудачной структурой базы данных и отсутствием индексов. В типичном Web-приложении наиболее часто выполняющимися являются запросы SELECT. Также именно эти запросы являются наиболее трудоемкими для сервера базы данных. Кроме того, этот тип SQL-инструкций является наиболее чувствительным к правильному индексированию структуры базы данных, поэтому для оптимизации производительности стоит придерживаться следующих рекомендаций:

  • У каждой таблицы должен быть первичный ключ. Он предоставляет сортировку по умолчанию, а также быстрый способ объединения данной таблицы с другими таблицами.
  • Все внешние ключи таблицы (т.е. ключи, связывающие запись в таблице с какой-либо записью в другой таблице) должны быть надлежащим образом проиндексированы. Здесь может помочь то, что многие базы данных автоматически накладывают на эти ключи ограничения, гарантирующие, что запись с данным ключом действительно существует в другой таблице.
  • Старайтесь ограничивать количество столбцов в таблице. Слишком большое количество столбцов в таблице может существенно увеличить время сканирования при выполнении запросов. Помимо этого, если в таблице много столбцов, которые, как правило, не используются, то место на диске будет заниматься множеством ненужных значений NULL. Это справедливо и для полей переменного размера, например, text или blob, при использовании которых размер таблицы может вырасти намного больше, чем нужно. В данном случае следует рассмотреть возможность выделения дополнительных столбцов в другую таблицу, которую можно будет объединять с главной таблицей по первичному ключу записей.

Анализируем выполняемые на сервере запросы

Наилучшим способом улучшения производительности базы данных является анализ того, какие запросы выполняются на сервере, и как долго они выполняются. Инструменты для этого анализа есть практически в каждой базе данных. В MySQL для нахождения проблемных запросов можно использовать журнал медленных запросов. Для его использования задайте в конфигурационном файле MySQL параметру slow_query_log значение 1, параметру log_output значение FILE. Медленно выполняющиеся запросы будут заноситься в файл журнала hostname-slow.log. В параметре long_query_time можно задать в секундах пороговое значение длительности запроса, начиная с которого запрос будет считаться "медленным". Я рекомендую для начала задать значение 5 секунд, а затем постепенно, в зависимости от размера ваших данных, сокращать его до 1 секунды. Если открыть этот файл, вы увидите в нем детальную информацию о запросах, похожую на то, что показано в листинге 1.

Листинг 1. Журнал медленных запросов в MySQL
/usr/local/mysql/bin/mysqld, Version: 5.1.49-log, started with:
Tcp port: 3306  Unix socket: /tmp/mysql.sock
Time                 Id Command    Argument
# Time: 030207 15:03:33
# User@Host: user[user] @ localhost.localdomain [127.0.0.1]
# Query_time: 13  Lock_time: 0  Rows_sent: 117  Rows_examined: 234
use sugarcrm;
select * from accounts inner join leads on accounts.id = leads.account_id;

Для нас ключевым является значение Query_time, которое показывает, как долго выполнялся запрос. Также представляют интерес значения Rows_sent и Rows_examined, так как они могут указать на ситуации, когда просматривается или возвращается слишком много строк, возможно, из-за неправильно написанного запроса. Вы можете подробно изучить, как работает запрос, добавив перед ним ключевое слово EXPLAIN. В этом случае будет выведен не результат, а план запроса, как показано в листинге 2.

Листинг 2. Результаты инструкции EXPLAIN в MySQL
mysql> explain select * from accounts inner join leads on accounts.id = leads.account_id;
+----+-------------+----------+--------+--------------------------+---------+---
| id | select_type | table    | type   | possible_keys           
 | key     | key_len | ref                       | rows | Extra |
+----+-------------+----------+--------+--------------------------+---------+--------
|  1 | SIMPLE      | leads    | ALL    | idx_leads_acct_del       | NULL    | NULL    
| NULL                      |  200 |       |
|  1 | SIMPLE      | accounts | eq_ref | PRIMARY,idx_accnt_id_del | PRIMARY | 108    
| sugarcrm.leads.account_id |    1 |       |
+----+-------------+----------+--------+--------------------------+---------+---------
2 rows in set (0.00 sec)

В руководстве по MySQL вывод инструкции EXPLAIN описывается намного подробнее (см. Ресурсы), а здесь мы упомянем лишь один важный момент. Обращайте внимание на места, где в столбце 'type' находится значение 'ALL', так как в таких случаях MySQL выполняет полное сканирование таблицы без использования ключей для поиска. Это поможет вам выявить места, в которых добавление индекса существенно увеличит скорость работы.


Эффективно кэшируйте данные

Как мы видели в предыдущем разделе, базы данных легко могут стать главным узким местом производительности Web-приложения. Однако что делать, если данные, которые вы запрашиваете, не так часто меняются? В таком случае стоит хранить данные локально, а не извлекать их из базы данных при каждом запросе.

В двух механизмах кэширования опкодов, которые мы рассмотрели ранее, - APC и Wincache - предусмотрены средства и для этого. Они позволяют хранить данные PHP непосредственно в общей памяти сегмента, откуда их можно быстро извлечь. В листинге 3 приведен пример того, как это делается.

Листинг 3. Пример использования APC для кэширования данных, полученных из базы данных
<?php

function getListOfUsers()
{
    $list = apc_fetch('getListOfUsers');
    
    if ( empty($list) ) {
        $conn = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'dbuser', 'dbpass');
        $sql = 'SELECT id, name FROM users ORDER BY name';
        foreach ($conn->query($sql) as $row) {
            $list[] = $row;
        }
        
        apc_store('getListOfUsers',$list);
    }
    
    return $list;
}

Каждый запрос нам нужно будет выполнить только один раз. После этого мы помещаем результат в пользовательский кэш APC под ключом getListOfUsers. Начиная с этого момента и до окончания срока действия кэша вы можете извлечь этот результат непосредственно из кэша, не выполняя SQL-запрос.

APC и Wincache являются не единственными средствами реализации пользовательского кэша; есть также популярные решения memcache и Redis, которые не обязательно должны работать на той же машине, где работает Web-сервер. Это дает дополнительную производительность и гибкость, особенно если ваше Web-приложение «выросло» и работает на нескольких Web-серверах.


Заключение

В этой статье мы рассмотрели пять простых способов настройки LAMP-приложения с целью повышения производительности. Мы рассмотрели как приемы, касающиеся PHP, – использование кэширования опкодов и оптимизацию конфигурации PHP, - так и рекомендации по улучшению архитектуры базы данных и добавлению индексов. Также мы рассмотрели использование пользовательского кэша (на примере APC), который позволяет избежать повторяющихся запросов к базе данных в случаях, когда данные не очень часто изменяются.


Загрузка

ОписаниеИмяРазмер
Исходный кодos-5waystunelamp.zip---

Ресурсы

Научиться

Получить продукты и технологии

  • Alternative PHP Cache является, возможно, самым популярным меахнизмом кэширования опкодов для PHP. (EN)
  • Wincache – это механизм кэширования опкодов, наиболее активно разрабатываемый командой IIS компании Microsoft, предназначенный только для использования на Windows с Web-сервером IIS (Internet Information Services). (EN)
  • eAccelerator – ответвление одного из «родных» механизмов кэширования PHP, Turck MMCache. (EN)

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Linux
ArticleID=779643
ArticleTitle=Пять простых способов оптимизации вашего LAMP-приложения
publish-date=12072011