Last active
September 4, 2025 03:08
-
-
Save linkerlin/94215239bf3323a97cfd425c82f21c0e to your computer and use it in GitHub Desktop.
MmapCache: 基于 mmap 的多进程共享缓存库
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* ShmCache: 基于 shmop 的多进程共享缓存库 | |
* 支持 key-value 存储,兼容 PHP 原生数据结构,优化读写数据量,保持 ACID | |
* 需要启用 shmop 扩展 | |
*/ | |
class ShmCache { | |
private $shmId; | |
private $shmKey; | |
private $shmSize; | |
private $lockFile; | |
private $lockHandle; | |
private $index; // key => [offset, length] | |
private $dataOffset; // 数据区起始偏移量 | |
private const INDEX_SIZE = 4096; // 索引区固定大小(字节) | |
/** | |
* 构造函数,初始化共享内存和锁 | |
* @param string $lockFilePath 锁文件路径 | |
* @param int $size 共享内存大小(字节) | |
*/ | |
public function __construct(string $lockFilePath, int $size = 1048576) { | |
$this->shmSize = $size; | |
$this->lockFile = $lockFilePath; | |
$this->dataOffset = self::INDEX_SIZE; | |
// 生成共享内存键 | |
$this->shmKey = ftok(__FILE__, 't'); | |
if ($this->shmKey === -1) { | |
throw new RuntimeException("无法生成共享内存键"); | |
} | |
// 初始化锁文件 | |
$this->lockHandle = fopen($this->lockFile, 'c+'); | |
if ($this->lockHandle === false) { | |
throw new RuntimeException("无法创建锁文件: {$this->lockFile}"); | |
} | |
// 初始化共享内存 | |
$this->shmId = @shmop_open($this->shmKey, 'w', 0, 0); | |
if ($this->shmId === false) { | |
$this->shmId = shmop_open($this->shmKey, 'c', 0644, $this->shmSize); | |
if ($this->shmId === false) { | |
fclose($this->lockHandle); | |
throw new RuntimeException("无法创建共享内存"); | |
} | |
$this->initializeShm(); | |
} | |
// 加载索引 | |
$this->loadIndex(); | |
} | |
/** | |
* 初始化共享内存 | |
*/ | |
private function initializeShm(): void { | |
$this->index = []; | |
$this->saveIndex(); | |
} | |
/** | |
* 加载索引 | |
*/ | |
private function loadIndex(): void { | |
try { | |
if (!flock($this->lockHandle, LOCK_SH)) { | |
throw new RuntimeException("无法获取共享锁"); | |
} | |
$data = shmop_read($this->shmId, 0, self::INDEX_SIZE); | |
if ($data === false || empty(trim($data, "\0"))) { | |
$this->index = []; | |
} else { | |
$decoded = @unserialize($data); | |
$this->index = $decoded !== false ? $decoded : []; | |
} | |
} finally { | |
flock($this->lockHandle, LOCK_UN); | |
} | |
} | |
/** | |
* 保存索引 | |
*/ | |
private function saveIndex(): void { | |
$serialized = serialize($this->index); | |
if (strlen($serialized) > self::INDEX_SIZE) { | |
throw new RuntimeException("索引大小超出限制: " . self::INDEX_SIZE . " 字节"); | |
} | |
try { | |
if (!flock($this->lockHandle, LOCK_EX)) { | |
throw new RuntimeException("无法获取独占锁"); | |
} | |
$result = shmop_write($this->shmId, str_pad($serialized, self::INDEX_SIZE, "\0"), 0); | |
if ($result === false) { | |
throw new RuntimeException("写入索引失败"); | |
} | |
} finally { | |
flock($this->lockHandle, LOCK_UN); | |
} | |
} | |
/** | |
* 设置缓存值 | |
* @param string $key 键 | |
* @param mixed $value 值(支持 PHP 原生数据结构) | |
* @return bool | |
*/ | |
public function set(string $key, $value): bool { | |
try { | |
if (!flock($this->lockHandle, LOCK_EX)) { | |
throw new RuntimeException("无法获取独占锁"); | |
} | |
$serialized = serialize($value); | |
$length = strlen($serialized); | |
// 检查剩余空间 | |
$newOffset = $this->getNextOffset(); | |
if ($newOffset + $length > $this->shmSize) { | |
throw new RuntimeException("数据超出共享内存容量"); | |
} | |
// 写入数据 | |
$result = shmop_write($this->shmId, $serialized, $newOffset); | |
if ($result === false) { | |
throw new RuntimeException("写入数据失败"); | |
} | |
// 更新索引 | |
$this->index[$key] = [$newOffset, $length]; | |
$this->saveIndex(); | |
return true; | |
} catch (RuntimeException $e) { | |
error_log("ShmCache set error: " . $e->getMessage()); | |
return false; | |
} finally { | |
flock($this->lockHandle, LOCK_UN); | |
} | |
} | |
/** | |
* 获取缓存值 | |
* @param string $key 键 | |
* @param mixed $default 默认值 | |
* @return mixed | |
*/ | |
public function get(string $key, $default = null) { | |
try { | |
if (!flock($this->lockHandle, LOCK_SH)) { | |
throw new RuntimeException("无法获取共享锁"); | |
} | |
if (!isset($this->index[$key])) { | |
return $default; | |
} | |
[$offset, $length] = $this->index[$key]; | |
$data = shmop_read($this->shmId, $offset, $length); | |
if ($data === false) { | |
throw new RuntimeException("读取数据失败"); | |
} | |
$value = @unserialize($data); | |
return $value !== false ? $value : $default; | |
} catch (RuntimeException $e) { | |
error_log("ShmCache get error: " . $e->getMessage()); | |
return $default; | |
} finally { | |
flock($this->lockHandle, LOCK_UN); | |
} | |
} | |
/** | |
* 删除缓存键 | |
* @param string $key 键 | |
* @return bool | |
*/ | |
public function delete(string $key): bool { | |
try { | |
if (!flock($this->lockHandle, LOCK_EX)) { | |
throw new RuntimeException("无法获取独占锁"); | |
} | |
if (!isset($this->index[$key])) { | |
return false; | |
} | |
unset($this->index[$key]); | |
$this->saveIndex(); | |
return true; | |
} catch (RuntimeException $e) { | |
error_log("ShmCache delete error: " . $e->getMessage()); | |
return false; | |
} finally { | |
flock($this->lockHandle, LOCK_UN); | |
} | |
} | |
/** | |
* 清空缓存 | |
* @return bool | |
*/ | |
public function clear(): bool { | |
try { | |
if (!flock($this->lockHandle, LOCK_EX)) { | |
throw new RuntimeException("无法获取独占锁"); | |
} | |
$this->index = []; | |
$this->saveIndex(); | |
return true; | |
} catch (RuntimeException $e) { | |
error_log("ShmCache clear error: " . $e->getMessage()); | |
return false; | |
} finally { | |
flock($this->lockHandle, LOCK_UN); | |
} | |
} | |
/** | |
* 检查键是否存在 | |
* @param string $key 键 | |
* @return bool | |
*/ | |
public function has(string $key): bool { | |
$this->loadIndex(); | |
return isset($this->index[$key]); | |
} | |
/** | |
* 获取下一个可用数据偏移量 | |
* @return int | |
*/ | |
private function getNextOffset(): int { | |
if (empty($this->index)) { | |
return $this->dataOffset; | |
} | |
$last = max(array_map(fn($entry) => $entry[0] + $entry[1], $this->index)); | |
return max($this->dataOffset, $last); | |
} | |
/** | |
* 析构函数,清理资源 | |
*/ | |
public function __destruct() { | |
if ($this->shmId !== false) { | |
shmop_close($this->shmId); | |
} | |
if ($this->lockHandle !== false) { | |
fclose($this->lockHandle); | |
} | |
} | |
} | |
/** | |
* 示例用法 | |
*/ | |
if (defined('STDIN')) { | |
try { | |
// 初始化缓存,指定锁文件路径和共享内存大小(1MB) | |
$cache = new ShmCache('/tmp/shm_cache.lock', 1048576); | |
// 设置简单值 | |
$cache->set('user1', ['id' => 1, 'name' => 'Alice']); | |
// 设置复杂数据结构 | |
$cache->set('config', [ | |
'settings' => ['theme' => 'dark', 'lang' => 'en'], | |
'data' => [1, 2, 3] | |
]); | |
// 获取值 | |
var_dump($cache->get('user1')); // ['id' => 1, 'name' => 'Alice'] | |
var_dump($cache->get('config')); // 复杂数组 | |
var_dump($cache->get('nonexistent', 'default')); // 'default' | |
// 检查键 | |
var_dump($cache->has('user1')); // true | |
var_dump($cache->has('unknown')); // false | |
// 删除键 | |
$cache->delete('user1'); | |
var_dump($cache->has('user1')); // false | |
// 清空缓存 | |
$cache->clear(); | |
var_dump($cache->get('config')); // null | |
} catch (Exception $e) { | |
echo "错误: " . $e->getMessage() . PHP_EOL; | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment