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