SugarCRM源码分析之缓存


        本篇分析下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;
}

        缓存实例加载流程分析完后,接下来就看看调用分析了,这里假设只用系统里的默认缓存类SugarCacheMemory

./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。


       最后看看SugarCache类中调用缓存的最终方法

/**
 * 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;
}



你可能感兴趣的:(SugarCRM源码分析之缓存)