有些時候,我們不希望使用redis等第三方緩存,使得系統依賴於其他服務。這時候,文件緩存會是一個不錯的選擇。
我們需要文件緩存實現哪些共更能:
功能實現:get、set、has、increment、decrement、delete、flush
能夠在較短的時間內返回數據
支持key過期
原理:
為了避免一個文件內的數據過大,造成讀取文件的時候延遲較高,我們采用一個key-value一個文件的方式實現存儲結構。
為了支持key過期,我們需要把expire數據寫入到文件中,所以需要對寫入的數據進行序列化處理
為了能夠快速的定位到文件路徑,我們采用hash算法一次計算出文件位置
實現:
<?php class LeoFileCache implements LeoCacheInterface { /** * 緩存目錄 * @var */ private $cache_dir; /** * @param $cache_dir * @throws Exception */ public function __construct($cache_dir) { $this->cache_dir = $cache_dir; if (!is_dir($cache_dir)) { $make_dir_result = mkdir($cache_dir, 0755, true); if ($make_dir_result === false) throw new Exception('Cannot create the cache directory'); } } /** * 根據key獲取值,會判斷是否過期 * @param $key * @return mixed */ public function get($key) { $cache_data = $this->getItem($key); if ($cache_data === false || !is_array($cache_data)) return false; return $cache_data['data']; } /** * 添加或覆蓋一個key * @param $key * @param $value * @param $expire * @return mixed */ public function set($key, $value, $expire = 0) { return $this->setItem($key, $value, time(), $expire); } /** * 設置包含元數據的信息 * @param $key * @param $value * @param $time * @param $expire * @return bool */ private function setItem($key, $value, $time, $expire) { $cache_file = $this->createCacheFile($key); if ($cache_file === false) return false; $cache_data = array('data' => $value, 'time' => $time, 'expire' => $expire); $cache_data = json_encode($cache_data); $put_result = file_put_contents($cache_file, $cache_data); if ($put_result === false) return false; return true; } /** * 創建緩存文件 * @param $key * @return bool|string */ private function createCacheFile($key) { $cache_file = $this->path($key); if (!file_exists($cache_file)) { $directory = dirname($cache_file); if (!is_dir($directory)) { $make_dir_result = mkdir($directory, 0755, true); if ($make_dir_result === false) return false; } $create_result = touch($cache_file); if ($create_result === false) return false; } return $cache_file; } /** * 判斷Key是否存在 * @param $key * @return mixed */ public function has($key) { $value = $this->get($key); if ($value === false) return false; return true; } /** * 加法遞增 * @param $key * @param int $value * @return mixed */ public function increment($key, $value = 1) { $item = $this->getItem($key); if ($item === false) { $set_result = $this->set($key, $value); if ($set_result === false) return false; return $value; } $check_expire = $this->checkExpire($item); if ($check_expire === false) return false; $item['data'] += $value; $result = $this->setItem($key, $item['data'], $item['time'], $item['expire']); if ($result === false) return false; return $item['data']; } /** * 減法遞增 * @param $key * @param int $value * @return mixed */ public function decrement($key, $value = 1) { $item = $this->getItem($key); if ($item === false) { $value = 0 - $value; $set_result = $this->set($key, $value); if ($set_result === false) return false; return $value; } $check_expire = $this->checkExpire($item); if ($check_expire === false) return false; $item['data'] -= $value; $result = $this->setItem($key, $item['data'], $item['time'], $item['expire']); if ($result === false) return false; return $item['data']; } /** * 刪除一個key,同事會刪除緩存文件 * @param $key * @return mixed */ public function delete($key) { $cache_file = $this->path($key); if (file_exists($cache_file)) { $unlink_result = unlink($cache_file); if ($unlink_result === false) return false; } return true; } /** * 清楚所有緩存 * @return mixed */ public function flush() { return $this->delTree($this->cache_dir); } /** * 遞歸刪除目錄 * @param $dir * @return bool */ function delTree($dir) { $files = array_diff(scandir($dir), array('.', '..')); foreach ($files as $file) { (is_dir("$dir/$file")) ? $this->delTree("$dir/$file") : unlink("$dir/$file"); } return rmdir($dir); } /** * 根據key獲取緩存文件路徑 * * @param string $key * @return string */ protected function path($key) { $parts = array_slice(str_split($hash = md5($key), 2), 0, 2); return $this->cache_dir . '/' . implode('/', $parts) . '/' . $hash; } /** * 獲取含有元數據的信息 * @param $key * @return bool|mixed|string */ protected function getItem($key) { $cache_file = $this->path($key); if (!file_exists($cache_file) || !is_readable($cache_file)) { return false; } $cache_data = file_get_contents($cache_file); if (empty($cache_data)) return false; $cache_data = json_decode($cache_data, true); if ($cache_data) { $check_expire = $this->checkExpire($cache_data); if ($check_expire === false) { $this->delete($key); return false; } } return $cache_data; } /** * 檢查key是否過期 * @param $cache_data * @return bool */ protected function checkExpire($cache_data) { $time = time(); $is_expire = intval($cache_data['expire']) !== 0 && (intval($cache_data['time']) + intval($cache_data['expire']) < $time); if ($is_expire) return false; return true; } }*