2018年3月4日星期日

magento缓存系列详解:如何缓存一个block

magento是基于zend framework的,所以cache的使用基本也继承了他的一些特性。我们要知道缓存中有几个重要的概念:
数据本身;数据的标识;缓存生命期;缓存操作接口;
Zend_Cache 的使用比较简单, 它可以把数据保存到 File, Memcache, SQLite 等介质(称为后端, Backend)中. 还有前端(Frontend), 主要用来对要缓存的数据进行转换, 如序列化.
Zend Framework 后端主要有
    apc (Zend_Cache_Backend_Apc),
    files (Zend_Cache_Backend_File),
    memcached (Zend_Cache_Backend_Memcached)
    and some more.. 
Magento 增加了几种:
    database (Varien_Cache_Backend_Database)
    eaccelarator (Varien_Cache_Backend_Eaccelarator).

为了更深入的理解magento cache,本文就以缓存某个block为例,分析下他的工作流程和原理。在magento中,所有的block都是继承自Mage_Core_Block_Abstract,当输出某个block到页面时,会调用到这个类的toHtml方法,该方法中有如下code片段:
[php] view plain copy
  1. $html = $this->_loadCache();//加载缓存,假设为第一次加载,还没有被缓存过,进入if代码段  
  2. if ($html === false) {  
  3.     $translate = Mage::getSingleton('core/translate');  
  4.     /** @var $translate Mage_Core_Model_Translate */  
  5.     if ($this->hasData('translate_inline')) {  
  6.         $translate->setTranslateInline($this->getData('translate_inline'));  
  7.     }  
  8.   
  9.     $this->_beforeToHtml();  
  10.     $html = $this->_toHtml();  
  11.     $this->_saveCache($html);//这句代码很简单,但却在内部完成了大量的工作,下面进入它内部深入分析  
  12.   
  13.     if ($this->hasData('translate_inline')) {  
  14.         $translate->setTranslateInline(true);  
  15.     }  
  16. }  
继续看本类中的function _saveCache($data)
[php] view plain copy
  1. function _saveCache($data)  
  2.     /** 
  3.      * Save block content to cache storage 
  4.      * 
  5.      * @param string $data 
  6.      * @return Mage_Core_Block_Abstract 
  7.      */  
  8.     protected function _saveCache($data)  
  9.     {  
  10.         if (is_null($this->getCacheLifetime()) || !Mage::app()->useCache(self::CACHE_GROUP)) {  
  11.             return false;  
  12.         }  
  13.         $cacheKey = $this->getCacheKey();  
  14.         /** @var $session Mage_Core_Model_Session */  
  15.         $session = Mage::getSingleton('core/session');  
  16.         $data = str_replace(  
  17.             $session->getSessionIdQueryParam() . '=' . $session->getEncryptedSessionId(),  
  18.             $this->_getSidPlaceholder($cacheKey),  
  19.             $data  
  20.         );  
  21.   
  22.     //可以肯定进入了Mage_Core_Model_App 的function saveCache  
  23.         Mage::app()->saveCache($data$cacheKey$this->getCacheTags(), $this->getCacheLifetime());  
  24.         return $this;  
  25.     }  
打开Mage_Core_Model_App 的function saveCache
[php] view plain copy
  1. /** 
  2.  * Saving cache data 
  3.  * 
  4.  * @param   mixed $data 
  5.  * @param   string $id 
  6.  * @param   array $tags 
  7.  * @return  Mage_Core_Model_App 
  8.  */  
  9. public function saveCache($data$id$tags=array(), $lifeTime=false)  
  10. {  
  11.     $this->_cache->save($data$id$tags$lifeTime);  
  12.     return $this;  
  13. }  
可以看到$this->_cache这个对象调用了save,继续看$this->_cache到底属于哪个类的实例,在本类中找到protected function _initCache(),code如下:
[php] view plain copy
  1. /** 
  2.  * Initialize application cache instance 
  3.  * 
  4.  * @return Mage_Core_Model_App 
  5.  */  
  6. protected function _initCache()  
  7. {  
  8.     $this->_isCacheLocked = true;  
  9.     $options = $this->_config->getNode('global/cache');  
  10.     if ($options) {  
  11.         $options = $options->asArray();  
  12.     } else {  
  13.         $options = array();  
  14.     }  
  15.   
  16.     //实例化Mage_Core_Model_Cache,并传入参数$options  
  17.     $this->_cache = Mage::getModel('core/cache'$options);  
  18.     $this->_isCacheLocked = false;  
  19.     return $this;  
  20. }  
