text-media / shm-cache
Shared Memory ReadOnly Cache
Requires
- php: >=7.0
Requires (Dev)
- ext-memcached: *
- jamm/memory: ^1.0
- phpunit/phpunit: ^7.1
- squizlabs/php_codesniffer: ^3.1
This package is auto-updated.
Last update: 2024-10-10 18:07:47 UTC
README
Кэш типа "ключ -> значение" на основе разделяемой памяти.
Предназначен для работы большого количества одновременно запущенных скриптов с большим блоком статичных неизменяемых данных:
- один скрипт по расписанию или какому-то событию "прогревает" кэш, т.е. читает откуда-то данные, приводит их к нужному виду и сохраняет в памяти;
- множество других скриптов читают данные из памяти, т.е. не тратят ни ресурсы процессора на чтение и приведение к нужному виду, ни память на хранение одних и тех же общих для всех них данных.
Настройка
Для настройки кэша необходимо создать класс-потомок абстракции \TextMedia\ShmCache\Behavior
.
В нем необходимо обязательно переопределить следующие константы:
Константа | Описание |
---|---|
PROJECT_ID | Идентификатор проекта. Это должен быть один символ (см. http://php.net/manual/ru/function.ftok.php). |
CACHE_SIZE | Размер блока разделяемой памяти. Может быть задан целым числом байт или строкой типа: 1m, 100k и т.п. Должен быть не менее 1 Кб. |
VALUE_SIZE | Определяет, сколько нужно байт для хранения длины упакованных данных. Может меняться в пределах от 1 до 4. |
VALUE_SIZE
должен подбираться из расчета длины хранимых значений. Если это просто число или небольшой текст
(не более 255 ASCII
-символов) - достаточно указать 1 байт.
CACHE_SIZE
следует выбирать исходя из суммы следующих значений:
- число записей в кэше, умноженное на 1 - столько байт потребуется для хранения длин ключей;
- суммарная длина строковых представлений всех ключей;
- число записей, умноженное на
VALUE_SIZE
- столько нужно для хранения длин значений; - суммарная длина строковых представлений всех значений.
Строковые представления значений не должны быть представлены в виде результата выполнения var_export
и т.п.,
т.к. такие данные имеют избыточную информацию, в частности - имена ключей.
Например, при сохранении массива вида ['x' => число1, 'y' => число2]
достаточно сохранить только числа в бинарном виде,
а при чтении из памяти приводить их к нужному типу и формировать массив нужного вида.
Для этого в классе необходимо переопределить следующие методы:
Метод | Описание |
---|---|
string packData(string $key, $data) | Упаковка элемента данных в строку для записи. |
mixed unpackData(string $key, string $packed) | Распаковка прочитанных из памяти данных в исходную структуру. |
Значение $key
передается в оба метода, т.к. оно может повлиять на алгоритм упаковки/распаковки.
Например, если хранимые данные имеют вид массива со следующими полями:
user_id
: число, на запись которого нужно не более 2 байт;name
: строка, длиной не более 255 символов;desc
: строка.
use TextMedia\ShmCache\Behavior;
class MyCacheBehavior extends Behavior
{
public function packData(string $key, $data): string
{
return (self::packNumber($data['user_id'], 2)
. self::packNumber(strlen($data['name'], 1))
. $data['name']
. $data['desc']);
}
public function unpackData(string $key, string $packed)
{
$nameLength = self::unpackNumber(substr($packed, 2, 1));
return [
'user_id' => self::unpackNumber(substr($packed, 0, 2)),
'name' => substr($packed, 3, $nameLength),
'desc' => substr($packed, 3 + $nameLength),
];
}
}
Если метод packData()
должен прости привести к строке, лучше использовать parent::packData()
,
т.к. он проверяет, можно ли значение привести к строки, и выбрасывает исключение, если нельзя.
Это позволит избежать фатальных ошибок.
Метод unpackData()
при распаковке данных может проверятьо их валидность, соответствие каким-то своим шаблонам,
и в случае ошибки - выбрасывать исключение типа \TextMedia\ShmCache\Exception
(см. раздел "Ошибки"),
которое будет перехвачено основным объектом, в следствии чего будут выполнены следующие действия:
- кэшу будет выставлен статус "поврежден";
- вызовется метод
onCorrupt
- по умолчанию он ничего не делает, его можно переопределить; - исключение будет проброшено выше, т.е. работа скрипта будет прекращена.
Так же этот класс обязательно должен определять метод getData()
, необходимый для "прогрева" кэша.
Этот метод должен возвращать ArrayObject
для сохранения в кэше в виде "ключ-значение". Например:
use ArrayObject;
use TextMedia\ShmCache\Behavior;
class MyCacheBehavior extends Behavior
{
public function getData(): ArrayObject
{
return new ArrayObject($this->database->getQueryResult('SELECT user_id, name, desc FROM users', 'user_id'));
}
}
Класс \TextMedia\ShmCache\Behavior
содержит следующие статичные методы,
которые можно использовать для упаковки/распаковки данных:
Метод | Описание |
---|---|
string packNumber(int $number, int $size) | Упаковка числа в последовательность символов ($size - число байт). |
int unpackNumber(string $string) | Распаковка последовательности символов в число. |
Обработчики событий
Класс-потомок \TextMedia\ShmCache\Behavior
может переопределять поведение при возникновении некоторых событий
(все три метода необязательны для переопределения и по умолчанию ничего не делают):
Метод | Событие |
---|---|
onCorrupt | В процессе обработки данных, прочитанных из кэша, произошла ошибка. |
onEmpty | При попытке чтения данных из кэша выяснилось, что он (кэш) пуст. |
onWarmed | Завершен "прогрев" кэша. |
onIndexed | Завершено формирование таблицы индексов (смещений). |
Первым аргументом для каждого метода является объект-кэш класса \TextMedia\ShmCache\Cache
,
при работе с которым произошло данное событие.
Для обработчика onCorrupt
дополнительными аргументами являются:
Аргумент | Тип | Описание |
---|---|---|
$key | string | Ключ, данные по которому не удалось обработать. |
$value | string | Данные, прочитанные из памяти. |
Обработчики onWarmed
и onIndexed
имеет дополнительно два аргумента $onStart
и $onReady
;
оба - являются обычными объектами со следующими полями:
Поле | Тип | Описание |
---|---|---|
time | float | Когда был запущен/завершен процесс (microtime(true)). |
memory | integer | Используемая память на момент запуска/завершения (memory_get_peak_usage(true)). |
records | integer | Число записанных/прочитанных записей (на момент старта = 0). |
size | integer | Сколько байт было записано/прочитано (на момент старта = 0); без учета размера заголовка кэша. |
Обработчик onEmpty
дополнительных аргументов не имеет и по умолчанию вызывается обработчиком onCorrupt
(если он не переопределен никак иначе),
т.к. очевидно, что итоговое поведение в обоих случаях должно быть одно - исправить содержимое кэша, "прогреть" его заново.
Использование
Для работы с кэшем необходимо создать экземпляр класса \TextMedia\ShmCache\Cache
,
передав в его конструктор объект-наследник \TextMedia\ShmCache\Behavior
.
Для "прогрева" кэша используется метод warmup()
, имеющий один необязательный параметр,
указывающий на то, нужно ли сперва полностью вычистить все данные из памяти, т.е. забить их 0-ми.
По умолчанию этот параметр имеет значение TRUE
.
Для чтение данных из разделяемой памяти доступны следующие методы:
Метод | Описание |
---|---|
mixed getItem(string $key) | Чтение по одному ключу ключу. |
array getItems(array $keys) | Чтение по множеству ключей. На выходе массив вида "ключ --> значение". |
Вторым аргументом в метод getItems()
можно передать bool $ignoreMissing
(по умолчанию = TRUE
),
который указывает, нужно ли игнорировать отсутствующие в таблице индексов ключи или все же выбрасывать исключения.
Если этот аргумент равен TRUE
и было выброшено исключение с кодом FAILED_SEARCH_KEY
(см. раздел "Ошибки"),
то значение не попадет в результирующий массив; в остальных случаях - исключение будет проброшено вверх.
Ошибки
Все вышеперечисленные классы в случае ошибок выбрасывают исключения типа \TextMedia\ShmCache\Exception
.
Каждому типу ошибок назначен свой код - константа данного класса:
Константа | Значение | Описание ошибки |
---|---|---|
INVALID_PROJECT_ID | 1 | Неправильное значение идентификатора проекта. |
FAILED_GET_SHM_ID | 2 | Не удалось определить идентификатор блока разделяемой памяти. |
INVALID_CACHE_SIZE | 3 | Неправильное значение размера блока разделяемой памяти. |
INVALID_VALUE_SIZE | 4 | Неправильное значение размера длины строкового представления данных. |
FAILED_OPEN_SHMOP | 5 | Не удалось открыть блок разделяемой памяти. |
FAILED_DELETE_SHMOP | 6 | Не удалось удалить блок разделяемой памяти. |
INVALID_STATUS_VALUE | 7 | Неправильное значение статуса. |
FAILED_READ_SHMOP | 8 | Ошибка чтения из разделяемой памяти. |
FAILED_WRITE_SHMOP | 9 | Ошибка записи в разделяемую память. |
FAILED_SEARCH_KEY | 10 | Указанный ключ отсутствует в таблице. |
CACHE_NOT_READY | 11 | Кэш не готов к чтению. |
FAILED_PACK_VALUE | 12 | Не удалось привести значение к строке. |
FAILED_UNPACK_VALUE | 13 | Не удалось распаковать значение из строки. |
Отладка
Для включения режима отладки необходимо вызвать метод Cache::setDebugMode()
с аргументом TRUE
;
для отключения - его же, но с аргументом FALSE
.
Режим отладки включает сохранение данных о времени выполнения операций и используемой для этого памяти. Отслеживаются следующие операции:
Название | Описание |
---|---|
CACHE CLEAN | Очистка кэша. |
CACHE WARMUP | "Прогрев" кэша. |
TABLE CREATION | Формирование таблицы индексов. |
OFFSET SEARCH | Поиск по таблице индексов. |
Данные отладки могут быть получены методом Cache::getDebugData()
, который возврашает массив,
список которого являются массивы со следующими полями:
Поле | Описание |
---|---|
action | Выполненное действие (см. таблицу выше). |
time | Затраченное время (секунды, до 8 знаков после запятой). |
memory | Сколько в итоге было максимально использовано памяти (в байтах). |
Тесты
cd /path/to/package
vendor/bin/phpunit
Кроме проверки записи/чтения в разделяюмую память, тесты включают в себя сравнение производительности по сравнению с Memcached
:
- запускается отдельный скрипт, который:
- очищает и разделяемую память, и
memcached
; - запускает 300 процессов (поровну для всех типов кэша); запущенные процессу "висят" в памяти, ожидая "прогрева" кэша;
- выполняет "прогрев" - все типы кэшей заполняются 200 одинаковыми элементами;
- очищает и разделяемую память, и
- запущенные скрипты выполняют 10000 запросов к кэшу к рандомным элемнтам;
- по завершении работы всех скриптов выводится:
- среднеее время работы отдельного процесса;
- максимальное;
- минимальное;
- время "прогрева" кэша.
Тест производительности активен по умолчанию, но может быть отключен следующим образом:
export PHP_UNIT='--no-performance' && vendor/bin/phpunit
Если тест производительности не был отключен и либо "завис", либо прерван, перед запуском следующего теста необоходимо "убить" запущенные ранее процессы:
pkill -f "TestPerformance.php"