Search by

cloud-castle / memcached

alex-4-17

Клиент Memcached для PHP 8.1+ на чистом PHP без расширений: текстовый, бинарный и meta-протокол, ketama-шардирование с failover, пул соединений, SASL и TLS, PSR-6 и PSR-16, теги, компрессия, CAS, счётчики, метрики, встроенный тестовый сервер.

v1.0.4 2026-07-31 05:50 UTC

README

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano

CloudCastle Memcached

CloudCastle Memcached

Клиент Memcached для PHP 8.1+ на чистом PHP — без ext-memcached и ext-memcache.

Packagist

Packagist Version Downloads PHP Version License

Репозиторий

GitVerse Issues Wiki

Качество кода

PHPStan Psalm PHPMD PHPCS Deptrac Coverage Infection MSI OpenSSF Scorecard

Зачем он нужен

Штатный способ работать с Memcached из PHP — расширение ext-memcached поверх libmemcached. Оно быстрое, но его нужно собирать под каждую версию PHP, и на управляемом хостинге или в чужом контейнере его может просто не быть.

Этот пакет реализует протокол Memcached на PHP: composer require — и клиент работает. Заодно становятся возможными вещи, которых расширение не даёт: подпись значений против подмены, meta-команды Memcached 1.6+ и тестовый сервер внутри процесса.

Установка

composer require cloud-castle/memcached

Требуется PHP 8.1+. Расширения не обязательны: igbinary, msgpack, zstd, lz4 подключаются автоматически, если собраны. Если собрано ext-memcached, клиент может работать через него — см. «Выбор бэкенда».

Быстрый старт

use CloudCastle\Memcached\Client;
use CloudCastle\Memcached\Configuration\{Config, Server};

$client = new Client(new Config([new Server('127.0.0.1', 11211)]));

$client->set('user:1', ['name' => 'Иван'], 300);
$user = $client->get('user:1');

// Значение возвращается того же типа, каким было записано:
// false не превратится в пустую строку, а записанный null отличим от промаха.
$client->set('flag', false);
var_dump($client->get('flag'));   // bool(false)
var_dump($client->has('flag'));   // bool(true)

Вычислить значение при промахе:

$report = $client->remember('report:2026-07', fn () => buildHeavyReport(), 3600);

Кластер с весами и переключением при отказе:

$client = new Client(new Config([
    new Server('10.0.0.1', 11211, weight: 2, alias: 'node-1'),
    new Server('10.0.0.2', 11211, weight: 1, alias: 'node-2'),
]));

Интеграция по стандартам PSR

Код, написанный под Psr\SimpleCache\CacheInterface или Psr\Cache\CacheItemPoolInterface, работает с этим клиентом без правок:

use CloudCastle\Memcached\Psr\{CacheItemPool, SimpleCache};

$psr16 = new SimpleCache($client);
$psr16->set('ключ', $value, 300);

$psr6 = new CacheItemPool($client);
$item = $psr6->getItem('ключ');

if (!$item->isHit()) {
    $item->set(buildValue())->expiresAfter(300);
    $psr6->save($item);
}

Разница между стандартами стоит того, чтобы её знать: PSR-16 не отличает записанный null от промаха, а PSR-6 отличает — через isHit(). Родной API клиента различает всегда, поэтому там, где кэшируются отрицательные ответы, лучше использовать его напрямую.

Выбор бэкенда

Операции выполняет один из двух исполнителей: собственная реализация протокола на PHP либо расширение ext-memcached поверх libmemcached.

use CloudCastle\Memcached\Configuration\{Backend, Config, Server, Topology};

// Расширение, если оно собрано; иначе — чистый PHP. Одна сборка
// приложения работает и на боевом сервере, и на управляемом хостинге.
$client = new Client(new Config(
    [new Server('127.0.0.1', 11211)],
    topology: new Topology(backend: Backend::Auto),
));

Что выбрать — зависит от нагрузки, и цифры честные:

НагрузкаЧистый PHPlibmemcached
5000 циклов set + get1239 мс1411 мс
500 пакетов по 100 ключей453 мс176 мс

На одиночных операциях быстрее собственный протокол: getMulti с CAS-токенами обходится дороже, чем один кадр meta. На пакетном чтении втрое выигрывает расширение — сотню ответов оно разбирает в C, а не в PHP.

Записи бэкенда libmemcached побайтово совместимы с symfony/cache и другими обёртками над расширением: сериализация оставлена самому расширению. Обратная сторона — формат отличается от собственного кодека пакета, поэтому смена бэкенда требует прогрева кэша. Подпись значений и meta-команды на этом бэкенде недоступны: расширение их не поддерживает.

