本篇分析下SugarCRM的缓存,缓存文件主要存放在./include/SugarCache里,实例化主要是SugarCache::instance()方法来实现。
// ./include/SugarCache/SugarCache.php class SugarCache { const EXTERNAL_CACHE_NULL_VALUE = "SUGAR_CACHE_NULL_ZZ"; protected static $_cacheInstance; /** * @var true if the cache has been reset during this request, so we no longer return values from * cache until the next reset */ public static $isCacheReset = false; private function __construct() {} /** * initializes the cache in question * 初始化缓存 */ protected static function _init() { $lastPriority = 1000; // 返回的是include/SugarCache路径及定制的custome/include/SugarCache中详细路径数组 $locations = SugarAutoLoader::getFilesCustom('include/SugarCache'); // 如果为空的话,则取SugarCRM默认的缓存类 if(empty($locations)) { $locations = array('include/SugarCache/SugarCacheMemory.php'); } // 依次加载include/SugarCache文件,本身文件除外 // 并实例化SugarCacheAbstract的子类,也就是继承SugarCacheAbstract的类 // 这里最终返回的实例条件为:$lastPriority为最小并且$cacheInstance->useBackend()成立 /* SugarCacheAbstract 1000 SugarCacheMemory 999 为SugarCRM自带的缓存,当所有的缓存都没打开时返回此实例 SugarCacheFile 990 SugarCachesMash 950 SugarCacheAPC 940 SugarCacheWincache 930 SugarCacheRedis 920 SugarCacheZend 910 SugarCacheMemcached 900 SugarCacheMemcache 900 */ foreach ( $locations as $location ) { $cacheClass = basename($location, ".php"); if($cacheClass == 'SugarCache') continue; require_once $location; if ( class_exists($cacheClass) && is_subclass_of($cacheClass,'SugarCacheAbstract') ) { $GLOBALS['log']->debug("Found cache backend $cacheClass"); $cacheInstance = new $cacheClass(); if ( $cacheInstance->useBackend() && $cacheInstance->getPriority() < $lastPriority ) { $GLOBALS['log']->debug("Using cache backend $cacheClass, since ".$cacheInstance->getPriority()." is less than ".$lastPriority); self::$_cacheInstance = $cacheInstance; $lastPriority = $cacheInstance->getPriority(); } } } } /** * Returns the instance of the SugarCacheAbstract object, cooresponding to the external * cache being used. * 实例化缓存类 * 条件:必须为SugarCacheAbstract类的子类,不是的话,就初始化 */ public static function instance() { if ( !is_subclass_of(self::$_cacheInstance,'SugarCacheAbstract') ) self::_init(); return self::$_cacheInstance; } /** * Try to reset any opcode caches we know about * * @todo make it so developers can extend this somehow */ public static function cleanOpcodes() { // APC if ( function_exists('apc_clear_cache') && ini_get('apc.stat') == 0 ) { apc_clear_cache(); } // Wincache if ( function_exists('wincache_refresh_if_changed') ) { wincache_refresh_if_changed(); } // Zend if ( function_exists('accelerator_reset') ) { accelerator_reset(); } // eAccelerator if ( function_exists('eaccelerator_clear') ) { eaccelerator_clear(); } // XCache if ( function_exists('xcache_clear_cache') && !ini_get('xcache.admin.enable_auth') ) { $max = xcache_count(XC_TYPE_PHP); for ($i = 0; $i < $max; $i++) { if (!xcache_clear_cache(XC_TYPE_PHP, $i)) { break; } } } } /** * Try to reset file from caches */ public static function cleanFile( $file ) { // APC if ( function_exists('apc_delete_file') && ini_get('apc.stat') == 0 ) { apc_delete_file( $file ); } } } // SugarAutoLoader::getFilesCustom('include/SugarCache'); public static function getFilesCustom($dir, $get_dirs = false, $extension = null) { return array_merge(self::getDirFiles($dir, $get_dirs, $extension), self::getDirFiles("custom/$dir", $get_dirs, $extension)); } public static function getDirFiles($dir, $get_dirs = false, $extension = null, $recursive = false) { // In development mode we don't have the filemap available. To avoid // full rebuilds on every page load, only load what we need at this // point. // 开发者模式中,不会有./cache/file_map.php和class_map.php文件,也没有$filemap映射类,每次都需重新生成 if (self::$devMode) { $data = self::scanSubDir($dir); } else { // 如果映射类也为空,那么则要重新走初始化了 if (empty(self::$filemap)) { self::init(); } $data = self::$filemap; } // remove leading . if present $extension = ltrim($extension, "."); $dir = rtrim($dir, "/"); $parts = explode('/', $dir); foreach ($parts as $part) { if (empty($part)) { continue; // allow sequences of /s } if (!isset($data[$part])) { return array(); } $data = $data[$part]; } // 此步时,$data为数组嘴里层的文件列表 // 接下的flatten返回的是加上路劲的文件列表 /* Array ( [SugarCache.php] => 1 [SugarCacheAbstract.php] => 1 [SugarCacheAPC.php] => 1 [SugarCacheFile.php] => 1 [SugarCacheMemcache.php] => 1 [SugarCacheMemcached.php] => 1 [SugarCacheMemory.php] => 1 [SugarCacheRedis.php] => 1 [SugarCachesMash.php] => 1 [SugarCacheWincache.php] => 1 [SugarCacheZend.php] => 1 ) */ if (!is_array($data)) { return array(); } return self::flatten($dir, $data, $get_dirs, $extension, $recursive); } protected function flatten($dir, array $data, $get_dirs, $extension, $recursive) { //lizhi $result = array(); foreach ($data as $file => $nodes) { // check extension if given if (!empty($extension) && pathinfo($file, PATHINFO_EXTENSION) != $extension) { continue; } $path = $dir . '/' . $file; // get dirs or files depending on $get_dirs if (is_array($nodes) == $get_dirs) { $result[] = $path; } if ($recursive && is_array($nodes)) { $result = array_merge( $result, self::flatten($path, $nodes, $get_dirs, $extension, $recursive) ); } } // 返回 /* Array ( [0] => include/SugarCache/SugarCache.php [1] => include/SugarCache/SugarCacheAbstract.php [2] => include/SugarCache/SugarCacheAPC.php [3] => include/SugarCache/SugarCacheFile.php [4] => include/SugarCache/SugarCacheMemcache.php [5] => include/SugarCache/SugarCacheMemcached.php [6] => include/SugarCache/SugarCacheMemory.php [7] => include/SugarCache/SugarCacheRedis.php [8] => include/SugarCache/SugarCachesMash.php [9] => include/SugarCache/SugarCacheWincache.php [10] => include/SugarCache/SugarCacheZend.php ) */ return $result; }
./include/SugarCache/SugarCache.php sugar_cache_retrieve($cache_key); function sugar_cache_retrieve($key) { // 这里会调用SugarCacheMemory的父类SugarCacheAbstract中的__get魔术方法 // 缓存类中的ttl并没有实现,只是实现了简单的存入、获取及销毁 return SugarCache::instance()->$key; } ./include/SugarCache/SugarCacheMemory.php class SugarCacheMemory extends SugarCacheAbstract { /** * @see SugarCacheAbstract::$_priority */ protected $_priority = 999; /** * @see SugarCacheAbstract::useBackend() */ public function useBackend() { // we'll always have this backend available return true; } /** * @see SugarCacheAbstract::_setExternal() * * Does nothing; cache is gone after request is done. */ protected function _setExternal($key,$value) { } /** * @see SugarCacheAbstract::_getExternal() * * Does nothing; cache is gone after request is done. */ protected function _getExternal($key) { } /** * @see SugarCacheAbstract::_clearExternal() * * Does nothing; cache is gone after request is done. */ protected function _clearExternal($key) { } /** * @see SugarCacheAbstract::_resetExternal() * * Does nothing; cache is gone after request is done. */ protected function _resetExternal() { } } ./include/SugarCache/SugarCacheAbstract.php abstract class SugarCacheAbstract { public function __get($key) { if ( SugarCache::$isCacheReset ) return null; $this->_cacheRequests++; if ( !$this->useLocalStore || !isset($this->_localStore[$key]) ) { $this->_localStore[$key] = $this->_getExternal($this->_keyPrefix.$key); if ( isset($this->_localStore[$key]) ) { $this->_cacheExternalHits++; } else { $this->_cacheMisses++; } } elseif ( isset($this->_localStore[$key]) ) { $this->_cacheLocalHits++; } if ( isset($this->_localStore[$key]) ) { return $this->_localStore[$key]; } return null; } public function __set( $key, $value) { $this->set($key, $value); } public function set($key, $value, $ttl = null) { if ( is_null($value) ) { $value = SugarCache::EXTERNAL_CACHE_NULL_VALUE; } if ( $this->useLocalStore ) { $this->_localStore[$key] = $value; } if( $ttl === NULL ) { $this->_setExternal($this->_keyPrefix.$key,$value); } else if( $ttl > 0 ) { //For BC reasons the setExternal signature will remain the same. $previousExpireTimeout = $this->_expireTimeout; $this->_expireTimeout = $ttl; $this->_setExternal($this->_keyPrefix.$key,$value); $this->_expireTimeout = $previousExpireTimeout; } } }
这里说说注意点,如果项目环境中装了redis【Memcached、Memcache也适合,如果同时装几种符合的缓存,那么可以看看上面缓存类的优先权】,那么得小心了,因为在实例化缓存时,会实例化redis,因为redis的缓存类小于项目本身的。如果项目中调用了sugar_cache_reset_full方法会清空redis所连接的数据库,切记切记。若是项目和redis部署在一起,有两种方法可以避过,一是注释掉清空redis库的方法【在./include/SugarCache/SugarCacheRedis.php中注释掉_resetExternal方法中的$this->_getRedisObject()->flushAll();语句】,一是在配置文件中把external_cache_disabled_redis置为true。
/** * Retrieve a key from cache. For the Zend Platform, a maximum age of 5 minutes is assumed. * * @param String $key -- The item to retrieve. * @return The item unserialized */ function sugar_cache_retrieve($key) { return SugarCache::instance()->$key; } /** * Put a value in the cache under a key * * @param String $key -- Global namespace cache. Key for the data. * @param Serializable $value -- The value to store in the cache. */ function sugar_cache_put($key, $value, $ttl = null) { SugarCache::instance()->set($key,$value, $ttl); } /** * Clear a key from the cache. This is used to invalidate a single key. * * @param String $key -- Key from global namespace */ function sugar_cache_clear($key) { unset(SugarCache::instance()->$key); } /** * Turn off external caching for the rest of this round trip and for all round * trips for the next cache timeout. This function should be called when global arrays * are affected (studio, module loader, upgrade wizard, ... ) and it is not ok to * wait for the cache to expire in order to see the change. */ function sugar_cache_reset() { SugarCache::instance()->reset(); SugarCache::cleanOpcodes(); } /** * Flush the cache in its entirety including the local and external store along with the opcodes. */ function sugar_cache_reset_full() { SugarCache::instance()->resetFull(); SugarCache::cleanOpcodes(); } /** * Clean out whatever opcode cache we may have out there. */ function sugar_clean_opcodes() { SugarCache::cleanOpcodes(); } /** * Internal -- Determine if there is an external cache available for use. * * @deprecated */ function check_cache() { SugarCache::instance(); } /** * This function is called once an external cache has been identified to ensure that it is correctly * working. * * @deprecated * * @return true for success, false for failure. */ function sugar_cache_validate() { $instance = SugarCache::instance(); return is_object($instance); } /** * Internal -- This function actually retrieves information from the caches. * It is a helper function that provides that actual cache API abstraction. * * @param unknown_type $key * @return unknown * @deprecated * @see sugar_cache_retrieve */ function external_cache_retrieve_helper($key) { return SugarCache::instance()->$key; }