Laravel 13 与 Memcached 的集成提供了高性能的缓存解决方案,本文介绍如何深度集成 Memcached。

Memcached 配置

缓存配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

return [
'default' => env('CACHE_DRIVER', 'memcached'),

'stores' => [
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
Memcached::OPT_CONNECT_TIMEOUT => 2000,
Memcached::OPT_RETRY_TIMEOUT => 1,
Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
Memcached::OPT_BINARY_PROTOCOL => true,
Memcached::OPT_TCP_NODELAY => true,
Memcached::OPT_COMPRESSION => true,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],

'memcached-cluster' => [
'driver' => 'memcached',
'persistent_id' => 'cluster',
'servers' => [
['host' => 'cache-1.example.com', 'port' => 11211, 'weight' => 100],
['host' => 'cache-2.example.com', 'port' => 11211, 'weight' => 100],
['host' => 'cache-3.example.com', 'port' => 11211, 'weight' => 50],
],
],
],

'prefix' => env('CACHE_PREFIX', 'laravel_'),
];

Memcached 服务

高级 Memcached 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<?php

namespace App\Services\Cache;

use Memcached;

class MemcachedService
{
protected Memcached $client;
protected string $prefix;

public function __construct(array $config)
{
$this->prefix = $config['prefix'] ?? '';
$this->client = $this->createClient($config);
}

protected function createClient(array $config): Memcached
{
$persistentId = $config['persistent_id'] ?? null;

$client = $persistentId
? new Memcached($persistentId)
: new Memcached();

if (!empty($config['options'])) {
$client->setOptions($config['options']);
}

if (!empty($config['sasl']) && !empty($config['sasl'][0])) {
$client->setSaslAuthData($config['sasl'][0], $config['sasl'][1] ?? '');
}

if (empty($client->getServerList())) {
foreach ($config['servers'] as $server) {
$client->addServer(
$server['host'],
$server['port'],
$server['weight'] ?? 0
);
}
}

return $client;
}

protected function prefixKey(string $key): string
{
return $this->prefix . $key;
}

public function get(string $key, mixed $default = null): mixed
{
$value = $this->client->get($this->prefixKey($key));

if ($this->client->getResultCode() === Memcached::RES_NOTFOUND) {
return $default;
}

return $value;
}

public function set(string $key, mixed $value, int $ttl = 0): bool
{
return $this->client->set($this->prefixKey($key), $value, $ttl);
}

public function add(string $key, mixed $value, int $ttl = 0): bool
{
return $this->client->add($this->prefixKey($key), $value, $ttl);
}

public function replace(string $key, mixed $value, int $ttl = 0): bool
{
return $this->client->replace($this->prefixKey($key), $value, $ttl);
}

public function delete(string $key): bool
{
return $this->client->delete($this->prefixKey($key));
}

public function increment(string $key, int $value = 1): int|false
{
return $this->client->increment($this->prefixKey($key), $value);
}

public function decrement(string $key, int $value = 1): int|false
{
return $this->client->decrement($this->prefixKey($key), $value);
}

public function getMulti(array $keys): array
{
$prefixedKeys = array_map(fn($k) => $this->prefixKey($k), $keys);

$values = $this->client->getMulti($prefixedKeys);

if ($this->client->getResultCode() === Memcached::RES_NOTFOUND) {
return [];
}

$result = [];
foreach ($keys as $key) {
$prefixedKey = $this->prefixKey($key);
if (isset($values[$prefixedKey])) {
$result[$key] = $values[$prefixedKey];
}
}

return $result;
}

public function setMulti(array $items, int $ttl = 0): bool
{
$prefixedItems = [];
foreach ($items as $key => $value) {
$prefixedItems[$this->prefixKey($key)] = $value;
}

return $this->client->setMulti($prefixedItems, $ttl);
}

public function deleteMulti(array $keys): bool
{
$prefixedKeys = array_map(fn($k) => $this->prefixKey($k), $keys);

return $this->client->deleteMulti($prefixedKeys);
}

public function flush(): bool
{
return $this->client->flush();
}

public function getStats(): array
{
$stats = $this->client->getStats();

return $stats;
}

public function getVersion(): array
{
return $this->client->getVersion();
}

public function getClient(): Memcached
{
return $this->client;
}
}

缓存标签

标签缓存实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php

namespace App\Services\Cache;