Возможности

Каждая возможность описана отдельной страницей wiki с примерами и сравнением с аналогами.

ВозможностьЧто даёт
Два бэкендачистый PHP или libmemcached, с автовыбором по окружению
Три диалекта протоколатекстовый, бинарный и meta-команды Memcached 1.6+
Meta-командызначение, TTL и версия CAS за одно обращение
Кластер и ketamaконсистентное хеширование, совместимое с libmemcached
Отказоустойчивостьпереключение на живой узел и карантин упавших
СериализацияPHP, JSON, igbinary, MessagePack + белый список классов
Сжатиеdeflate, gzip, zstd, lz4 с порогом и проверкой выгоды
Подпись значенийHMAC против подмены записи в кэше
Безопасностьзащита от инъекции команд, SASL, TLS
Счётчики и CASатомарные операции на стороне сервера
Тестовый сервертесты без сети и без запуска memcached
Метрикипопадания, промахи и здоровье узлов
PSR-16 и PSR-6интеграция с фреймворками без правок кода

Сравнение с аналогами

Все таблицы ниже и бейджи качества выше генерируются автоматически из замеров и отчётов инструментов: composer docs:build. Ни одна цифра не написана руками — зашитое в разметку число устаревает после первого же добавленного теста и начинает врать читателю.

Функциональность

ВозможностьCloudCastlesymfonyilluminatescrapbookphpfastcachestashlaminasext-memcached¹
Работает без расширений PHP (чистый PHP)✅———————
PSR-16 (SimpleCache) из коробки✅✅✅✅✅—✅—
PSR-6 (CacheItemPool) из коробки✅✅—✅✅✅✅—
Выбор бэкенда: чистый PHP или libmemcached✅———————
Автовыбор бэкенда по наличию расширения✅———————
Текстовый протокол✅✅✅✅✅✅✅✅
Бинарный протокол✅✅✅✅✅—✅✅
Meta-команды Memcached 1.6+✅———————
Чтение TTL и CAS одним запросом✅———————
Консистентное хеширование ketama✅✅✅✅✅✅✅✅
Автоматическое переключение при отказе узла✅——————✅
Карантин упавших узлов✅———————
SASL-аутентификация✅✅✅✅✅—✅✅
TLS-соединение✅✅✅✅✅—✅✅
UNIX-сокеты✅✅✅✅✅✅✅✅
Оптимистическая блокировка (CAS)✅——✅——✅✅
Атомарные счётчики с созданием при промахе✅——————✅
append / prepend без чтения значения✅——————✅
Подпись значений (HMAC) против подмены✅———————
Белый список классов при восстановлении✅✅——————
Выбор формата сериализации (4 формата)✅✅——✅—✅✅
Выбор алгоритма сжатия (4 алгоритма)✅✅————✅✅
Встроенный тестовый сервер без сети✅———————
Инъектируемые часы (детерминированный TTL)✅✅——————
Счётчики попаданий и промахов клиента✅———✅———
Итого возможностей2512791041112
🏆 Победитель🏆

Безопасность

Механизм защитыCloudCastlesymfonyilluminatescrapbookphpfastcachestashlaminasext-memcached¹
Отказ на управляющие символы в ключе (инъекция команды)✅✅—✅✅—✅✅
Ключ не обрезается молча при превышении длины✅✅——————
Белый список классов при восстановлении значения✅———————
Лимит длины нагрузки при разборе (защита от исчерпания памяти)✅———————
Подпись значений HMAC с обнаружением подмены✅———————
Строгий режим: неподписанная запись отвергается✅———————
TLS с полной проверкой сертификата по умолчанию✅✅✅✅✅—✅✅
Пароль SASL не попадает в дампы и трассировки✅———————
Учётные данные не сериализуются✅———————
Fail-safe: отказ узла не выдаётся за промах кэша✅———————
Итого возможностей103122022
🏆 Победитель🏆

Производительность

set + get на общем сервере Memcached, 20 000 раз (минимум из 3).

ПакетВремя🏆 Победитель
ext-memcached¹ (базовый уровень)3 972.4 мс
illuminate4 649.4 мс🏆
phpfastcache5 213.5 мс
scrapbook5 286.4 мс
symfony5 822.3 мс
stash9 082.3 мс
laminas9 388.1 мс
CloudCastle10 271.6 мс

Потребление памяти

Память самой библиотеки (классы + структуры данных): пик рабочей фазы минус baseline, снятый до создания клиента в изолированном процессе, — стоимость PHP и автолоадера вычтена.