上面Mage::getModel('core/cache', $options)的$options是app/etc/local.xml中节点global/cache下的配置,假设ocal.xml如下:
[html] view plain copy
  1. <config>  
  2.     <global>  
  3.         <cache>  
  4.             <backend>apc</backend>  
  5.             <slow_backend>File</slow_backend>  
  6.             <auto_refresh_fast_cache>1</auto_refresh_fast_cache>  
  7.             <prefix>MYSHOP_</prefix>  
  8.             <default_priority>10</default_priority>  
  9.         </cache>  
  10.     </global>  
  11. </config>  
能看到$this->_cache是实例化了实例化Mage_Core_Model_Cache,所以Mage_Core_Model_App 中的$this->_cache->save($data, $id, $tags, $lifeTime)即调用的Mage_Core_Model_Cache的function save。进入Mage_Core_Model_Cache的save:
[php] view plain copy
  1. /** 
  2.  * Save data 
  3.  * 
  4.  * @param string $data 
  5.  * @param string $id 
  6.  * @param array $tags 
  7.  * @param int $lifeTime 
  8.  * @return bool 
  9.  */  
  10. public function save($data$id$tags=array(), $lifeTime=null)  
  11. {  
  12.     /** 
  13.      * Add global magento cache tag to all cached data exclude config cache 
  14.      */  
  15.     if (!in_array(Mage_Core_Model_Config::CACHE_TAG, $tags)) {  
  16.         $tags[] = Mage_Core_Model_App::CACHE_TAG;  
  17.     }  
  18.     return $this->_frontend->save((string)$data$this->_id($id), $this->_tags($tags), $lifeTime);  
  19. }  
可以看到$this->_frontend->save(...)这句,那么这里的_frontend是什么呢?继续看此类的构造函数(只列出部分code):
[php] view plain copy
  1. $backend    = $this->_getBackendOptions($options);  
  2.    $frontend   = $this->_getFrontendOptions($options);  
  3.   
  4.    $this->_frontend =Zend_Cache::factory('Varien_Cache_Core'$backend['type'], $frontend$backend['options'],true, true, true);  
没错_frontend就是类Varien_Cache_Core的一个实例,因为Varien_Cache_Core是继承自Zend_Cache_Core,所以这里_frontend实际上实例化的是Zend_Cache_Core,即调用的是Zend_Cache_Core的save,再继续分析,打开Zend_Cache_Core,找到function save它里面包含了如下代码片段:
[php] view plain copy
  1. if (($this->_extendedBackend) && ($this->_backendCapabilities['priority'])) {  
  2.     $result = $this->_backend->save($data$id$tags$specificLifetime$priority);  
  3. else {  
  4.     $result = $this->_backend->save($data$id$tags$specificLifetime);  
  5. }  
可以看到这里实际上又变成了_backend的来调用的save,(开头我们已经说了backend是用来保存数据的),那么这里_backend到底是哪个backend(file,db,apc...)呢?继续分析,在这个类里面有一个方法setBackend:
[php] view plain copy
  1. /** 
  2.  * Set the backend 
  3.  * 
  4.  * @param  Zend_Cache_Backend $backendObject 
  5.  * @throws Zend_Cache_Exception 
  6.  * @return void 
  7.  */  
  8. public function setBackend(Zend_Cache_Backend $backendObject)  
  9. {  
  10.    $this->_backend= $backendObject;  
  11.     // some options (listed in $_directivesList) have to be given  
  12.     // to the backend too (even if they are not "backend specific")  
  13.     $directives = array();  
  14.     foreach (Zend_Cache_Core::$_directivesList as $directive) {  
  15.         $directives[$directive] = $this->_options[$directive];  
  16.     }  
  17.     $this->_backend->setDirectives($directives);  
  18.     if (in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_backend))) {  
  19.         $this->_extendedBackend = true;  
  20.         $this->_backendCapabilities = $this->_backend->getCapabilities();  
  21.     }  
  22.   
  23. }  
