Memcached — это система кэширования объектов с распределенной памятью, разработанная danga.com (технической командой, которая управляет LiveJournal) для снижения нагрузки на базу данных и повышения производительности в динамических системах. Что касается этой вещи, я считаю, что многие люди использовали ее. Цель этой статьи — получить более глубокое понимание этого превосходного программного обеспечения с открытым исходным кодом посредством реализации и анализа кода memcached, а также дальнейшей его оптимизации в соответствии с нашими потребностями. Наконец, посредством анализа расширения BSM_Memcache мы углубим наше понимание использования memcached.
Некоторое содержание этой статьи может потребовать более качественной математической основы.
◎Что такое Memcached
Прежде чем подробно остановиться на этом вопросе, мы должны сначала понять, чем он «не является». Многие люди используют его в качестве носителя данных, например SharedMemory. Хотя memcached использует тот же метод «Ключ => Значение» для организации данных, он сильно отличается от локальных кэшей, таких как общая память и APC. Memcached является распределенным, а значит, не локальным. Он завершает работу службы на основе сетевого подключения (конечно, он также может использовать localhost). Это независимая от приложения программа или демон-процесс (режим демона).
Memcached использует библиотеку libevent для реализации служб сетевых подключений и теоретически может обрабатывать неограниченное количество соединений. Однако, в отличие от Apache, он чаще ориентирован на стабильные непрерывные соединения, поэтому его реальные возможности параллелизма ограничены. В консервативных условиях максимальное количество одновременных подключений для memcached составляет 200, что связано с возможностями потоков Linux. Это значение можно изменить. Информацию о libevent можно найти в соответствующей документации. Использование памяти Memcached также отличается от APC. APC основан на общей памяти, а MMAP имеет собственный алгоритм распределения памяти и метод управления. Он не имеет ничего общего с общей памятью и не имеет ограничений на общую память. Обычно каждый процесс memcached может управлять 2 ГБ пространства памяти. требуется больше места, количество процессов можно увеличить.
◎Для каких случаев подходит Memcached?
Во многих случаях memcached подвергался злоупотреблениям, что, конечно, неизбежно приводит к жалобам на него. Я часто вижу, как люди пишут на форумах типа «как повысить эффективность», а отвечают «использовать memcached». Что касается того, как его использовать, где его использовать и для чего он используется, нет никакого предложения. Memcached не является панацеей и не подходит для всех ситуаций.
Memcached — это «распределенная» система кэширования объектов в памяти. То есть для тех приложений, которые не требуют «распределения», не требуют совместного использования или просто достаточно малы, чтобы иметь только один сервер, memcached не подойдет. Напротив, это также снижает эффективность системы, поскольку сетевые соединения также требуют ресурсов, даже локальных соединений UNIX. Мои предыдущие тестовые данные показали, что скорость локального чтения и записи memcached в десятки раз медленнее, чем у прямых массивов памяти PHP, в то время как методы APC и разделяемой памяти аналогичны прямым массивам. Видно, что если это только кеш локального уровня, то использование memcached очень неэкономично.
Memcached часто используется в качестве внешнего кэша базы данных. Поскольку он требует гораздо меньше синтаксического анализа SQL, дисковых операций и других накладных расходов, чем база данных, и использует память для управления данными, он может обеспечить более высокую производительность, чем непосредственное чтение базы данных. В больших системах очень сложно получить доступ к одним и тем же данным. Зачастую memcached может значительно снизить нагрузку на базу данных и повысить эффективность работы системы. Кроме того, memcached часто используется в качестве носителя данных для обмена данными между серверами. Например, данные, которые сохраняют статус единого входа в систему в системе SSO, могут быть сохранены в memcached и совместно использованы несколькими приложениями.
Следует отметить, что memcached использует память для управления данными, поэтому она нестабильна. При перезапуске сервера или завершении процесса memcached данные будут потеряны, поэтому memcached нельзя использовать для сохранения данных. Многие люди неправильно понимают, что производительность memcached настолько же хороша, насколько хороша память и жесткий диск. На самом деле memcached не обеспечит сотни или тысячи улучшений скорости чтения и записи за счет использования памяти. Его фактическое узкое место находится в сети. Преимущество соединения, связанного с использованием памяти, по сравнению с дисковой базой данных состоит в том, что оно очень «легкое», поскольку нет чрезмерных накладных расходов и прямых методов чтения и записи, оно может легко обрабатывать очень большой объем. обмена данными, поэтому зачастую имеется две гигабитные полосы пропускания сети. Они все полностью загружены, а сам процесс memcached не занимает много ресурсов процессора.
◎Как работает Memcached
Читателям следующих разделов лучше всего подготовить копию исходного кода memcached.
Memcached — это традиционная программа сетевой службы. Если при запуске используется параметр -d, она будет выполняться как процесс-демон. Создание процесса демона завершается daemon.c. Эта программа имеет только одну функцию демона, которая очень проста (если не указаны специальные инструкции, код должен соответствовать 1.2.1):
КОД:[Копировать в буфер обмена]#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int
демон (ночдир, ноклоуз)
int nochdir, noclose;
{
ИНТ ФД
переключатель (вилка ()) {
случай-1:
возврат (-1);
случай 0:
перерыв;
по умолчанию:
_выход (0);
}
если (setsid() == -1)
вернуть (-1);
если (!ночдир)
(void)chdir("/");
if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
если (fd > STDERR_FILENO)
(недействительно) закрыть (fd);
}
возврат (0);
}
После того, как эта функция разветвляет весь процесс, родительский процесс завершает работу, а затем перемещает STDIN, STDOUT и STDERR на пустые устройства, и демон успешно устанавливается.
Процесс запуска самого Memcached в основной функции memcached.c выглядит следующим образом:
1. Вызовите settings_init(), чтобы установить параметры инициализации.
2. Считайте параметры из команды запуска, чтобы установить значение настройки.
3. Установите параметр LIMIT.
4. Запустите мониторинг сетевых сокетов (если существует не-socketpath) (режим UDP поддерживается после версии 1.2)
5. Проверьте удостоверение пользователя (Memcached не разрешает запуск корневого удостоверения)
6. Если путь к сокету существует, откройте локальное соединение UNIX (Sock Pipe).
7. Если запущен в режиме -d, создайте процесс демона (вызовите функцию демона, как указано выше).
8. Инициализация элемента, события, информации о состоянии, хеша, соединения, плиты.
9. Если в настройках вступило в силу управление, создайте массив сегментов.
10. Проверьте, нужно ли блокировать страницу памяти.
11. Инициализация сигнала, соединение, удаление очереди.
12. В режиме демона обработать идентификатор процесса.
13. Событие запускается, процесс запуска завершается, и основная функция входит в цикл.
В режиме демона, поскольку поток stderr был направлен в черную дыру, во время выполнения не будет возвращаться никаких видимых сообщений об ошибках.
Основной функцией цикла memcached.c является диск_машина. Входящий параметр представляет собой указатель структуры, указывающий на текущее соединение, а действие определяется на основе статуса члена состояния.
Memcached использует набор пользовательских протоколов для обмена данными. Документ протокола можно найти по адресу: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt.
В API символы новой строки. унифицированы как rn
◎Метод управления памятью Memcached
Memcached имеет уникальный метод управления памятью. Чтобы повысить эффективность, он использует методы предварительного применения и группировки для управления пространством памяти вместо malloc каждый раз, когда необходимо записать данные. ., освободите указатель при удалении данных. Memcached использует метод организации slab->chunk для управления памятью.
Есть некоторые различия в алгоритмах разделения пространства плит в slabs.c версий 1.1 и 1.2, которые будут представлены отдельно позже.
Плиту можно понимать как блок памяти. Плита — это наименьшая единица памяти, которую memcached может использовать для одновременного использования памяти. В memcached размер плиты по умолчанию составляет 1048576 байт (1 МБ), поэтому memcached использует весь МБ памяти. Каждый блок разделен на несколько фрагментов, и каждый фрагмент хранит элемент. Каждый элемент также содержит структуру элемента, ключ и значение (обратите внимание, что значение в memcached представляет собой всего лишь строку). Слэбы формируют связанные списки по своим ID, и эти связанные списки вешаются на массив slabclass по своим ID. Вся структура немного напоминает двумерный массив. Длина slabclass составляет 21 в 1.1 и 200 в 1.2.
slab имеет начальный размер фрагмента, который составляет 1 байт в версии 1.1 и 80 байт в версии 1.2. В версии 1.2 есть значение коэффициента, которое по умолчанию равно 1,25.
В версии 1.1 размер фрагмента выражается как начальный размер * 2^n, n равно. classid, то есть: плита с идентификатором 0 имеет размер фрагмента 1 байт, плита с идентификатором 1 имеет размер фрагмента 2 байта, плита с идентификатором 2 имеет размер фрагмента 4 байта... плита с идентификатором 20 имеет размер чанка 4 байта. Размер составляет 1 МБ, что означает, что в плите есть только один чанк с идентификатором 20:
КОД: [Копировать в буфер обмена]void slabs_init (предел размера_t) {
интервал я;
int size = 1;
mem_limit = предел;
for(i=0; i<=POWER_LARGEST; i++, size*=2) {
slabclass[i].size = размер;
slabclass[i].perslab = POWER_BLOCK / размер;
slabclass[i].slots = 0;
slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;
slabclass[i].end_page_ptr = 0;
slabclass[i].end_page_free = 0;
slabclass[i].slab_list = 0;
slabclass[i].list_size = 0;
slabclass[i].killing = 0;
}
/* для набора тестов: имитация того, сколько мы уже выполнили malloc */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
если (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* предварительно выделяем плиты по умолчанию, если только переменная среды
для тестирования установлено значение, отличное от нуля */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate (лимит / POWER_BLOCK);
}
}
}
В версии 1.2 размер чанка выражается как начальный размер * f^n, f — это фактор, который определен в memcached.c, а n — это класс. В то же время не все 201 головку необходимо инициализировать, поскольку фактор. является переменной, и инициализация зацикливается только до Вычисленный размер достигает половины размера плиты и начинается с id1, то есть: плита с идентификатором 1, размер каждого фрагмента составляет 80 байт, плита с идентификатором 2, размер каждого фрагмента составляет 80* f, идентификатор равен 3 слябам, размер каждого фрагмента равен 80*f^2, а размер инициализации имеет корректирующее значение CHUNK_ALIGN_BYTES для обеспечения n-байтового выравнивания (гарантируя, что результат является целым кратным CHUNK_ALIGN_BYTES). Таким образом, при стандартных обстоятельствах memcached1.2 будет инициализирован с идентификатором 40. Размер каждого фрагмента в этом блоке равен 504692, и в каждом блоке имеется два фрагмента. Наконец, функция slab_init добавит в конец id41, который представляет собой целый блок, то есть в этом блоке есть только один чанк размером 1 МБ:
КОД: [Копировать в буфер обмена]void slabs_init (предел size_t, двойной коэффициент) {
интервал я = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size
/* Коэффициент 2,0 означает использование поведения memcached по умолчанию */
if (коэффициент == 2,0 && размер < 128)
размер = 128;
mem_limit = предел;
memset(slabclass, 0, sizeof(slabclass));
while (++i <POWER_LARGEST && size <= POWER_BLOCK / 2) {
/* Убедитесь, что элементы всегда выровнены по n-байтам */
если (размер % CHUNK_ALIGN_BYTES)
размер += CHUNK_ALIGN_BYTES - (размер % CHUNK_ALIGN_BYTES);
slabclass[i].size = size;
slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
размер *= коэффициент;
если (settings.verbose > 1) {
fprintf(stderr, "класс плиты %3d: размер фрагмента %6d perslab %5dn",
я, slabclass[i].size, slabclass[i].perslab);
}
}
power_largest = я;
slabclass[power_largest].size = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
/* для набора тестов: подделка того, сколько мы уже выполнили malloc */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
если (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
#ifndef DONT_PREALLOC_SLABS
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate (лимит / POWER_BLOCK);
}
}
#endif
}
Как видно из вышеизложенного, выделение памяти memcached является избыточным. Когда плита не делится на размер чанка, которым она владеет, оставшееся пространство в конце плиты отбрасывается. Например, в id40 занимают два чанка. 1009384 байт, общий размер этого блока составляет 1 МБ, поэтому 39192 байта тратятся впустую.
Memcached использует этот метод для выделения памяти, чтобы быстро найти идентификатор класса по длине элемента. Он немного похож на хэш, поскольку длину элемента можно вычислить. Например, в версии 1.2 длина элемента составляет 300 байт. Вы можете получить, что он должен храниться в блоке id7, потому что в соответствии с приведенным выше методом расчета размер чанка id6 составляет 252 байта, размер чанка id7 — 316 байт, а размер чанка id8 — 396 байт, это означает, что все элементы размером от 252 до 316 байт должны храниться в id7. Аналогично, в версии 1.1 также можно вычислить, что оно находится между 256 и 512 и должно быть помещено в id9 с размером chunk_size, равным 512 (32-битная система).
При инициализации Memcached будут инициализированы плиты (как вы могли видеть ранее, в основной функции вызывается slabs_init()). Он проверит константу DONT_PREALLOC_SLABS в slabs_init(). Если она не определена, это означает, что плита инициализируется с использованием заранее выделенной памяти, так что плита создается для каждого идентификатора среди всех определенных классов плит. Это означает, что 1.2 выделит 41 МБ дискового пространства после запуска процесса в среде по умолчанию. Во время этого процесса происходит вторая избыточность памяти memcached, поскольку возможно, что идентификатор вообще не использовался, но это тоже A. slab применяется по умолчанию, и каждый slab будет использовать 1 МБ памяти.
Когда slab израсходован и необходимо вставить новый элемент с этим идентификатором, он повторно подаст заявку на новый slab. slab, соответствующий идентификатор. Связанный список slab будет расти в геометрической прогрессии.
КОД:[Копировать в буфер обмена]static int Grove_slab_list (беззнаковый идентификатор int) {
slabclass_t *p = &slabclass[id];
if (p->slabs == p->list_size) {
size_t new_size = p->list_size ? p->list_size * 2: 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
если (new_list == 0) вернуть 0;
p->list_size = new_size;
р->slab_list = new_list;
}
возврат 1;
}
При поиске элемента используется функция slabs_clsid. Входящий параметр — это размер элемента, а возвращаемое значение — classid. Из этого процесса видно, что третья избыточность памяти memcached возникает в процессе сохранения элемента. Элемент всегда меньше или равен размеру чанка. Когда элемент меньше размера чанка, пространство снова теряется.
◎Алгоритм Memcached NewHash
Хранилище элементов Memcached основано на большой хэш-таблице. Ее фактический адрес — это смещение фрагмента в плите, но его позиционирование зависит от результата хеширования ключа, который находится в первичной_хэш-таблице. Все операции хеширования и элементов определены в файлах assoc.c и items.c.
Memcached использует алгоритм NewHash, который очень эффективен и эффективен. Есть некоторые различия между NewHash в 1.1 и 1.2. Основной метод реализации остался прежним. Хеш-функция 1.2 была организована и оптимизирована, а ее адаптивность стала лучше.
Ссылка на прототип NewHash: http://burtleburtle.net/bob/hash/evahash.html . Математики всегда немного странные, ха-ха~
Чтобы облегчить преобразование, определены два типа данных: u4 и u1, u4 — длинное целое число без знака, а u1 — символ без знака (0–255).
Конкретные коды см. в пакетах исходного кода 1.1 и 1.2.
Обратите внимание на длину хэш-таблицы. Здесь также есть разница между 1.1 и 1.2. В версии 1.1 константа HASHPOWER определена как 20, а длина таблицы хеш-таблицы равна хеш-размеру (HASHPOWER), что составляет 4 МБ (хеш-размер — это макрос, указывающий). что 1 сдвинута вправо на n бит). В 1.2 это переменная 16, то есть длина хеш-таблицы равна 65536:
КОД:[Копировать в буфер обмена]typedef unsigned long int ub4 /* 4-байтовые величины без знака */;
typedef unsigned char ub1; /* 1-байтовые величины без знака */
#define hashsize(n) ((ub4)1<<(n))
#define hashmask(n) (hashsize(n)-1)
В assoc_init() будет инициализирована таблица Primary_hashtable. Соответствующие хэш-операции включают в себя: assoc_find(), assoc_expand(), assoc_move_next_bucket(), assoc_insert(), assoc_delete(), соответствующие операциям чтения и записи элемента. Среди них assoc_find() — это функция, которая находит соответствующий адрес элемента на основе ключа и длины ключа (обратите внимание, что в C во многих случаях строка и длина строки передаются напрямую одновременно, вместо выполнения strlen внутри функции ), и возвращается указатель структуры элемента, его адрес данных находится в фрагменте плиты.
items.c — это программа для работы с элементами данных. Каждый полный элемент включает в себя несколько частей, которые определяются в item_make_header() как:
key: key.
nkey: длина ключа
flags: определяемый пользователем флаг (на самом деле этот флаг не включен в memcached)
nbytes: длина значения (включая символ новой строки rn)
суффикс: суффикс Буфер
nsuffix: длина суффикса
полного элемента равна длине ключа + длине значения + длине суффикса + размеру структуры элемента (32 байта). Операция элемента основана на этой длине для вычисления classid плиты.
Каждый сегмент в хэш-таблице снабжен двойным связным списком. Во время item_init() три массива с орешками, хвостами и размерами инициализируются значением 0. Размеры этих трех массивов — это константа LARGEST_ID (по умолчанию — 255, это значение требует изменения с помощью фактора), каждый раз, когда вызывается item_assoc(), он сначала пытается получить свободный фрагмент из плиты. Если доступного фрагмента нет, он сканирует связанный список 50 раз, чтобы получить ранее существовавший фрагмент. запущенный элементом LRU, отсоедините его, а затем вставьте элемент, который нужно вставить в связанный список.
Обратите внимание на член refcount элемента. После отсоединения элемента он удаляется только из связанного списка. Он не освобождается сразу. Он просто помещается в очередь удаления (функция item_unlink_q()).
Элемент соответствует некоторым операциям чтения и записи, включая удаление, обновление и замену. Конечно, наиболее важной из них является операция выделения.
Еще одна особенность элемента заключается в том, что он имеет срок действия, что является очень полезной функцией memcached. Многие приложения полагаются на срок действия элемента memcached, например, для хранения сеансов, блокировки операций и т. д. Функция item_flush_expired() сканирует элементы в таблице и выполняет операцию отсоединения для элементов с истекшим сроком действия. Конечно, это всего лишь действие по переработке. На самом деле при получении также требуется оценка времени.
КОД:[Копировать в буфер обмена]/* истекает срок действия элементов, которые более поздние, чем параметр old_live */.
void item_flush_expired() {
интервал я;
элемент *iter, *next;
если (! settings.oldest_live)
возвращаться;
для (я = 0; я <НАИБОЛЬШИЙ_ИД; я++) {
/* LRU сортируется в порядке убывания времени и временной метки элемента.
* никогда не бывает новее, чем время последнего доступа, поэтому нам нужно только пройти
* назад, пока не найдем элемент старше самого старого_живого времени.
* Самая старая проверка_live автоматически истечет срок действия оставшихся элементов.
*/
for (iter = head[i]; iter != NULL; iter = следующий) {
if (iter->time >= settings.oldest_live) {
следующий = iter-> следующий;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink (итер);
}
} еще {
/* Мы нашли первый старый элемент. Переходим к следующей очереди */.
перерыв;
}
}
}
}
КОД:[Копировать в буфер обмена]/* оболочка вокруг assoc_find, которая выполняет логику отложенного истечения срока действия/удаления */
item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
элемент *it = assoc_find(ключ, nkey);
если (delete_locked) *delete_locked = 0;
if (it && (it->it_flags & ITEM_DELETED)) {
/* он помечен как заблокированный для удаления, давайте посмотрим, соответствует ли это условие.
просрочен, а 5-секундный таймер delete_timer просто не сработал
дошёл ещё... */
если (!item_delete_lock_over(it)) {
если (delete_locked) *delete_locked = 1;
оно = 0;
}
}
if (it && settings.oldest_live && settings.oldest_live <= current_time &&
it->time <= settings.oldest_live) {
item_unlink (оно);
оно = 0;
}
if (it && it->exptime && it->exptime <= current_time) {
item_unlink (оно);
оно = 0;
}
верните его;
}
Метод управления памятью Memcached очень сложен и эффективен. Он значительно сокращает количество прямых выделений системной памяти, снижает накладные расходы на функции и вероятность фрагментации памяти. Хотя этот метод приведет к некоторым избыточным потерям, в больших системах эти потери тривиальны. приложения.
◎Метод расчета теоретических параметров Memcached
имеет несколько параметров, влияющих на работу memcached:
константа REALTIME_MAXDELTA 60*60*24*30
Максимальный срок действия 30 дней
бесплатно (= 200) в conn_init().
Максимальное количество одновременных подключений
, константа KEY_MAX_LENGTH 250
Максимальная длина ключа
settings.factor (=1,25)
фактор повлияет на размер шага чанка
settings.maxconns (=1024)
Максимальные настройки мягкого соединения.chunk_size
(=48)
Консервативно оцененная длина ключ+значение, используемая для генерации длины фрагмента (1.2) в id1. Длина фрагмента id1 равна этому значению плюс длина структуры элемента (32), которая по умолчанию равна 80 байтам.
Константа POWER_SMALLEST 1
Минимальный класс (1.2),
константа POWER_LARGEST 200
Максимальный класс (1.2),
константа POWER_BLOCK 1048576
размера плиты по умолчанию
CHUNK_ALIGN_BYTES (sizeof(void *))
Убедитесь, что размер чанка является целым числом, кратным этому значению, чтобы предотвратить выход за пределы (длина void * различна в разных системах, в стандартных 32-битных системах она равна 4)
константа ITEM_UPDATE_INTERVAL 60
Константа интервала обновления
очереди LARGEST_ID 255
Максимальное количество элементов в связанном списке (это значение не может быть меньше наибольшего идентификатора класса),
переменная хеш-мощность (постоянная HASHPOWER в версии 1.1)
Определение размера хеш-таблицы
На основе представленных выше настроек содержимого и параметров можно вычислить некоторые результаты:
1. Не существует программного верхнего предела для количества элементов, которые можно сохранить в memcached. Мое предыдущее утверждение составляло 1 миллион. неправильный.
2. Предполагая, что алгоритм NewHash имеет равномерные коллизии, количество циклов поиска элемента равно общему количеству элементов, деленному на размер хэш-таблицы (определяемый хеш-мощностью), который является линейным.
3. Memcached ограничивает максимально допустимый размер элемента 1 МБ, а данные размером более 1 МБ будут игнорироваться.
4. Использование пространства Memcached тесно связано с характеристиками данных, а также связано с константой DONT_PREALLOC_SLABS. В худшем случае 198 слябов будут потрачены впустую (все элементы сосредоточены в одном слябе, а все 199 идентификаторов полностью распределены).
◎Оптимизация Memcached с фиксированной длиной
Основываясь на описаниях в приведенных выше разделах, я получил более глубокое понимание memcached. Его можно оптимизировать только на основе глубокого понимания.
Сам Memcached предназначен для данных переменной длины. По характеристикам данных его можно назвать «публично-ориентированным». Однако во многих случаях наши данные не являются такими «универсальными». неравномерное распределение, то есть длина данных сосредоточена в нескольких областях (например, сохранение пользовательских сеансов); другое, более экстремальное состояние, — это данные одинаковой длины (например, значения ключей фиксированной длины, в основном данные фиксированной длины). виден в доступе, онлайн-статистике или блокировках выполнения).
Здесь мы в основном изучаем решение по оптимизации для данных фиксированной длины (1.2). Централизованные распределенные данные переменной длины предназначены только для справки и просты в реализации.
Чтобы решить проблему с данными фиксированной длины, первое, что необходимо решить, — это проблема распределения блоков. Первое, что необходимо подтвердить, — это то, что нам не нужно так много блоков с разной длиной фрагментов, чтобы максимально эффективно использовать их. Из ресурсов лучше всего, чтобы куски и элементы были одинаковой длины, поэтому сначала необходимо вычислить длину элемента.
Ранее существовал алгоритм расчета длины элемента. Следует отметить, что помимо длины строки необходимо добавить длину структуры элемента в 32 байта.
Предположим, мы подсчитали, что нам нужно сохранить 200 байт данных одинаковой длины.
Следующим шагом является изменение связи между идентификатором класса плиты и длиной чанка. В исходной версии существует соответствующая связь между длиной фрагмента и идентификатором класса. Теперь, если для всех фрагментов установлено значение 200 байт, то этой взаимосвязи не существует. Один из методов — использовать только фиксированный идентификатор для всей структуры хранения, то есть использовать только 1 из 199 слотов. В этом случае необходимо определить DONT_PREALLOC_SLABS, чтобы избежать дополнительных потерь при предварительном выделении. Другой метод — установить хэш-отношение для определения classid из элемента. Вы не можете использовать длину в качестве ключа. Вы можете использовать переменные данные, такие как результат ключа NewHash, или напрямую выполнять хэш на основе ключа (теперь это можно сделать с помощью хэш-связи). ключи данных фиксированной длины также должны быть одинаковой длины). Для простоты здесь мы выбираем первый метод. Недостаток этого метода в том, что используется только один идентификатор. Когда объем данных очень велик, цепочка блоков будет очень длинной (поскольку все данные переполнены). одна цепь). Стоимость прохождения относительно высока.
Три типа избыточности пространства были представлены ранее. Установка длины блока, равной длине элемента, решает первую проблему нерационального использования пространства. Отсутствие предварительной заявки на пространство решает вторую проблему ненужного пространства. А что насчет первой проблемы (оставшегося в плите)? )? Чтобы решить эту проблему, вам нужно изменить константу POWER_BLOCK так, чтобы размер каждой плиты был точно равен целому числу, кратному длине фрагмента, чтобы плиту можно было разделить на n фрагментов. Это значение должно быть ближе к 1 МБ. Если оно слишком велико, это также приведет к избыточности. Если оно слишком мало, это приведет к слишком большому количеству выделенных блоков. В соответствии с длиной фрагмента 200 выберите 1000000 в качестве значения POWER_BLOCK. таким образом размер блока составит 1 миллион байт, а не 1048576. Все три проблемы избыточности решены, и использование пространства будет значительно улучшено.
Измените функцию slabs_clsid так, чтобы она напрямую возвращала фиксированное значение (например, 1):
КОД: [Копировать в буфер обмена] unsigned int slabs_clsid (size_t size) {
возврат 1;
}
Измените функцию slabs_init, удалите часть, которая зацикливается для создания всех атрибутов classid, и напрямую добавьте slabclass[1]:
КОД:[Копировать в буфер обмена]slabclass[1].size = 200 //200 байт на фрагмент;
slabclass[1].perslab = 5000 //1000000/200;
◎Клиент Memcached
Memcached — это сервисная программа. При ее использовании вы можете подключаться к серверу memcached по его протоколу, отправлять команды процессу обслуживания, а затем управлять вышеуказанными данными. Для простоты использования в memcached имеется множество клиентских программ, соответствующих различным языкам, а также клиенты на разных языках. К числу тех, которые основаны на языке C, относятся libmemcache и APR_Memcache; к тем, которые основаны на Perl, относятся Cache::Memcached, а также поддерживаются Python, Ruby, Java, C# и другие языки; У PHP больше всего клиентов, не только два расширения mcache и PECL memcache, но и большое количество классов инкапсуляции, написанных PHP. Вот введение в использование memcached в PHP:
Расширение mcache повторно инкапсулируется на основе libmemcache. . libmemcache не выпустила стабильную версию. Текущая версия — 1.4.0-rc2, которую можно найти здесь. Очень плохая особенность libmemcache заключается в том, что она записывает много сообщений об ошибках в stderr. Обычно при использовании в качестве библиотеки stderr обычно направляется в другие места, например в журнал ошибок Apache, и libmemcache совершает самоубийство, что может привести к ненормальному состоянию. , но его производительность по-прежнему очень хороша.
Расширение mcache в последний раз обновлялось до 1.2.0-beta10. Вероятно, автор подал в отставку. Он не только перестал обновляться, но и не смог открыть сайт (~_~ Ему пришлось пойти в другое место, чтобы получить это безответственное расширение). . После распаковки метод установки обычный: phpize & configure & make & make install. Обязательно сначала установите libmemcache. Использовать это расширение просто:
КОД:[Копировать в буфер обмена]<?php
$mc = memcache(); // Создаем объект подключения к кэшу памяти. Обратите внимание, что new здесь не используется!
$mc->add_server('localhost', 11211 // Добавляем служебный процесс);
$mc->add_server('localhost', 11212 // Добавляем второй служебный процесс);
$mc->set('key1', 'Привет'); // Запись key1 => Привет.
$mc->set('key2', 'World', 10 // Запись key2 => World, срок действия истекает через 10 секунд);
$mc->set('arr1', array('Hello', 'World')); // Записываем массив
$key1 = $mc->get('key1'); // Получаем значение "key1" и присваиваем его $key1.
$key2 = $mc->get('key2'); // Получаем значение 'key2' и присваиваем его $key2. Если оно превышает 10 секунд, оно будет недоступно.
$arr1 = $mc->get('arr1'); // Получаем массив 'arr1';
$mc->delete('arr1'); // Удалить 'arr1';
$mc->flush_all(); // Удалить все данные
$stats = $mc->stats(); // Получаем информацию о сервере
var_dump($stats); // Информация о сервере представляет собой массив.
?>
Преимущество этого расширения в том, что оно позволяет легко реализовать распределенное хранилище и балансировку нагрузки, поскольку при сохранении данных они будут располагаться на определенном сервере на основе результата хэширования. Это также особенность libmemcache. . libmemcache поддерживает методы централизованного хеширования, включая CRC32, ELF и хэш Perl.
PECL memcache — это расширение, выпущенное PECL. Последнюю версию — 2.1.0, которую можно получить на веб-сайте pecl. Использование расширения memcache можно найти в некоторых новых руководствах по PHP. Оно очень похоже на mcache, очень похоже:
КОД:[Копировать в буфер обмена]<?php
$memcache = новый Memcache;
$memcache->connect('localhost', 11211) или умрет («Не удалось подключиться»);
$version = $memcache->getVersion();
echo "Версия сервера: ".$version."n"
$tmp_object = новый stdClass;
$tmp_object->str_attr = 'тест';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) или умереть («Не удалось сохранить данные на сервере»);
echo "Сохранить данные в кеше (срок действия данных истекает через 10 секунд)n"
$get_result = $memcache->get('key');
echo "Данные из кеша:n";
var_dump($get_result)
;
Это расширение использует поток PHP для прямого подключения к серверу memcached и отправки команд через сокет. Он не так полон, как libmemcache, и не поддерживает распределенные операции, такие как add_server, но поскольку он не зависит от других внешних программ, он обладает лучшей совместимостью и относительно стабилен. Что касается эффективности, то разница невелика.
Кроме того, существует множество классов PHP, таких как MemcacheClient.inc.php, и многие из них можно найти на phpclasses.org. Обычно они представляют собой повторную инкапсуляцию клиентского API Perl и используются аналогичным образом.
◎BSM_Memcache
С точки зрения клиента C APR_Memcache — это очень зрелая и стабильная клиентская программа, которая поддерживает блокировки потоков и операции на атомарном уровне для обеспечения операционной стабильности. Тем не менее, он основан на APR (APR будет представлен в последнем разделе) и не имеет такого широкого спектра приложений, как Libmemcache, в настоящее время не так много программ. потому что он не может работать вне среды APR. Тем не менее, APR может быть установлен отдельно от Apache.
BSM_MEMCACE - это расширение PHP, основанное на APR_MEMCACE, которое я разработал в BS.Magic Project. Эта программа очень проста и не выполняет слишком много функций.
В отличие от расширения MCACHE, которое поддерживает многомерное распределенное хранилище, BSM_MEMCACE поддерживает несколько групп серверов. Горячая резервная копия реализована. Конечно, стоимость реализации этой функции - это жертва производительности. Обычно вы можете получить его в следующий раз.
Bsm_memcache только поддерживает эти функции:
Код: [копирование в буфер обмена] zend_function_entry bsm_memcache_functions [] =
{
Php_fe (mc_get, null)
Php_fe (mc_set, null)
Php_fe (mc_del, null)
Php_fe (mc_add_group, null)
Php_fe (mc_add_server, null)
Php_fe (mc_shutdown, null)
{Null, null, null}
};
Функция MC_ADD_GROUP возвращает целое число (на самом деле это должен быть объект, я был ленивым ~ _ ~) в качестве идентификатора группы. Addrort).
Код: [копирование в буфер обмена]/**
*Добавить группу серверов
*/
Php_function (mc_add_group)
{
APR_INT32_T GROUP_ID;
APR_STATUS_T
RV;
{
Rong_param_count;
Return_null ();
}
group_id = free_group_id ();
if (-1 == Group_id)
{
Return_false;
}
APR_MEMCACHE_T *MC;
RV = APR_MEMCACHE_CREATE (P, MAX_G_SERVER, 0, & MC
)
;
}
Код: [копирование в буфер обмена]/**
* Добавить сервер в группу
*/
Php_function (mc_add_server)
{
APR_STATUS_T RV;
APR_INT32_T GROUP_ID;
двойной g;
char *srv_str;
int srv_str_l
;
{
Rong_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ds", & g & srv_str, & srv_str_l) == affice)
{
Return_false;
}
group_id = (APR_INT32_T) g
;
{
Return_false;
}
char *host, *scope;
PORT
;
if (apr_success == rv)
{
// Создать этот объект сервера
APR_MEMCACHE_SERVER_T *ST;
rv = APR_MEMCACHE_SERVER_CREATE (P, хост, порт, 0, 64, 1024, 600, & st);
if (apr_success == rv)
{
if (null == mc_groups [group_id])
{
Return_false;
}
// Добавить сервер
APR_MEMCACHE_ADD_SERVER
(MC_GROUPS [GROUP_ID], ST);
{
Return_true;
}
}
}
Return_false;
}
При настройке и отмене данных проберите все группы:
Код: [копирование в буфер обмена]/**
* Хранить товар во всех группах
*/
Php_function (mc_set)
{
char *key, *value;
int key_l, value_l;
Double TTL = 0;
Double set_ct = 0
;
{
Rong_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ss | d", & key, & key_l, & value, & value_l, ttl) == сбой)
{
Return_false;
}
// Записать данные в каждый объект
APR_INT32_T I = 0;
if (ttl <0)
{
ttl = 0;
}
APR_STATUS_T RV
;
{
if (0 == is_validate_group (i))
{
// Напишите это!
rv = apr_memcache_add (mc_groups [i], key, value, value_l, (Apr_uint32_t) ttl, 0);
if (apr_success == rv)
{
set_ct ++;
}
}
}
Return_double (set_ct);
}
В MC_GET вы сначала выбираете группу, а затем начинаете избираться из этой группы:
Код: [копирование в буфер обмена]/**
* Принесите элемент из случайной группы
*/
Php_function (mc_get)
{
char *key, *value = null;
int key_l;
apr_size_t
value_l;
{
Rong_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "s", & key, & key_l) == сбой)
{
Return_mull ();
}
// Я постараюсь...
// случайное чтение
APR_INT32_T curr_group_id = random_group ();
APR_INT32_T I = 0;
APR_INT32_T try = 0;
APR_UINT32_T Flag;
APR_MEMCACHE_T *OURY;
APR_STATUS_T RV
;
{
попробуйте = i + curr_group_id;
попробуйте = try % max_group;
if (0 == is_validate_group (try))
{
// Получить значение
oper = mc_groups [try];
rv = apr_memcache_getp (mc_groups [try], p, (const char *) key, & value, & value_l, 0);
if (apr_success == rv)
{
Return_string (значение, 1);
}
}
}
Return_false;
}
Код: [копирование в буфер обмена]/**
* Случайный идентификатор группы
* Для mc_get ()
*/
APR_INT32_T random_group ()
{
struct TimeVal TV;
структура часового пояса TZ;
int
usec
;
}
Использование BSM_MEMCACHECH аналогично другим клиентам:
Код: [Скопировать в буфер обмена] <? PHP
$ g1 = mc_add_group ();
$ g2 = mc_add_group ();
MC_ADD_SERVER ($ G1, 'Localhost: 11211');
MC_ADD_SERVER ($ G1, 'Localhost: 11212');
MC_ADD_SERVER ($ G2, '10 .0.0.16: 11211 ');
(
$ G2, '10 .0.0.17: 11211 ');
$ key = mc_get ('key');
mc_del ('key');
mc_shutdown ();
?>
Соответствующую информацию о APR_MEMCACE можно найти здесь, и BSM_MEMCACE может быть загружен с этого сайта.
◎ APR Environment Введение
полное имя APR: Apache Portable Stime выполнения. Это набор кроссплатформенных языковых библиотек C, созданных и поддерживаемых Apache Software Foundation. Он извлечен из Apache Httpd1.x и не зависит от HTTPD. APR предоставляет много удобных интерфейсов API для использования, включая практические функции, такие как пулы памяти, строковые операции, сети, массивы, хэш -таблицы и т. Д. Разработка модуля Apache2 требует воздействия многих функций APR.
◎ Постскриптум
Это моя последняя статья в бингксу -году лунного календаря (мой год рождения). Спасибо Sina.com за предоставление возможностей для исследования и коллег в отделе за их помощь.
Доктор NP02-13-2007