Memcached PHP 客户端
Memcached 是一个高性能的分布式内存对象缓存系统,以简单的 Key-Value 形式存储数据,专为加速动态 Web 应用而设计。本节将详细讲解 Memcached 的安装配置、PHP 客户端操作、CAS 机制、分布式部署以及与 Redis 的对比选型。
安装与配置
安装 Memcached 服务
bash
# macOS(Homebrew)
brew install memcached
# CentOS / RHEL
yum install -y memcached
# Ubuntu / Debian
apt-get install -y memcached
# Docker
docker run -d --name memcached \
-p 11211:11211 \
-m 256m \
memcached:latest -m 256
# 启动服务
memcached -d -m 256 -u root -l 127.0.0.1 -p 11211
# 参数说明:
# -d 守护进程模式
# -m 256 最大内存 256MB
# -u root 运行用户
# -l 127.0.0.1 监听地址
# -p 11211 监听端口
# -c 1024 最大并发连接数
# -t 4 工作线程数安装 PHP Memcached 扩展
bash
# 安装 libmemcached 依赖
# macOS
brew install libmemcached
# CentOS
yum install -y libmemcached libmemcached-devel
# Ubuntu
apt-get install -y libmemcached-dev
# 安装 PHP 扩展(注意是 memcached,不是 memcache)
# pecl 安装
pecl install memcached
# 或从源码编译
wget https://pecl.php.net/get/memcached-3.2.0.tgz
tar zxf memcached-3.2.0.tgz
cd memcached-3.2.0
phpize
./configure --with-php-config=$(which php-config) --disable-memcached-sasl
make && make install
# 启用扩展
echo "extension=memcached.so" >> /path/to/php.ini
# 验证安装
php -m | grep memcached
# 输出: memcached
# 查看 Memcached 扩展信息
php -i | grep memcachedmemcache vs memcached
memcache:老版本扩展,原生 PHP 实现,已停止维护memcached:基于 libmemcached 的扩展,功能更丰富,支持更多特性(CAS、二进制协议、SASL 认证等)- 推荐使用
memcached扩展
配置选项
ini
; php.ini 或 php-fpm 配置
memcached.sess_locking = On ; 会话锁(默认开启)
memcached.sess_lock_wait = 150000 ; 等待锁的最大时间(微秒)
memcached.sess_lock_max_wait = 0 ; 最大等待总时间(0=无限)
memcached.sess_prefix = "memc.sess." ; 会话 Key 前缀
memcached.compression_type = "fastlz" ; 压缩算法(fastlz/zlib)
memcached.compression_factor = 1.3 ; 压缩阈值(值大小超过存储值/compression_factor)
memcached.compression_threshold = 2000 ; 最小压缩字节数
memcached.serializer = "igbinary" ; 序列化方式(php/json/igbinary/msgpack)
memcached.use_binary_protocol = On ; 使用二进制协议(性能更好)
memcached.default_consistent_hash = On ; 一致性哈希(分布式推荐)
memcached.default_connect_timeout = 1000 ; 连接超时(毫秒)PHP 客户端操作
基础连接
php
<?php
declare(strict_types=1);
namespace App\Cache;
use Memcached;
class MemcachedClient
{
private Memcached $memcached;
public function __construct()
{
$this->memcached = new Memcached('pool');
// 添加服务器列表
$this->memcached->addServers([
['192.168.1.101', 11211, 33], // IP, 端口, 权重
['192.168.1.102', 11211, 33],
['192.168.1.103', 11211, 34],
]);
// 连接选项
$this->memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
$this->memcached->setOption(Memcached::OPT_COMPRESSION, true);
$this->memcached->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY);
$this->memcached->setOption(Memcached::OPT_HASH, Memcached::HASH_MD5);
$this->memcached->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$this->memcached->setOption(Memcached::OPT_CONNECT_TIMEOUT, 1000);
$this->memcached->setOption(Memcached::OPT_RETRY_TIMEOUT, 1);
$this->memcached->setOption(Memcached::OPT_SEND_TIMEOUT, 1000);
$this->memcached->setOption(Memcached::OPT_RECV_TIMEOUT, 1000);
$this->memcached->setOption(Memcached::OPT_POLL_TIMEOUT, 1000);
$this->memcached->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 2);
$this->memcached->setOption(Memcached::OPT_AUTO_EJECT_HOST, true);
$this->memcached->setOption(Memcached::OPT_CACHE_LOOKUPS, true);
$this->memcached->setOption(Memcached::OPT_TCP_NODELAY, true);
}
public function getClient(): Memcached
{
return $this->memcached;
}
}CRUD 操作
php
<?php
declare(strict_types=1);
namespace App\Cache;
use Memcached;
class MemcachedRepository
{
public function __construct(
private readonly Memcached $memcached
) {}
// ==================== 基础操作 ====================
/**
* 设置缓存
*/
public function set(string $key, mixed $value, int $expiration = 0): bool
{
return $this->memcached->set($key, $value, $expiration);
// $expiration = 0 表示永不过期
// $expiration > 2592000(30天)会被当作 UNIX 时间戳
// $expiration <= 2592000 会被当作相对秒数
}
/**
* 获取缓存
*/
public function get(string $key, mixed $default = null): mixed
{
$result = $this->memcached->get($key);
if ($result === Memcached::GET_NOT_FOUND) {
return $default;
}
return $result;
}
/**
* 删除缓存
*/
public function delete(string $key, int $time = 0): bool
{
return $this->memcached->delete($key, $time);
// $time > 0 时,不是立即删除,而是在 $time 秒后才删除
}
/**
* 判断 Key 是否存在
*/
public function exists(string $key): bool
{
$this->memcached->get($key);
return $this->memcached->getResultCode() === Memcached::RES_SUCCESS;
}
// ==================== 批量操作 ====================
/**
* 批量设置
*/
public function setMulti(array $items, int $expiration = 0): bool
{
return $this->memcached->setMulti($items, $expiration);
}
/**
* 批量获取
*/
public function getMulti(array $keys): array
{
return $this->memcached->getMulti($keys);
}
/**
* 批量删除
*/
public function deleteMulti(array $keys, int $time = 0): bool
{
return $this->memcached->deleteMulti($keys, $time);
}
// ==================== 高级操作 ====================
/**
* 原子递增
*/
public function increment(string $key, int $offset = 1, int $initialValue = 0, int $expiry = 0): int|false
{
$result = $this->memcached->increment($key, $offset, $initialValue, $expiry);
return $result;
}
/**
* 原子递减
*/
public function decrement(string $key, int $offset = 1, int $initialValue = 0, int $expiry = 0): int|false
{
return $this->memcached->decrement($key, $offset, $initialValue, $expiry);
}
/**
* 添加(仅在 Key 不存在时设置)
*/
public function add(string $key, mixed $value, int $expiration = 0): bool
{
return $this->memcached->add($key, $value, $expiration);
}
/**
* 替换(仅在 Key 已存在时更新)
*/
public function replace(string $key, mixed $value, int $expiration = 0): bool
{
return $this->memcached->replace($key, $value, $expiration);
}
/**
* 追加数据(追加到已有值的末尾)
*/
public function append(string $key, string $value): bool
{
return $this->memcached->append($key, $value);
}
/**
* 前置数据
*/
public function prepend(string $key, string $value): bool
{
return $this->memcached->prepend($key, $value);
}
/**
* 清除所有缓存
*/
public function flush(int $delay = 0): bool
{
return $this->memcached->flush($delay);
// $delay > 0 时,在 $delay 秒后才执行清除
}
/**
* 获取服务器统计信息
*/
public function getStats(): array
{
return $this->memcached->getStats();
}
/**
* 获取服务器版本
*/
public function getVersion(): array
{
return $this->memcached->getVersion();
}
}CAS 操作(Compare And Swap)
CAS(Compare And Swap)是一种乐观并发控制机制,用于防止并发更新导致的数据覆盖。
php
<?php
declare(strict_types=1);
namespace App\Cache;
use Memcached;
class CasRepository
{
public function __construct(
private readonly Memcached $memcached
) {}
/**
* CAS 读取(获取值和 CAS token)
*/
public function getWithCas(string $key): array|false
{
$casToken = null;
$value = $this->memcached->get($key, null, Memcached::GET_EXTENDED);
if ($value === false) {
return false;
}
return [
'value' => $value['value'],
'cas_token' => $value['cas'],
];
}
/**
* CAS 更新(仅在数据未被修改时更新)
*/
public function casUpdate(string $key, mixed $newValue, float $casToken, int $expiration = 0): bool
{
return $this->memcached->cas($casToken, $key, $newValue, $expiration);
}
/**
* 安全计数器(使用 CAS 实现无竞争计数器)
*/
public function safeIncrement(string $key, int $amount = 1): int
{
$maxRetries = 5;
for ($i = 0; $i < $maxRetries; $i++) {
$extended = $this->memcached->get($key, null, Memcached::GET_EXTENDED);
$cas = $extended['cas'] ?? 0;
$current = $extended['value'] ?? 0;
$newValue = $current + $amount;
if ($this->memcached->cas($cas, $key, $newValue)) {
return $newValue;
}
// CAS 失败,重试
usleep(1000); // 1ms
}
throw new \RuntimeException("CAS update failed after {$maxRetries} retries");
}
/**
* CAS 实现分布式锁
*/
public function acquireLock(string $lockKey, int $ttl = 10): bool
{
// add 操作是原子的,如果 key 不存在才会成功
return $this->memcached->add($lockKey, '1', $ttl);
}
/**
* 释放锁(使用 CAS 确保只释放自己的锁)
*/
public function releaseLock(string $lockKey, float $casToken): bool
{
// 使用 CAS 删除,防止误删其他请求的锁
return $this->memcached->cas($casToken, $lockKey, null);
}
}分布式部署
一致性哈希
Memcached 使用一致性哈希(Consistent Hashing)算法实现分布式存储,这是其核心优势之一。
一致性哈希环(Hash Ring)
Node A
/ \
/ \
Node D ---- Node B
\ /
\ /
Node C
Key "user:1001" → hash → 落在 Node A 上
Key "product:50" → hash → 落在 Node B 上
当 Node B 宕机时:
- 只有 Node B 上的数据需要迁移到 Node C
- Node A 和 Node D 上的数据不受影响
- 避免了全部重新哈希php
<?php
declare(strict_types=1);
namespace App\Cache;
use Memcached;
class DistributedMemcachedFactory
{
/**
* 创建生产级分布式 Memcached 客户端
*/
public static function create(array $servers): Memcached
{
$memcached = new Memcached('app_pool');
// 添加服务器(带权重)
$serverList = [];
foreach ($servers as $server) {
$serverList[] = [
$server['host'],
$server['port'],
$server['weight'] ?? 100,
];
}
$memcached->addServers($serverList);
// 一致性哈希配置
$memcached->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$memcached->setOption(Memcached::OPT_HASH, Memcached::HASH_MD5);
// 连接与故障处理
$memcached->setOption(Memcached::OPT_CONNECT_TIMEOUT, 500); // 连接超时 500ms
$memcached->setOption(Memcached::OPT_RETRY_TIMEOUT, 1); // 失败重试 1 秒
$memcached->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 2); // 2 次失败标记节点不可用
$memcached->setOption(Memcached::OPT_AUTO_EJECT_HOST, true); // 自动剔除不可用节点
$memcached->setOption(Memcached::OPT_BUFFER_WRITES, true); // 启用写缓冲
// 性能优化
$memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true); // 二进制协议
$memcached->setOption(Memcached::OPT_TCP_NODELAY, true); // 禁用 Nagle 算法
$memcached->setOption(Memcached::OPT_COMPRESSION, true); // 压缩大值
$memcached->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY);
return $memcached;
}
}
// 使用示例
$config = [
['host' => '192.168.1.101', 'port' => 11211, 'weight' => 100],
['host' => '192.168.1.102', 'port' => 11211, 'weight' => 100],
['host' => '192.168.1.103', 'port' => 11211, 'weight' => 100],
];
$memcached = DistributedMemcachedFactory::create($config);Session 存储到 Memcached
php
<?php
// php.ini 配置
// session.save_handler = memcached
// session.save_path = "192.168.1.101:11211,192.168.1.102:11211"
// 或在代码中配置
ini_set('session.save_handler', 'memcached');
ini_set('session.save_path', '192.168.1.101:11211,192.168.1.102:11211');
// 使用自定义选项
ini_set('memcached.sess_binary_protocol', '1');
ini_set('memcached.sess_consistent_hash', '1');
ini_set('memcached.sess_locking', '1');
ini_set('memcached.sess_prefix', 'sess_');
session_start();
// 之后正常使用 $_SESSION 即可
$_SESSION['user_id'] = 1001;
$_SESSION['cart'] = ['items' => []];与 Redis 对比
功能对比
| 特性 | Memcached | Redis |
|---|---|---|
| 数据类型 | 纯字符串(Key-Value) | String, Hash, List, Set, ZSet, Stream 等 |
| 持久化 | 不支持 | 支持 RDB 和 AOF |
| 内存管理 | LRU 淘汰 | 多种淘汰策略 + 淘汰日志 |
| 线程模型 | 多线程(IO 多路复用) | 单线程(IO 多路复用,Redis 6.0+ 多 IO 线程) |
| 分布式 | 客户端一致性哈希 | Redis Cluster / Sentinel / 分片 |
| 发布/订阅 | 不支持 | 支持 |
| Lua 脚本 | 不支持 | 支持 |
| 事务 | 支持 CAS | 支持 MULTI/EXEC 和 WATCH |
| 管道 | 不支持 | 支持 Pipeline |
| 内存碎片 | 较少(slab allocator) | 可能较多(可开启 activedefrag) |
| 集群 | 客户端分片 | 原生 Cluster 模式 |
| 最大内存 | 单值最大 1MB | 单值最大 512MB |
| 高可用 | 无原生支持 | Sentinel / Cluster |
| 适用场景 | 纯缓存 | 缓存 + 消息队列 + 排行榜等 |
性能对比
| 场景 | Memcached | Redis |
|---|---|---|
| 简单读写(小 Value) | 极快(~10us) | 快(~15us) |
| 批量读写 | 较快 | 非常快(Pipeline) |
| 大 Value 读写 | 不太擅长 | 擅长 |
| 并发连接 | 高(多线程) | 较高(单线程但 IO 多路复用) |
| 数据结构操作 | 不支持 | 强(原生支持各种数据结构) |
选型建议
| 场景 | 推荐 | 原因 |
|---|---|---|
| 纯缓存(简单的 KV 存储) | Memcached | 简单、高效、内存利用率高 |
| 需要丰富的数据结构 | Redis | Hash、Set、ZSet 等天然支持 |
| 需要持久化 | Redis | Memcached 不支持持久化 |
| 需要消息队列 | Redis | 支持 Stream 和 Pub/Sub |
| 需要排行榜/计数器 | Redis | ZSet 原生支持有序集合 |
| 超大并发(百万连接) | Memcached | 多线程模型更优 |
| 已有 Redis 集群 | Redis | 统一技术栈,减少运维成本 |
现代项目建议
在现代 PHP 项目中,Redis 是更常用的选择,因为它功能更丰富(数据结构、持久化、集群)、社区更活跃、生态更完善。Memcached 适合已有基础设施或对纯缓存有极致性能要求的场景。
实战示例
php
<?php
declare(strict_types=1);
namespace App\Service;
use Memcached;
class CacheService
{
public function __construct(
private readonly Memcached $memcached
) {}
/**
* 缓存装饰器模式:为任意数据查询添加缓存
*/
public function cache(string $key, callable $query, int $ttl = 3600): mixed
{
$result = $this->memcached->get($key);
if ($result !== Memcached::GET_NOT_FOUND) {
return $result;
}
$data = $query();
if ($data !== null) {
$this->memcached->set($key, $data, $ttl);
}
return $data;
}
/**
* 带版本控制的缓存(解决缓存更新问题)
*/
public function cacheWithVersion(string $key, callable $query, int $ttl = 3600): mixed
{
$versionKey = "{$key}:version";
$version = $this->memcached->get($versionKey) ?: 0;
$dataKey = "{$key}:data:v{$version}";
$data = $this->memcached->get($dataKey);
if ($data !== Memcached::GET_NOT_FOUND) {
return $data;
}
$freshData = $query();
if ($freshData !== null) {
$this->memcached->set($dataKey, $freshData, $ttl);
}
return $freshData;
}
/**
* 更新缓存版本
*/
public function refreshVersion(string $key): void
{
$versionKey = "{$key}:version";
$this->memcached->increment($versionKey, 1, 1);
}
/**
* 原子限流器(滑动窗口)
*/
public function rateLimit(string $key, int $limit = 100, int $window = 60): bool
{
$current = $this->memcached->get($key);
if ($current === Memcached::GET_NOT_FOUND) {
$this->memcached->set($key, 1, $window);
return true;
}
if ($current >= $limit) {
return false;
}
$this->memcached->increment($key);
return true;
}
}注意事项
- Value 大小限制:Memcached 单个 Value 最大 1MB,超大数据不适合存 Memcached
- 无持久化:Memcached 重启后数据全部丢失,不应将其作为唯一数据源
- 连接管理:使用长连接池,避免频繁创建销毁连接
- 序列化:推荐使用 igbinary 序列化,比 PHP 默认序列化更快、更紧凑
- 压缩:对大于 2KB 的 Value 启用压缩,但注意压缩/解压有 CPU 开销
最佳实践
- Key 设计:使用冒号分隔的命名空间,如
app:user:1001:profile - TTL 设置:所有缓存都应设置 TTL,避免内存泄漏
- 错误处理:Memcached 宕机不应影响业务,做好降级策略
- 连接复用:使用单例模式管理 Memcached 客户端
- 监控:监控命中率、内存使用、连接数、错误率
下一节
继续学习:缓存最佳实践