class TaggedCache
{
protected MemcachedService $cache;

public function __construct(MemcachedService $cache)
{
$this->cache = $cache;
}

public function tagged(array $tags): TaggedCacheBuilder
{
return new TaggedCacheBuilder($this->cache, $tags);
}

public function tags(string|array $tags): TaggedCacheBuilder
{
return $this->tagged((array) $tags);
}
}

class TaggedCacheBuilder
{
protected MemcachedService $cache;
protected array $tags;

public function __construct(MemcachedService $cache, array $tags)
{
$this->cache = $cache;
$this->tags = $tags;
}

protected function tagKey(string $tag): string
{
return "tag:{$tag}";
}

protected function referenceKey(string $tag, string $key): string
{
return "tag:{$tag}:key:{$key}";
}

public function get(string $key): mixed
{
return $this->cache->get($key);
}

public function put(string $key, mixed $value, int $ttl = 0): bool
{
foreach ($this->tags as $tag) {
$tagKey = $this->tagKey($tag);
$referenceKey = $this->referenceKey($tag, $key);

$tagKeys = $this->cache->get($tagKey, []);

if (!in_array($key, $tagKeys)) {
$tagKeys[] = $key;
$this->cache->set($tagKey, $tagKeys);
}

$this->cache->set($referenceKey, true);
}

return $this->cache->set($key, $value, $ttl);
}

public function forget(string $key): bool
{
foreach ($this->tags as $tag) {
$referenceKey = $this->referenceKey($tag, $key);
$this->cache->delete($referenceKey);

$tagKeys = $this->cache->get($this->tagKey($tag), []);
$tagKeys = array_filter($tagKeys, fn($k) => $k !== $key);
$this->cache->set($this->tagKey($tag), $tagKeys);
}

return $this->cache->delete($key);
}

public function flush(): void
{
foreach ($this->tags as $tag) {
$tagKey = $this->tagKey($tag);
$tagKeys = $this->cache->get($tagKey, []);

foreach ($tagKeys as $key) {
$this->cache->delete($key);
$this->cache->delete($this->referenceKey($tag, $key));
}

$this->cache->delete($tagKey);
}
}

public function remember(string $key, int $ttl, callable $callback): mixed
{
$value = $this->get($key);

if ($value !== null) {
return $value;
}

$value = $callback();
$this->put($key, $value, $ttl);

return $value;
}
}

缓存预热

预热服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?php

namespace App\Services\Cache;

use App\Models\Category;
use App\Models\Product;
use App\Models\Setting;

class CacheWarmer
{
protected MemcachedService $cache;

public function __construct(MemcachedService $cache)
{
$this->cache = $cache;
}

public function warmAll(): array
{
return [
'settings' => $this->warmSettings(),
'categories' => $this->warmCategories(),
'featured_products' => $this->warmFeaturedProducts(),
];
}

public function warmSettings(): int
{
$settings = Setting::all()->pluck('value', 'key');

$this->cache->set('settings:all', $settings->toArray(), 3600);

foreach ($settings as $key => $value) {
$this->cache->set("setting:{$key}", $value, 3600);
}

return count($settings);
}

public function warmCategories(): int
{
$categories = Category::with('children')->whereNull('parent_id')->get();

$this->cache->set('categories:tree', $categories->toArray(), 3600);

foreach ($categories as $category) {
$this->cache->set("category:{$category->id}", $category->toArray(), 3600);
}

return count($categories);
}

public function warmFeaturedProducts(): int
{
$products = Product::where('is_featured', true)
->where('status', 'active')
->limit(20)
->get();

$this->cache->set('products:featured', $products->toArray(), 1800);

return count($products);
}

public function warmProduct(int $productId): bool
{
$product = Product::with(['category', 'brand', 'images'])->find($productId);

if (!$product) {
return false;
}

$this->cache->set("product:{$productId}", $product->toArray(), 3600);

return true;
}
}

缓存监控

监控服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<?php

namespace App\Services\Cache;

