hulei0102 发表于 2012-1-27 21:32:59

优化ci缓存机制,扩展CI_Output

本帖最后由 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个函数即可达到目的,代码如下:

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机制带来的这种烦恼,让大家不再用这种方式去解决问题了。




luo620 发表于 2014-9-21 15:44:09

这个自定义类 是怎么使用的啊 也是 用$this->output->cache(1);来使用吗?

hulei0102 发表于 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缓存机制的问题是文件夹结构的问题吧。

世界尽头 发表于 2012-2-14 13:27:37

但是,你为什么要用文件的方式来缓存呢?
你的两个函数 _write_cache _display_cache 可以优化下啊.例如将缓存写到Redis里面,就没有你这个大量文件的问题了,而且速度比文件存储快多了.可靠性也强.

kamengwang 发表于 2012-2-16 09:45:12

本帖最后由 kamengwang 于 2012-2-16 09:46 编辑

我们centos,ext3格式的单文件夹文件数量上限是32000,超了就写不了东西了...

kinglike 发表于 2012-4-23 09:47:51

为啥我调用老出错?

五月杨柳 发表于 2012-5-24 16:17:57

我的也是,经常出错。

zshgsf 发表于 2012-7-11 15:24:04

如果当前访问的页面数据有时间字段的话可以取时间年份+月份做为文件夹 只要不是超大型网站的话 一个月的数据应该超过10W的不多吧

风筝 发表于 2012-8-30 17:49:51

学习

海中叶 发表于 2012-8-31 09:40:59

这个不能自己删除吧要是能自己删除主好了
页: [1] 2
查看完整版本: 优化ci缓存机制,扩展CI_Output