用户
 找回密码
 入住 CI 中国社区
搜索
查看: 13950|回复: 16
收起左侧

[优化] 优化ci缓存机制,扩展CI_Output

[复制链接]
发表于 2012-1-27 21:32:59 | 显示全部楼层 |阅读模式
本帖最后由 hulei0102 于 2012-1-27 21:53 编辑

首先问题是这样,我有个站大约200万数据,全站文件缓存。我一直用的是CI的文件缓存机制,考虑到磁盘性能问题我会定期(每天)清除一下文件缓存。但是现在问题来了,就这几天引擎搜索暴增每天大约产生10W左右的页,这样一来CI文件缓存机制直接崩溃!!!直接的后果就是用户浏览页面速度直接慢过不用缓存,删除cache文件夹用了我半个小时TT 于是我终于下定决心改进CI_Output的缓存机制。


首先搜索了一下老贴,发现已有很多前辈提出了问题,并有了解决方案:

脆弱的CI缓存系统,1天攻陷你的CI网站
http://codeigniter.org.cn/forums/forum.php?mod=viewthread&tid=16901

CI缓存终极解决思路
http://codeigniter.org.cn/forums/forum.php?mod=viewthread&tid=2527

看了《CI缓存终极解决思路》这一帖,发现思路很不错,但是没有现成的代码,所以我依照这贴思路改进了CI_Output。

原理如下:
取Cache文件名md5码的后2位,生成目录存放缓存
因为C(36,2) = 630,也就是说总共生成630个文件夹,2000000/630 = 3174 ,这样就会吧所有缓存数据分散到各个目录提高索引速度。

CI版本2.1.0,代码如下,只需新建Application\core\My_Output.php,只扩展_write_cache、_display_cache2个函数即可达到目的,代码如下:
PHP复制代码
 
class MY_Output extends CI_Output
{
        //$param=1写入文件缓存 $param=0读取文件缓存
        function breakup_cachefiles($cache_path,$param=1)
        {
                $ret = '';
               
                /*1、取得md5码的后2个字母,目的是分散缓存到不同的文件夹,使磁盘能够更快索引
                  substr($cache_path, -3) C(36,3) = 7140(排列组合数),磁盘可以承受再大就不行了
                */

                $md5_2 = substr($cache_path, -2);
                //echo '<font color=blue>'.$md5_2.'<font>';
                //2、建立目录名=$md5_2的目录
                $dir = dirname($cache_path);//获取当前目录名
                $file = basename($cache_path);//获取当前文件名
        $newdir = $dir.'/'.$md5_2;//新的目录名
                if($param==1)
                {
                        if (!file_exists($newdir))//目录不存在则创建
                                mkdir($newdir,0777);
                }        
                //3、将        $cache_path 定位到新的文件夹
                $ret = $newdir.'/'.$file;
                       
                return $ret;
        }
       
        // --------------------------------------------------------------------
 
        /**
         * Write a Cache File
         *
         * @access        public
         * @param         string
         * @return        void
         */

        function _write_cache($output)
        {
                $CI =& get_instance();
                $path = $CI->config->item('cache_path');
 
                $cache_path = ($path == '') ? APPPATH.'cache/' : $path;
 
                if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
                {
                        log_message('error', "Unable to write cache file: ".$cache_path);
                        return;
                }
 
                $uri =        $CI->config->item('base_url').
                                $CI->config->item('index_page').
                                $CI->uri->uri_string();
 
                $cache_path .= md5($uri);
               
                /*生成Md5缓存文件后处理*/
                $cache_path = $this->breakup_cachefiles($cache_path,1);
               
                if ( ! $fp = @fopen($cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE))
                {
                        log_message('error', "Unable to write cache file: ".$cache_path);
                        return;
                }
 
                $expire = time() + ($this->cache_expiration * 60);
 
                if (flock($fp, LOCK_EX))
                {
                        fwrite($fp, $expire.'TS--->'.$output);
                        flock($fp, LOCK_UN);
                }
                else
                {
                        log_message('error', "Unable to secure a file lock for file at: ".$cache_path);
                        return;
                }
                fclose($fp);
                @chmod($cache_path, FILE_WRITE_MODE);
 
                log_message('debug', "Cache file written: ".$cache_path);
        }
 
        // --------------------------------------------------------------------
 
