PHP与Memcached缓存实战
PHP与Memcached缓存实战
Memcached是一个高性能的分布式内存缓存系统。虽然Redis越来越流行,但Memcached在某些场景下仍有不可替代的优势。今天说说PHP中Memcached的使用技巧。
PHP有两个Memcached扩展,memcache和memcached。推荐使用memcached扩展,功能更完善,性能更好。
```php
// 连接Memcached
$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);
// 检查连接
$stats = $memcached->getStats();
$serverStats = $stats['127.0.0.1:11211'] ?? [];
echo "Memcached版本: " . ($serverStats['version'] ?? '未知') . "\n";
echo "当前连接数: " . ($serverStats['curr_connections'] ?? '0') . "\n";
// 基本操作
$memcached->set('key1', 'value1', 3600);
echo $memcached->get('key1') . "\n";
// 存储数组
$memcached->set('user:1', [
'name' => '张三',
'age' => 28,
'email' => 'zhangsan@test.com',
], 3600);
$user = $memcached->get('user:1');
echo "姓名: {$user['name']}\n";
?>
```
Memcached不支持数据持久化,纯内存缓存性能极高。适合缓存数据库查询结果、API响应、计算结果等。
```php
class CacheManager
{
private Memcached $memcached;
private string $prefix;
private int $defaultTtl;
public function __construct(string $prefix = 'app:', int $defaultTtl = 3600)
{
$this->memcached = new Memcached();
$this->memcached->addServer('127.0.0.1', 11211);
$this->memcached->setOption(Memcached::OPT_PREFIX_KEY, $prefix);
$this->memcached->setOption(Memcached::OPT_COMPRESSION, true);
$this->memcached->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$this->prefix = $prefix;
$this->defaultTtl = $defaultTtl;
}
public function get(string $key): mixed
{
$result = $this->memcached->get($this->prefix . $key);
$this->checkResultCode('get', $key);
return $result !== false ? $result : null;
}
public function set(string $key, mixed $value, ?int $ttl = null): bool
{
$ttl = $ttl ?? $this->defaultTtl;
$result = $this->memcached->set($this->prefix . $key, $value, $ttl);
$this->checkResultCode('set', $key);
return $result;
}
public function delete(string $key): bool
{
$result = $this->memcached->delete($this->prefix . $key);
$this->checkResultCode('delete', $key);
return $result;
}
public function remember(string $key, callable $callback, ?int $ttl = null): mixed
{
$value = $this->get($key);
if ($value !== null) {
return $value;
}
$value = $callback();
$this->set($key, $value, $ttl);
return $value;
}
public function getMulti(array $keys): array
{
$prefixedKeys = array_map(fn($k) => $this->prefix . $k, $keys);
$results = $this->memcached->getMulti($prefixedKeys);
// 去掉前缀
$unprefixed = [];
foreach ($results as $key => $value) {
$originalKey = str_replace($this->prefix, '', $key);
$unprefixed[$originalKey] = $value;
}
return $unprefixed;
}
public function setMulti(array $items, ?int $ttl = null): bool
{
$ttl = $ttl ?? $this->defaultTtl;
$prefixed = [];
foreach ($items as $key => $value) {
$prefixed[$this->prefix . $key] = $value;
}
return $this->memcached->setMulti($prefixed, $ttl);
}
public function increment(string $key, int $offset = 1): int|false
{
return $this->memcached->increment($this->prefix . $key, $offset);
}
public function decrement(string $key, int $offset = 1): int|false
{
return $this->memcached->decrement($this->prefix . $key, $offset);
}
public function flush(): bool
{
return $this->memcached->flush();
}
public function getStats(): array
{
return $this->memcached->getStats();
}
private function checkResultCode(string $operation, string $key): void
{
$code = $this->memcached->getResultCode();
if ($code !== Memcached::RES_SUCCESS) {
error_log("Memcached $operation $key: " . $this->memcached->getResultMessage());
}
}
}
$cache = new CacheManager('myapp:', 1800);
$users = $cache->remember('users.active', function () {
return [['id' => 1, 'name' => '张三'], ['id' => 2, 'name' => '李四']];
}, 600);
print_r($users);
?>
```
Memcached用于计数器场景:
```php
// 计数器
function incrementPageView(string $pageId): int
{
$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);
$key = "pageview:$pageId";
$count = $memcached->increment($key, 1);
if ($count === false) {
$memcached->set($key, 1, 86400);
$count = 1;
}
return $count;
}
echo "页面浏览量: " . incrementPageView('homepage') . "\n";
echo "页面浏览量: " . incrementPageView('homepage') . "\n";
echo "页面浏览量: " . incrementPageView('homepage') . "\n";
?>
```
Memcached的CAS(Check and Set)操作用于并发控制:
```php
// CAS操作
function atomicUpdate(Memcached $memcached, string $key, callable $updateFn): bool
{
do {
$value = $memcached->get($key, null, $casToken);
if ($value === false) {
// key不存在
$newValue = $updateFn(null);
return $memcached->add($key, $newValue, 3600);
}
$newValue = $updateFn($value);
// CAS更新,如果casToken变了说明被其他进程修改了
$result = $memcached->cas($casToken, $key, $newValue, 3600);
} while ($result === false && $memcached->getResultCode() === Memcached::RES_DATA_EXISTS);
return $result;
}
$memcached = new Memcached();
$memcached->addServer('127.0.0.1', 11211);
$memcached->set('counter', 0, 3600);
for ($i = 0; $i < 10; $i++) {
atomicUpdate($memcached, 'counter', function ($current) {
return ($current ?? 0) + 1;
});
}
echo "最终计数: " . $memcached->get('counter') . "\n";
?>
```
Memcached作为session存储后端,可以跨服务器共享会话:
```php
// 使用Memcached存储session
ini_set('session.save_handler', 'memcached');
ini_set('session.save_path', '127.0.0.1:11211');
session_start();
$_SESSION['user_id'] = 123;
$_SESSION['username'] = '张三';
$_SESSION['role'] = 'admin';
echo "会话已存储在Memcached\n";
?>
```
Memcached的LRU淘汰策略会在内存不足时自动淘汰最近最少使用的数据。所以使用Memcached时要注意设置合理的过期时间,避免热点数据被非热点数据挤出。Memcached适合缓存小数据块,单条数据不要超过1MB。如果需要持久化或复杂数据结构,Redis是更好的选择。