里面有$this->_backend= $backendObject这句,$backendObject是传入的参数,那么是何时调用的setBackend呢?实际上,这个方法是在上边Mage_Core_Model_Cache里的save方法中调用$this->_frontend = Zend_Cache::factory('Varien_Cache_Core', $backend['type'], $frontend, $backend['options'], true, true, true);这句的的时候,已经被调用了,可以打开Zend_Cache的factory 方法查看,会发现有一句$frontendObject->setBackend($backendObject);参数$backendObject是factory传入的参数$backend['type'],$backend['type']到底是什么呢,我们在Mage_Core_Model_Cache的构造函数中看到$backend=$this->_getBackendOptions($options)这句,所以$backend是在本类的_getBackendOptions中被设置的,找到_getBackendOptions,code如下:
[php] view plain copy
  1. /** 
  2.  * Get cache backend options. Result array contain backend type ('type' key) and backend options ('options') 
  3.  * 
  4.  * @param   array $cacheOptions 
  5.  * @return  array 
  6.  */  
  7. protected function _getBackendOptions(array $cacheOptions)  
  8. {  
  9.     $enable2levels = false;  
  10.     $type   = isset($cacheOptions['backend']) ? $cacheOptions['backend'] : $this->_defaultBackend;  
  11.     if (isset($cacheOptions['backend_options']) && is_array($cacheOptions['backend_options'])) {  
  12.         $options = $cacheOptions['backend_options'];  
  13.     } else {  
  14.         $options = array();  
  15.     }  
  16.   
  17.     $backendType = false;  
  18.     switch (strtolower($type)) {  
  19.         case 'sqlite':  
  20.             if (extension_loaded('sqlite') && isset($options['cache_db_complete_path'])) {  
  21.                 $backendType = 'Sqlite';  
  22.             }  
  23.             break;  
  24.         case 'memcached':  
  25.             if (extension_loaded('memcache')) {  
  26.                 if (isset($cacheOptions['memcached'])) {  
  27.                     $options = $cacheOptions['memcached'];  
  28.                 }  
  29.                 $enable2levels = true;  
  30.                 $backendType = 'Memcached';  
  31.             }  
  32.             break;  
  33.         case 'apc':  
  34.             if (extension_loaded('apc') && ini_get('apc.enabled')) {  
  35.                 $enable2levels = true;  
  36.                 $backendType = 'Apc';  
  37.             }  
  38.             break;  
  39.         case 'xcache':  
  40.             if (extension_loaded('xcache')) {  
  41.                 $enable2levels = true;  
  42.                 $backendType = 'Xcache';  
  43.             }  
  44.             break;  
  45.         case 'eaccelerator':  
  46.         case 'varien_cache_backend_eaccelerator':  
  47.             if (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) {  
  48.                 $enable2levels = true;  
  49.                 $backendType = 'Varien_Cache_Backend_Eaccelerator';  
  50.             }  
  51.             break;  
  52.         case 'database':  
  53.             $backendType = 'Varien_Cache_Backend_Database';  
  54.             $options = $this->getDbAdapterOptions();  
  55.             break;  
  56.         default:  
  57.             if ($type != $this->_defaultBackend) {  
  58.                 try {  
  59.                     if (class_exists($type, true)) {  
  60.                         $implements = class_implements($type, true);  
  61.                         if (in_array('Zend_Cache_Backend_Interface'$implements)) {  
  62.                             $backendType = $type;  
  63.                         }  
  64.                     }  
  65.                 } catch (Exception $e) {  
  66.                 }  
  67.             }  
  68.     }  
  69.   
  70.     if (!$backendType) {  
  71.         $backendType = $this->_defaultBackend;  
  72.         foreach ($this->_defaultBackendOptions as $option => $value) {  
  73.             if (!array_key_exists($option$options)) {  
  74.                 $options[$option] = $value;  
  75.             }  
  76.         }  
  77.     }  
  78.   
  79.     $backendOptions = array('type' => $backendType'options' => $options);  
  80.     if ($enable2levels) {  
  81.         $backendOptions = $this->_getTwoLevelsBackendOptions($backendOptions$cacheOptions);  
  82.     }  
  83.     return $backendOptions;  
  84. }  
代码有些长,但这个方法很重要,后端具体使用什么类型存储缓存都是在这里被设置和转换的,有代码分析可知,默认的Backend为file,其他有sqlite, apc, memcached, xcache, eaccelerator, database等几种类型,当backend是memcached, apc,xcache时,magento就自动开启two level cache,具体设置可以自行看function _getTwoLevelsBackendOptions。

接着上面的save分析,本例因为backend设置为apc,所以backend最终的type为TwoLevels(apc默认开启TwoLevels),即调用的是Zend_Cache_Backend_TwoLevels的save 方法;save成功之后,第二次需要输出这个block时,首先会$this->_loadCache(),对于已经缓存过的block,会直接从缓存中取出。
以上就是magento缓存某个block的整个流程,至于具体如何去save,因backend不同会有所不同,可以自行参看源码;clean cache原理类似,不再赘述。
本例以magento 1.5版本为基础进行分析,不同版本code可能会有所不同;

没有评论:

发表评论

注意:只有此博客的成员才能发布评论。

Also Read: