blog icon indicating copy to clipboard operation
blog copied to clipboard

MEM快速缓存实现(原来,php不受这一套,只能换成文件缓存)

Open diamont1001 opened this issue 4 years ago • 0 comments

最近有个php项目,单机部署,在做性能优化的时候发现有些内部处理函数需要做下数据缓存。因为项目没有部署 redis 或者 memcached,所以就想偷个懒,直接把缓存放到内存来实现,反正是单机部署方便快捷,只需要控制好缓存量别被挤爆了就好。

嗯,不用想了,单例模式上……信心满满,很快就写好了!

然而……然而……童话里都是骗人的!!!

一顿操作萌如虎……上机运行之后,结果并不是想象中的那样,数据怎么也缓存不起来,调试了好久,最后才发现,原来是php运行机制的问题。

最近刚接触 php,对它的运行机制还不熟悉,惯性的就用了其他语言(JAVA/NODE/..)的实现方法,但是由于 PHP 是解释运行的,PHP 页面被解释执行后,所有相关的资源都会被回收,对象也被销毁了,所以PHP 程序无法做到常驻内存运行。

当然,目前好像也出现了一些常驻内存的解决方法,但是基于目前的项目环境(PHP 5.3.29),感觉会得不偿失,还是老老实实用 redis 或者 memcached 吧。

不过,代码不写也写了,虽然用不了但删掉也可惜,就放出来当个纪念吧!

基于RAM内存的数据缓存方案(由于机制问题,缓存效果并不生效)

<?php

/**
 * 缓存管理(内存缓存)
 * 单例模式
 *
 * 用法举例:mem_cache::ins()->get('key');
 *
 * $Author: Eric Chen 2020-11-4
 * $Email: [email protected]
 */

if (!defined('IN_ECTOUCH'))
{
    die('Hacking attempt');
}


class mem_cache
{
    private static $_instance = null;

    private $_pool; // 缓存池
    private $_maxlen; // 缓存key数量限制,防止爆内存
    private $_lucky_percent; // 缓存失效概率 0-100(读缓存的时候,设定一个概率让它清空,也就是除了时间还提供了一个随机机制让缓存更新,如果想关闭该机制,则设定为0即可


    private function __construct($maxlen = 100, $percent = 5){
        $this->_pool = array();
        $this->_maxlen = $maxlen;
        $this->_lucky_percent = $percent;
    }

    /*
     * 公有化获取实例方法
     *
     * 用法:mem_cache::ins()->xxx
     */
    public static function ins(){
        if (!(self::$_instance instanceof mem_cache)){
            self::$_instance = new mem_cache();
        }
        return self::$_instance;
    }

    // 设置缓存,默认有效时间为 600s
    public function set($key, $value, $seconds = 600, $percent = -1) {
        if (!$key) {
            return;
        }

        // 检测超池
        if (count($this->_pool, COUNT_NORMAL ) >= $this->_maxlen) {
            // 先清一次过期缓存,如果还是饱和,则直接返回
            if(!$this->gc()) {
                return;
            }
        }

        // 更新/添加缓存
        $obj_item = new mem_cache_item();
        $obj_item->value = $value;
        $obj_item->time = time();
        $obj_item->maxage = $seconds;
        $obj_item->lucky_percent = $percent < 0 ? $this->_lucky_percent : $percent;
        $this->_pool[$key] = $obj_item;
    }

    public function get($key) {
        if (!$key || !$this->_pool[$key]) {
            return false;
        }

        // 过期/缓存异常/命中清除概率,则判断为缓存不可取,清除它
        if (!$this->_pool[$key]->value ||
            (time() - $this->_pool[$key]->time) > $this->_pool[$key]->maxage ||
            mt_rand(0, 100) < $this->_pool[$key]->lucky_percent
        ) {
            $this->delete($key);
            return false;
        }

        return $this->_pool[$key]->value;
    }

    public function delete($key) {
        if (!$key) {
            return;
        }
        unset($this->_pool[$key]);
    }

    // 清除过期缓存,清完后还饱和则返回false,否则返回true
    public function gc() {
        foreach($this->_pool as $key => $item) {
            if (!$this->_pool[$key]->value ||
                (time() - $this->_pool[$key]->time) > $this->_pool[$key]->maxage
            ) {
                $this->delete($key);
            }
        }

        if (count($this->_pool, COUNT_NORMAL) >= $this->_maxlen) {
            return false;
        }
        return true;
    }