        /**
         * Update/serve a cached file
         *
         * @access        public
         * @param         object        config class
         * @param         object        uri class
         * @return        void
         */

        function _display_cache(&$CFG, &$URI)
        {
                $cache_path = ($CFG->item('cache_path') == '') ? APPPATH.'cache/' : $CFG->item('cache_path');
 
                // Build the file path.  The file name is an MD5 hash of the full URI
                $uri =        $CFG->item('base_url').
                                $CFG->item('index_page').
                                $URI->uri_string;
 
                $filepath = $cache_path.md5($uri);
               
                /*生成Md5缓存文件后处理*/
                $filepath = $this->breakup_cachefiles($filepath,0);
               
                if ( ! @file_exists($filepath))
                {
                        return FALSE;
                }
 
                if ( ! $fp = @fopen($filepath, FOPEN_READ))
                {
                        return FALSE;
                }
 
                flock($fp, LOCK_SH);
 
                $cache = '';
                if (filesize($filepath) > 0)
                {
                        $cache = fread($fp, filesize($filepath));
                }
 
                flock($fp, LOCK_UN);
                fclose($fp);
 
                // Strip out the embedded timestamp
                if ( ! preg_match("/(\d+TS--->)/", $cache, $match))
                {
                        return FALSE;
                }
 
                // Has the file expired? If so we'll delete it.
                if (time() >= trim(str_replace('TS--->', '', $match['1'])))
                {
                        if (is_really_writable($cache_path))
                        {
                                @unlink($filepath);
                                log_message('debug', "Cache file has expired. File deleted");
                                return FALSE;
                        }
                }
 
                // Display the cache
                $this->_display(str_replace($match['0'], '', $cache));
                log_message('debug', "Cache file is current. Sending it to browser.");
                return TRUE;
        }
}
       
// END class MY_Output
 
复制代码


就这么简单!最后附上My_Output.php,祝各位新年快乐{:soso_e128:}最后希望,CI官方在下一个版本能够彻底解决文件cache机制带来的这种烦恼,让大家不再用这种方式去解决问题了。
My_Output.zip (1.77 KB, 下载次数: 138)



发表于 2014-9-21 15:44:09 | 显示全部楼层
这个自定义类 是怎么使用的啊 也是 用  $this->output->cache(1);  来使用吗?
 楼主| 发表于 2012-1-27 21:43:48 | 显示全部楼层
本帖最后由 hulei0102 于 2012-1-27 21:46 编辑

最后我奉劝大家,windows环境Ntfs下单文件夹缓存数量>5万就会影响到性能,>10万你干脆关掉文件缓存功能,不然会慢死!>20万你的机器恐怕要烧掉!Linux环境下没测试。
发表于 2012-1-28 13:45:32 | 显示全部楼层
文件夹的结构有问题吧,一个文件夹上了5000个文件,打开这个文件夹都要几秒钟的时间,你说的问题不是CI缓存机制的问题是文件夹结构的问题吧。

点评

CI的文件缓存就在一个目录下生成所有的缓存文件,这就是问题所在。  发表于 2012-1-28 19:24
发表于 2012-2-14 13:27:37 | 显示全部楼层
但是,你为什么要用文件的方式来缓存呢?
你的两个函数 _write_cache _display_cache 可以优化下啊.例如将缓存写到Redis里面,就没有你这个大量文件的问题了,而且速度比文件存储快多了.可靠性也强.
发表于 2012-2-16 09:45:12 | 显示全部楼层
本帖最后由 kamengwang 于 2012-2-16 09:46 编辑

我们centos,ext3格式的单文件夹文件数量上限是32000,超了就写不了东西了...
发表于 2012-4-23 09:47:51 | 显示全部楼层
为啥我调用老出错?
发表于 2012-5-24 16:17:57 | 显示全部楼层
我的也是,经常出错。
发表于 2012-7-11 15:24:04 | 显示全部楼层
如果当前访问的页面数据有时间字段的话可以取时间年份+月份做为文件夹 只要不是超大型网站的话 一个月的数据应该超过10W的不多吧
发表于 2012-8-30 17:49:51 | 显示全部楼层
学习
发表于 2012-8-31 09:40:59 | 显示全部楼层
这个不能自己删除吧要是能自己删除主好了

本版积分规则