ПакетПамять🏆 Победитель
ext-memcached¹ (базовый уровень)1 KB
scrapbook96 KB🏆
illuminate188 KB
stash396 KB
symfony431 KB
CloudCastle695 KB
laminas1 396 KB
phpfastcache2 772 KB

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

_Рост памяти за 20 000 операций после прогрева и gc_collectcycles (изолированный процесс, только целевая библиотека; 0 — утечек нет).

ПакетРост🏆 Победитель
stash-17 KB🏆
CloudCastle0 KB
symfony0 KB
illuminate0 KB
scrapbook0 KB
phpfastcache0 KB
laminas0 KB
ext-memcached¹ (базовый уровень)0 KB

Качество кода

МетрикаCloudCastlesymfonyilluminatescrapbookphpfastcachestashlaminas🏆 Победитель
Синтаксические ошибки (phplint)0000000CloudCastle, symfony, illuminate, scrapbook, phpfastcache, stash, laminas 🏆
Файлы со strict_types, %1000010099019CloudCastle, scrapbook 🏆
final-классы, %1009001054CloudCastle 🏆
Runtime-зависимостей4542217stash 🏆
Файлов исходников938944401423374stash 🏆
Минимальная версия PHP>=8.1>=8.1^8.1>=8.0.0>=8.0^8.0~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0—

¹ Базовый уровень: расширение на C, не composer-библиотека. Показано для контекста и не претендует на победу среди PHP-пакетов.

Возможности проверены по исходникам и документации пакетов. Отметка «есть» ставится и тогда, когда возможность приходит из расширения под пакетом: пользователю она доступна.

Замер выполнен 2026-07-30 на PHP 8.1.34.

Честно о плюсах и минусах

Плюсы

  • Работает без расширений: composer require — и всё, никакой пересборки PHP.
  • Функционально шире любого аналога: meta-команды, подпись значений, карантин узлов, встроенный тестовый сервер — этого нет ни у одного из сравниваемых.
  • Два бэкенда под одним API: без расширения работает чистый PHP, с расширением — libmemcached, и пакетное чтение ускоряется втрое.
  • Стандарты PSR-16 и PSR-6 из коробки — пакет встаёт в любой фреймворк без переписывания прикладного кода.
  • Безопасность заложена в поведение: инъекция команды через ключ невозможна, восстановление объектов ограничено белым списком, отказ узла не выдаётся за промах кэша.
  • Тестируемость: клиент проверяется целиком, включая обрывы связи и повреждённые ответы, без поднятия сервера.

Минусы

  • На пакетных операциях чистый PHP уступает расширению втрое. Разбор сотни ответов в PHP объективно медленнее, чем в C. Лечится переключением на бэкенд libmemcached там, где расширение доступно, — но тогда теряются подпись значений и meta-команды.
  • Памяти на процесс тратится больше, чем у обёрток над расширением: структуры протокола живут в PHP, а не в C.
  • Подпись значений несовместима с серверными счётчиками: подписанное число сервер не умеет инкрементировать.

Когда его стоит брать

Берите, если:

  • расширение ext-memcached недоступно или его установка — отдельная боль (управляемый хостинг, чужой контейнер, быстрый прототип);
  • в кэше лежат данные, подмена которых опасна: права доступа, результаты проверок, флаги доступности — тогда подпись значений окупает всё;
  • нужны meta-команды Memcached 1.6+: чтение значения вместе с остатком TTL экономит целый цикл запросов при досрочном продлении;
  • важна тестируемость кэширующего слоя без инфраструктуры в CI;
  • кластер меняет состав, и нужен предсказуемый перенос ключей плюс карантин отказавших узлов.

Не берите, если:

  • узкое место — именно скорость обращений к кэшу, расширение уже установлено, и никакой дополнительный функционал не нужен: тогда ext-memcached напрямую или тонкая обёртка над ним будут быстрее;
  • нужен только PSR-16 поверх нескольких бэкендов сразу — для этого лучше подходит cloud-castle/cache.

Разработка

composer install
composer check       # линтеры + статический анализ + тесты
composer test:full   # полный порядок проверок качества
composer docs:build  # перегенерировать сравнительные таблицы и бейджи

Интеграционные тесты требуют сервер; адрес задаётся через MEMCACHED_HOST и MEMCACHED_PORT. Без сервера они помечаются пропущенными, а не «зелёными».

docker run -d --name memcached -p 11211:11211 memcached:1.6-alpine

Документация

Лицензия

MIT © CloudCastle (alex-4-17@yandex.ru)

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano