УрокУскоряем работу с memcached. Сокеты, PECL Memcached, настройки.
В рамках подробного изучения внутренней кухни мемкэша я обнаружил несколько нюансов, которые позволяют существенно увеличить количество запросов в секунду (request per second) к данным, хранящимся в мемкэше, а также повысить эффективность использования оперативной памяти. По итогам моих экспериментов я выделил 4 основных пункта, каждый из которых существенно влияет на производительность:
1. Доступ к демону осуществляется быстрее через сокеты (что, в принципе, и не удивительно).
2. Библиотика PECL Memcached работает быстрее, чем PECL Memcache (а вот это для меня оказалось сюрпризом).
3. Настройки для библиотек по-умолчанию использовать нельзя, надо конфигурировать самостоятельно.
4. Настройки для демона memcached также необходимо подстраивать под сервер и используемую среду, дефолтные никуда не годятся.
Давайте теперь рассмотрим эти пункты более подробно.
Важно: вся конфигурация представлена для работы под CentOS. Для других систем настройки и расположения файлов могут незначительно отличаться.
Часть первая. Использование сокетов
Не секрет, что сокеты работают быстрее, чем сетевой протокол. К тому же поменять сетевой протокол на сокет достаточно просто, и не требует никаких существенных телодвижений, кроме небольших правок в конфигурационном файле демона.
По-умолчанию настройки демона мемкэша лежат в файле /etc/sysconfig/memcached и выглядят следующим образом:
PORT="11211" USER="memcached" MAXCONN="64" CACHESIZE="64" OPTIONS=""
Будем полагать, что наш сайт более-менее нагружен, и под мемкэш мы можем выделить хотя бы 2 гб памяти. Таким образом, подключение сокета вместо tcp соединения будет выглядеть следующим образом:
PORT=0 USER="memcached" MAXCONN="1024" CACHESIZE="2048" OPTIONS="-a 0766 -s /var/run/memcached/memcached.socket"
После сохранения настроек перезапустите memcached:
service memcached restart
Теперь надо настроить модуль Memcache Storage на работу с нашим сокетом. Для этого достаточно в settings.php дописать такую настройку:
$conf['memcache_servers'] = array( 'unix:///var/run/memcached/memcached.socket' => 'default', );
Вот и вся хитрость. Уже после этих настроек вы получите прирост в скорости работы с мемкэшем. Однако сокеты всё же имеют какой-то предел на количество одновременных подключений к нему. И если нагрузка на него слишком большая, то он может стать бутылочным горлышком при обращении к оперативной памяти. В этом случае можно использовать несколько сокетов.
Настройка нескольких сокетов несколько сложнее. Я накидал свой конфиг для этих целей (всё тот же файл /etc/sysconfig/memcached):
#! /bin/sh # # chkconfig: - 55 45 # description: The memcached daemon is a network memory cache service. # processname: memcached # config: /etc/sysconfig/memcached # pidfile: /var/run/memcached/memcached.pid # Standard LSB functions #. /lib/lsb/init-functions # Source function library. . /etc/init.d/functions USER=memcached OPTIONS="-a 0766 -c 2048" RETVAL=0 PROG="memcached" start_instance() { echo -n $"Starting $PROG ($1): " daemon --pidfile /var/run/memcached/memcached.$1.pid memcached -d -s $3 -u $USER -m $2 -P /var/run/memcached/memcached.$1.pid $OPTIONS RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/memcached.$1 } stop_instance() { echo -n $"Stopping $PROG ($1): " killproc -p /var/run/memcached/memcached.$1.pid /usr/bin/memcached RETVAL=$? echo if [ $RETVAL -eq 0 ] ; then rm -f /var/lock/subsys/memcached.$1 rm -f /var/run/memcached.$1.pid fi } start () { # insure that /var/run/memcached has proper permissions if [ "`stat -c %U /var/run/memcached`" != "$USER" ]; then chown $USER /var/run/memcached fi # start 2 socket streams for memcached. start_instance default 2048 /var/run/memcached/memcached.socket0; start_instance page 512 /var/run/memcached/memcached.socket1; } stop () { stop_instance default; stop_instance page; } restart () { stop start } # See how we were called. case "$1" in start) start ;; stop) stop ;; status) status memcached ;; restart|reload|force-reload) restart ;; condrestart) [ -f /var/lock/subsys/memcached ] && restart || : ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart}" exit 1 esac exit $?
Этот файл запускает 2 демона мемкэша, каждый из которых работает через свой сокет. Для того, чтобы поменять количество запущенных демонов и объём их оперативной памяти, достаточно поменять параметры здесь:
# start 2 socket streams for memcached. start_instance default 2048 /var/run/memcached/memcached.socket0; start_instance page 512 /var/run/memcached/memcached.socket1;
Давайте коротко расскажу о смысле этих строк:
- start_instance - название функции, которая запускает работу memcached.
- default - название сущности. Может быть любым.
- 2048 - количество памяти в мегабайтах, которое выделится для работы демона.
- /var/run/memcached/memcached.socket0 - путь к сокету. Для добавления ещё одного сокета просто поменяйте последнюю цифру.
Ну и конечно, если добавляете ещё одну сущность мемкэша, не забудьте добавить в конфиг её остановку:
stop () { stop_instance default; stop_instance page; }
Для двух сокетов настройки Memcache Storage в settings.php будут выглядеть так:
# Add multiple memcached instances. $conf['memcache_servers'] = array( 'unix:///var/run/memcached/memcached.socket0' => 'default', 'unix:///var/run/memcached/memcached.socket1' => 'page', ); # Set reference between cache bins and memcache server names. $conf['memcache_bins'] = array( 'cache_page' => 'page', );
Для более подробного мануала внимательно читайте README.
Часть вторая. PECL Memcached vs PECL Memcache
Изначально я был уверен, что PECL Memcache быстрее, чем PECL Memcached. Подтверждения этому я находил в тестах, проведённых различными разработчиками на просторах интернета. Однако потом мудрые люди натолкнули меня на идею попробовать сделать тесты самостоятельно. В результате оказалось, что PECL Memcached несколько быстрее своего аналога. А если его ещё и правильно настроить - он оказывается существенно быстрее.
Обратите внимание, что минимальная версия PECL Memcached, которая работает с Memcache Storage - 2.0.1. При использовании более старых версий я гаратнировать стабильность и скорость работы не берусь.
Помимо установки расширения PECL Memcached на сервере (если на нём уже стоит PECL Memcache), в settings.php надо указать, какое из расширений использовать в качестве связующего звена между клиентом и демоном memcached:
# Set current extension. $conf['memcache_extension'] = 'Memcached';
Часть третья. Настройка PECL Memcached.
Поигравшись со всеми настройками расширения pecl memcached, я получил наиболее оптимальный набор, который даёт прирост в скорости:
$conf['memcache_options'] = array( Memcached::OPT_TCP_NODELAY => TRUE, Memcached::OPT_BINARY_PROTOCOL => TRUE, Memcached::OPT_BUFFER_WRITES => TRUE, Memcached::OPT_NO_BLOCK => TRUE );
Подсказка: на некоторых серверах Memcached::OPT_BINARY_PROTOCOL лучше поставить в FALSE.
Часть четвёртая. Настройка демона memcached.
У memcached есть масса собственных настроек, с которыми его можно запускать. Понаблюдав за нескольми серверами, на которых стоит мемкэш и их взаимодействием с Друпалом, я вывел свой конфиг, который приносит хороший результат как в производительности, так и в эффективном использовании памяти:
#! /bin/sh # # chkconfig: - 55 45 # description: The memcached daemon is a network memory cache service. # processname: memcached # config: /etc/sysconfig/memcached # pidfile: /var/run/memcached/memcached.pid # Standard LSB functions #. /lib/lsb/init-functions # Source function library. . /etc/init.d/functions USER=memcached OPTIONS="-a 0766 -c 2048 -f 1.07 -n 512 -L" RETVAL=0 PROG="memcached" start_instance() { echo -n $"Starting $PROG ($1): " daemon --pidfile /var/run/memcached/memcached.$1.pid memcached -d -s $3 -u $USER -m $2 -P /var/run/memcached/memcached.$1.pid -v 2>> /var/log/memcached/memcached.$1.log $OPTIONS RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/memcached.$1 } stop_instance() { echo -n $"Stopping $PROG ($1): " killproc -p /var/run/memcached/memcached.$1.pid /usr/bin/memcached RETVAL=$? echo if [ $RETVAL -eq 0 ] ; then rm -f /var/lock/subsys/memcached.$1 rm -f /var/run/memcached.$1.pid fi } start () { # insure that /var/run/memcached has proper permissions if [ "`stat -c %U /var/run/memcached`" != "$USER" ]; then chown $USER /var/run/memcached fi # start 3 socket streams for memcached. start_instance default 2048 /var/run/memcached/memcached.socket0; start_instance page 1024 /var/run/memcached/memcached.socket1; } stop () { stop_instance default; stop_instance page; } restart () { stop start } # See how we were called. case "$1" in start) start ;; stop) stop ;; status) status memcached ;; restart|reload|force-reload) restart ;; condrestart) [ -f /var/lock/subsys/memcached ] && restart || : ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart}" exit 1 esac exit $?
В этом конфигурационном файле запускается 2 сокета - один для кэша страниц, второй для остального кэша. Это позволяет мне отдавать кэш анонимам и авторизованным пользователям через разные сокеты, что ведёт к увеличению пропускной способности.
Теперь о настройках, которые я добавлял:
- -a 0766: маска доступа к файлу с сокетом. По идее это оптимальный вариант. На 0755 мемкэш ругается, что нехватает прав на работу с файлом, а 0777 уж слишком небезопасно.
- -c 2048: количетво одновременных подключений к мемкэшу. Учитывая, что это число дублируется для каждого сокета, мне пока хватает. И не думаю, что скоро придётся это число менять.
- -f 1.07: фактор роста размера chunk'ов. Не буду вдаваться в подробности фрагментации памяти мемкэша, но смысл такой: чем меньше этот показатель, тем эффективнее использование памяти (уменьшается показатель wasted). Однако в то же время, чем меньше этот показатель, тем чаще из памяти начинают выбрасываться записи кэша, у которых не истёк срок годности (eviction per second). Поэтому важно словить баланс между количеством памяти и этим показателем, при котором эффективность будет максимальная. Для себя я его нашёл: параметр -f колеблется от 1.07 до 1.1 на разных Drupal проектах.
- -n 512: минимальный размер chunk'a, с которого начинается умножение на фактор роста (-f). Учитывая что Друпал в основном хранит массивы, оставлять значение по умолчанию (64 байта) крайне неэффективно. Опять же, как показывает мой опыт - на более-менее серьёзном проекте практически нет кэшированных данных, размером меньше 500 байт. А необходимости в бесполезной трате памяти я не вижу.
- -L: попытка использование больших страниц памяти. При этой настройке мемкэш пытается объеденить всю предоставленную ему память в один большой chunk. Этот параметр позволяет улучшить производительность при работе с мемкэшем. Однако он может быть недоступен на некоторых операционных системах.
- -v 2>> /var/log/memcached/memcached.$1.log: логирование ошибок и уведомлений при работе с мемкэшем. Может быть полезным, если его начинает штормить по непонятным причинам.
Если мой конфиг не запустится с первого раза, попробуйте сделать 2 вещи:
1. Уберите параметр -L из него (как я уже говорил - он не везде работает).
2. Создайте директорию memcached в /var/log.
А теперь о настройках Memcache Storage для эффективной работы:
Исходя из вышеуказанных настроек, а также учитывая опыт снижения нагрузки на сервер для анонимных пользователей, мой текущий конфиг settings.php выглядит таким образом:
# Move cache data info memcache storage. $conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc'; $conf['cache_default_class'] = 'MemcacheStorage'; $conf['cache_class_cache_form'] = 'DrupalDatabaseCache'; # Configure Memcache Storage module. $conf['memcache_storage_debug'] = TRUE; $conf['memcache_storage_wildcard_invalidate'] = 60 * 60 * 24 * 5; // 5 days. # Add multiple memcached instances. $conf['memcache_servers'] = array( 'unix:///var/run/memcached/memcached.socket0' => 'default', 'unix:///var/run/memcached/memcached.socket1' => 'page', ); # Set reference between cache bins and memcache server names. $conf['memcache_bins'] = array( 'cache_page' => 'page', ); # Set custom expiration for cached pages. $conf['memcache_storage_page_cache_custom_expiration'] = TRUE; $conf['memcache_storage_page_cache_expire'] = 0; // Never. # Set current extension. $conf['memcache_extension'] = 'Memcached'; # Configure memcached extenstion. $conf['memcache_options'] = array( Memcached::OPT_TCP_NODELAY => TRUE, Memcached::OPT_BINARY_PROTOCOL => TRUE, Memcached::OPT_BUFFER_WRITES => TRUE, Memcached::OPT_NO_BLOCK => TRUE ); # Avoid database connection for cached pages. $conf['page_cache_without_database'] = TRUE; $conf['page_cache_invoke_hooks'] = FALSE; # Move storage for locking mechanism into memcache. $conf['lock_inc'] = 'sites/all/modules/memcache_storage/includes/lock.inc';
По поводу последней строки конфига - да, Memcache Storage уже позволяет вынести механизм блокировки потоков в memcached :)
Итог. Тесты.
По итогу работы я накидал небольшой тест, в котором сравниваю производительность модулей Memcache Storage, Memcache API при их работе с PECL Memcache и PECL Memcached, с использованием сокетов и без них.
Тест выглядит так:
$bins = array('cache', 'cache_path', 'cache_filter', 'cache_bootstrap', 'cache_page'); $data = array_fill(0, 100, rand()); timer_start('cache_set'); for ($i = 0; $i < 2000; $i++) { foreach ($bins as $bin) { cache_set($bin . '_' . $i, $data, $bin); } } print '10 000 sets: ' . timer_read('cache_set') . ' ms<br>'; timer_start('cache_get'); for ($i = 0; $i < 2000; $i++) { foreach ($bins as $bin) { cache_get($bin . '_' . $i, $bin); } } print '10 000 gets: ' . timer_read('cache_get') . ' ms<br>'; timer_start('cache_clear_all'); for ($i = 0; $i < 2000; $i++) { foreach ($bins as $bin) { cache_clear_all($bin . '_' . $i, $bin); } } print '10 000 deletes: ' . timer_read('cache_clear_all') . ' ms<br>';
А вот так выглядят результаты тестов:
Memcache Storage (PECL Memcache) 10 000 sets: 1232.38 ms 10 000 gets: 1317.51 ms 10 000 deletes: 819.4 ms Memcache Storage (PECL Memcache) + UNIX SOCKET 10 000 sets: 1079.81 ms 10 000 gets: 1083.23 ms 10 000 deletes: 670.9 ms Memcache Storage (PECL Memcached) 10 000 sets: 381.98 ms 10 000 gets: 1010.02 ms 10 000 deletes: 145.95 ms Memcache Storage (PECL Memcached) + UNIX SOCKET 10 000 sets: 352.63 ms 10 000 gets: 908.82 ms 10 000 deletes: 142.94 ms Memcache API (PECL Memcache) 10 000 sets: 1745.43 ms 10 000 gets: 1543.33 ms 10 000 deletes: 890.73 ms Memcache API (PECL Memcache) + UNIX SOCKET 10 000 sets: 1547.99 ms 10 000 gets: 1348.77 ms 10 000 deletes: 712.43 ms Memcache API (PECL Memcached) 10 000 sets: 446.28 ms 10 000 gets: 1067.4 ms 10 000 deletes: 170.53 ms Memcache API (PECL Memcached) + UNIX SOCKET **Not working** MySQL (Percona) + fun :) 10 000 sets: 33322.87 ms 10 000 gets: 2457.66 ms 10 000 deletes: 22653.61 ms
Как видите, наиболее быстрой на данный момент является связка Memcache Storage + PECL Memcached + unix sockets.
- Spleshka
- 21.04.2013
- 62051
Комментарии
Статьи становятся все интереснее и полезнее для нагруженных сайтов. Автор - молодец :)
На open-server не пробовали такое повторить ?
На винду нельзя поставить pecl memcached. Да и само название "unix socket" подразумевает использование другой платформы. Поэтому - нет, по вполне понятным причинам.
Немного поправил скрипт запуска memcached для Debian.
Debian memcached socket start script
Подскажите, как правильно пользоваться отладкой.
Выставляю параметр $conf['memcache_storage_debug'] = TRUE;
Внизу страниц появляется большая таблица со статистикой. Но у меня почему-то 100% промахи постоянно. Включал просмотр отладки для анонимных пользователей. Там тоже 100% промахи. При этом никаких ошибок на сайте нет. Memcached 2.1.0 установлен.
Короче, ситуация оказалось смешной до слез. Когда Вы написали эту статью, я сразу обратился к своему хостинг провайдеру с вопросом, где и как подключается memcached. Мне объяснили. Я подключил. Настроил Друпал по вашим рекомендациям. И вроде сайт начал быстрее работать. А вот вчера решил все-таки разобраться, работает ли memcached. Почему всегда 100% промахи. Причина оказалась неожиданной:
- К сожалению, на виртуальном хостинге мы не предоставляем memcached из-за соображения безопасности. - получил сегодня от хостера.
Memcache Storage (PECL Memcache) на пока не сильно нагруженном сайте
10 000 sets: 1466.87 ms
10 000 gets: 623.17 ms
10 000 deletes: 783.24 ms
Тот же сайт, что и комментом выше:
10 000 sets: 761.84 ms
10 000 gets: 209.21 ms
10 000 deletes: 222.77 ms
Вроде, все работает, memcache выводит
memcache
memcache support => enabled
memcache.allow_failover => 1 => 1
memcache.chunk_size => 8192 => 8192
memcache.default_port => 11211 => 11211
memcache.default_timeout_ms => 1000 => 1000
memcache.hash_function => crc32 => crc32
memcache.hash_strategy => standard => standard
memcache.max_failover_attempts => 20 => 20
Registered save handlers => files user memcache
В admin/reports/status/php memcache есть
но попытки теста memcache (на разных сайтах примеры php-кода есть) проваливаются - белый экран выводится.
Я в этих делах, мягко говоря, не опытен; вроде, по цифрам, выходит, что работает все как надо, да?
Привет. Спасибо за статью.
Пробовал вариант с одним сокетом.
Вроде, кеширует (включил дебаг), но на скорость загрузки страниц это никак не влияет. Все те же 150ms.
Вот кусок из переделанного /etc/memcached.conf (у меня дебиан):
--------------------------
...
# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default
# Note that the daemon will grow to this size, but does not start out holding this much
# memory
-m 256
# Default connection port is 11211
#-p 11211
# UNIX socket path to listen on (disables network support)
-s /var/run/memcached.socket
# access mask for UNIX socket, in octal (default: 0700)
-a 0766
# chunk size growth factor (default: 1.25)
-f 1.07
# Run the daemon as root. The start-memcached will default to running as root if no
# -u command is present in this config file
-u root
# Specify which IP address to listen on. The default is to listen on all IP addresses
# This parameter is one of the only security measures that memcached has, so make sure
# it's listening on a firewalled interface.
# -l 127.0.0.1
...
------------------------
и вот, что добавил в settings.php
// MEMCACHED
# Move all cached data (except form cache) to memcache storage.
$conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc';
$conf['cache_default_class'] = 'MemcacheStorage';
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
$conf['cache_class_cache_update'] = 'DrupalDatabaseCache';
# Advanced usage of Drupal page cache.
$conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.page_cache.inc';
$conf['cache_class_cache_page'] = 'MemcacheStoragePageCache';
# Enable storing of plain HTML text instead of Drupal usual cache object.
$conf['memcache_storage_external_page_cache'] = TRUE;
$conf['memcache_extension'] = 'Memcached';
$conf['memcache_storage_debug'] = TRUE;
$conf['memcache_servers'] = array(
'unix:///var/run/memcached.socket' => 'default',
);
-------------
... и ничего. Накешировал целых два метра, но воз и ныне там. 150ms.
Такое подозрение, что страницы не кешируются. Какие Cache bin или Cache ID должны быть у главной страницы?
Изменения в конфигурации nginx (http://drupalace.ru/lesson/otdayom-kesh-anonimov-bez-podnyatiya-bekenda-drupal-7-nginx-memcached) тоже ничего не дало (с тем же одним сокетом):
---------------------
location / {
...
## MEMCACHED
default_type text/html;
add_header X-Nginx-Page-Cache HIT;
set $memcached_key "cache_page-$scheme://$server_name$uri$is_args$args";
memcached_pass unix:/var/run/memcached.socket;
#proxy_intercept_errors on;
#error_page 404 502 = @drupal;
## First we try the URI and relay to the /index.php?q=$uri&$args if not found.
try_files $uri @drupal;
}
########### Security measures ##########
## Restrict access to the strictly necessary PHP files. Reducing the
## scope for exploits. Handling of PHP code and the Drupal event loop.
location @drupal {
add_header X-Nginx-Page-Cache MISS;
...
---------------------
Короче говоря, что-то я туплю робяты. Голова идет кругом. Помогите кто чем может. :)
p.s.: Жду ответа, как соловей лета.
Виктор, кэш Nginx + memcached - это кэш для анонимных пользователей. То есть если в админке включена галочка "Кэшировать страницы для анонимных пользователей", то анонимам страница должна отдаваться из кэша.
То, что у вас страница отдаётся за 150мс - это отлично. Значит, что сайт достаточно свежий и лёгкий, и нет большой нагрузки на выборку из базы данных. Есть вещи в производительности, которые заметны только на больших нагрузках. На простых сайтах не всегда есть смысл ставить мемкэш, тем более, если без него работает так же.
P.S. Конфиги правильные.
>Однако сокеты всё же имеют какой-то предел на количество одновременных подключений к нему.
Можно подробнее раскрыть тему максимального количества подключений к UNIX-сокету? Сколько их может быть?
Информация по настройке Hugepages (параметр -L у memcached): https://wiki.debian.org/Hugepages, http://www.peuss.de/node/67
Вопрос проверки работы связки Drupal + Memcache Storage, если включить в модуле $conf['memcache_storage_debug'] = TRUE; то судя по данным модуль работает. Вопрос - почему при попытке сделать curl -sIXGET site.com получаем
X-Drupal-Cache: MISS ?
кто-кто, а друпалисты знают о кэше все)
Комментировать