    // 清除所有缓存
    public function clear() {
        $this->_pool = array();
    }
}

// 缓存元素类
class mem_cache_item
{
    public $value;
    public $time;
    public $maxage;
    public $lucky_percent;
}

?>

基于文件系统的数据缓存方案

更新@2020-11-5 今天把它改成了基于文件系统的缓存,自测没问题,记录一下:

<?php

/**
 * 缓存管理(内存缓存)
 * 单例模式
 *
 * 用法举例:mem_cache::ins()->get('key');
 *
 * $Author: Eric Chen 2020-11-5
 * $Email: [email protected]
 */

if (!defined('IN_ECTOUCH'))
{
    die('Hacking attempt');
}


class mem_cache
{
    private static $_instance = null;

    private $_cache_path;
    private $_lucky_percent; // 缓存命中率 0-100(读缓存的时候,设定一个命中概率,不命中的时候不返回数据,让调用方以为没缓存然后去取数据更新缓存,也就是除了时间还提供了一个随机机制让缓存更新,如果想关闭该机制,则设定为100即可


    private function __construct($percent = 98)
    {
        $this->_lucky_percent = $percent;
        $this->_cache_path = ROOT_PATH . 'data/mem_cache/';

        if (!file_exists($this->_cache_path)) {
            if (!make_dir($this->_cache_path)) {
                return false;
            }
        }
    }

    /*
     * 公有化获取实例方法
     *
     * 用法:mem_cache::ins()->xxx
     */
    public static function ins()
    {
        if (!(self::$_instance instanceof mem_cache)) {
            self::$_instance = new mem_cache();
        }
        return self::$_instance;
    }

    // 设置缓存,默认有效时间为 600s
    public function set($key, $value, $seconds = 600, $percent = -1)
    {
        if (!$key) {
            return;
        }

        // 更新/添加缓存
        $obj_item = new mem_cache_item();
        $obj_item->value = $value;
        $obj_item->time = time();
        $obj_item->maxage = $seconds;
        $obj_item->lucky_percent = ($percent < 0 || $percent > 100) ? $this->_lucky_percent : $percent;

        $cache_file = $this->_cache_path . crc32($key) . '.cache';
        file_put_contents($cache_file, serialize($obj_item)); // 序列化写入
    }

    public function get($key)
    {
        if (!$key) {
            return false;
        }

        // 从文件读取缓存
        $cache_file = $this->_cache_path . crc32($key) . '.cache';
        if (!is_file($cache_file)) {
//            echo 'cache not found. key: ' . $key . '<br>';
            return false;
        }
        $fp = fopen($cache_file, 'r');
        $item = unserialize(fread($fp, filesize($cache_file))); //反序列化,并赋值

        // 缓存异常或者过期,清理它
        if (!$item || !$item->value || (time() - $item->time) > $item->maxage) {
//            echo 'cache exist but not good, to delete it. key: ' .$key .'<br>';
            $this->delete($key);
            return false;
        }

        // 命中率
        if (mt_rand(0, 100) > $item->lucky_percent) {
//            echo 'cache exist, but I do not want to show you. key: ' . $key . '<br>';
            return false;
        }

        return $item->value;
    }

    public function delete($key)
    {
        if (!$key) {
            return;
        }

        // 删除缓存文件
        $cache_file = $this->_cache_path . crc32($key) . '.cache';
        unlink($cache_file);

//        echo 'delete cache. key: ' . $key . '<br>';
    }

    // 清除所有缓存
    public function clear()
    {
        $this->delete_directory($this->_cache_path);
    }

    public function delete_directory($dirname)
    {
        if (is_dir($dirname)) {
            $dir_handle = opendir($dirname);
        }
        if (!$dir_handle) {
            return false;
        }
        while ($file = readdir($dir_handle)) {
            if ($file != "." && $file != "..") {
                if (!is_dir($dirname . "/" . $file)) {
                    unlink($dirname . "/" . $file);
                } else {
                    $this->delete_directory($dirname . '/' . $file);
                }
            }
        }
        closedir($dir_handle);
        rmdir($dirname);
        return true;
    }

}

// 缓存元素类
class mem_cache_item
{
    public $value;
    public $time;
    public $maxage;
    public $lucky_percent;
}

?>

diamont1001 avatar Nov 05 '20 08:11 diamont1001