class CacheMonitor
{
protected MemcachedService $cache;

public function __construct(MemcachedService $cache)
{
$this->cache = $cache;
}

public function getStats(): array
{
$stats = $this->cache->getStats();

$result = [];

foreach ($stats as $server => $data) {
$hitRate = $data['get_hits'] > 0
? ($data['get_hits'] / ($data['get_hits'] + $data['get_misses'])) * 100
: 0;

$usagePercent = $data['limit_maxbytes'] > 0
? ($data['bytes'] / $data['limit_maxbytes']) * 100
: 0;

$result[$server] = [
'uptime' => $data['uptime'],
'curr_items' => $data['curr_items'],
'total_items' => $data['total_items'],
'bytes' => $data['bytes'],
'bytes_read' => $data['bytes_read'],
'bytes_written' => $data['bytes_written'],
'limit_maxbytes' => $data['limit_maxbytes'],
'usage_percent' => round($usagePercent, 2),
'get_hits' => $data['get_hits'],
'get_misses' => $data['get_misses'],
'hit_rate' => round($hitRate, 2),
'curr_connections' => $data['curr_connections'],
'total_connections' => $data['total_connections'],
];
}

return $result;
}

public function getHealth(): array
{
$stats = $this->getStats();

$issues = [];

foreach ($stats as $server => $data) {
if ($data['hit_rate'] < 80) {
$issues[] = "Low hit rate on {$server}: {$data['hit_rate']}%";
}

if ($data['usage_percent'] > 90) {
$issues[] = "High memory usage on {$server}: {$data['usage_percent']}%";
}

if ($data['curr_connections'] > 100) {
$issues[] = "High connection count on {$server}: {$data['curr_connections']}";
}
}

return [
'status' => empty($issues) ? 'healthy' : 'warning',
'issues' => $issues,
];
}

public function getMemoryUsage(): array
{
$stats = $this->getStats();

$total = 0;
$used = 0;

foreach ($stats as $server => $data) {
$total += $data['limit_maxbytes'];
$used += $data['bytes'];
}

return [
'total' => $this->formatBytes($total),
'used' => $this->formatBytes($used),
'free' => $this->formatBytes($total - $used),
'usage_percent' => $total > 0 ? round(($used / $total) * 100, 2) : 0,
];
}

protected function formatBytes(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
return number_format($bytes / pow(1024, $power), 2) . ' ' . $units[$power];
}
}

缓存命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php

namespace App\Console\Commands;

use App\Services\Cache\CacheMonitor;
use App\Services\Cache\CacheWarmer;
use Illuminate\Console\Command;

class MemcachedCommand extends Command
{
protected $signature = 'memcached:manage {action}';
protected $description = 'Manage Memcached cache';

public function handle(CacheMonitor $monitor, CacheWarmer $warmer): int
{
$action = $this->argument('action');

return match ($action) {
'stats' => $this->showStats($monitor),
'health' => $this->showHealth($monitor),
'warm' => $this->warmCache($warmer),
'flush' => $this->flushCache(),
default => $this->invalidAction(),
};
}

protected function showStats(CacheMonitor $monitor): int
{
$stats = $monitor->getStats();

foreach ($stats as $server => $data) {
$this->info("Server: {$server}");
$this->table(
['Metric', 'Value'],
[
['Current Items', $data['curr_items']],
['Total Items', $data['total_items']],
['Memory Usage', $data['usage_percent'] . '%'],
['Hit Rate', $data['hit_rate'] . '%'],
['Get Hits', $data['get_hits']],
['Get Misses', $data['get_misses']],
['Connections', $data['curr_connections']],
]
);
$this->newLine();
}

return self::SUCCESS;
}

protected function showHealth(CacheMonitor $monitor): int
{
$health = $monitor->getHealth();

if ($health['status'] === 'healthy') {
$this->info('Cache is healthy');
} else {
$this->warn('Cache has issues:');
foreach ($health['issues'] as $issue) {
$this->line(" - {$issue}");
}
}

return self::SUCCESS;
}

protected function warmCache(CacheWarmer $warmer): int
{
$this->info('Warming cache...');

$results = $warmer->warmAll();

foreach ($results as $type => $count) {
$this->line("Warmed {$count} {$type}");
}

return self::SUCCESS;
}

protected function flushCache(): int
{
if (!$this->confirm('Are you sure you want to flush all cache?')) {
return self::SUCCESS;
}

\Illuminate\Support\Facades\Cache::flush();

$this->info('Cache flushed');

return self::SUCCESS;
}

protected function invalidAction(): int
{
$this->error('Invalid action. Use: stats, health, warm, or flush');
return self::FAILURE;
}
}

总结

Laravel 13 与 Memcached 的集成提供了高性能的缓存解决方案。通过合理的配置、标签缓存、预热机制和监控,可以显著提升应用性能。