Skip to content

Memcached PHP 客户端

Memcached 是一个高性能的分布式内存对象缓存系统,以简单的 Key-Value 形式存储数据,专为加速动态 Web 应用而设计。本节将详细讲解 Memcached 的安装配置、PHP 客户端操作、CAS 机制、分布式部署以及与 Redis 的对比选型。

前置知识

阅读本节前,建议先了解:缓存策略概览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 memcached

memcache 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 对比

功能对比

特性MemcachedRedis
数据类型纯字符串(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
适用场景纯缓存缓存 + 消息队列 + 排行榜等

性能对比

场景MemcachedRedis
简单读写(小 Value)极快(~10us)快(~15us)
批量读写较快非常快(Pipeline)
大 Value 读写不太擅长擅长
并发连接高(多线程)较高(单线程但 IO 多路复用)
数据结构操作不支持强(原生支持各种数据结构)

选型建议

场景推荐原因
纯缓存(简单的 KV 存储)Memcached简单、高效、内存利用率高
需要丰富的数据结构RedisHash、Set、ZSet 等天然支持
需要持久化RedisMemcached 不支持持久化
需要消息队列Redis支持 Stream 和 Pub/Sub
需要排行榜/计数器RedisZSet 原生支持有序集合
超大并发(百万连接)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;
    }
}

注意事项

  1. Value 大小限制:Memcached 单个 Value 最大 1MB,超大数据不适合存 Memcached
  2. 无持久化:Memcached 重启后数据全部丢失,不应将其作为唯一数据源
  3. 连接管理:使用长连接池,避免频繁创建销毁连接
  4. 序列化:推荐使用 igbinary 序列化,比 PHP 默认序列化更快、更紧凑
  5. 压缩:对大于 2KB 的 Value 启用压缩,但注意压缩/解压有 CPU 开销

最佳实践

  1. Key 设计:使用冒号分隔的命名空间,如 app:user:1001:profile
  2. TTL 设置:所有缓存都应设置 TTL,避免内存泄漏
  3. 错误处理:Memcached 宕机不应影响业务,做好降级策略
  4. 连接复用:使用单例模式管理 Memcached 客户端
  5. 监控:监控命中率、内存使用、连接数、错误率

下一节

继续学习:缓存最佳实践

参考链接