[Magento] Cache 探索

虽然 Magento 非常慢,但是 Magento 开发团队对 Magento 的架构的优化却非常棒, 这篇文章记录了我研究和学习 Magento 缓存层的过程和心得。我一直想把 Magento 默认的缓存层改到 Memcached 或者 Redis 上试试,Magento 默认的缓存层是基于文件IO的,如果将缓存层改成基于NoSQL,性能应该可以再提高一点。

从 Magento 后台来看, Magento 默认的缓存类型有8种:
cache-type

首先来看的 Collections Data 这个缓存类型:
所有 collection 的基类是 Varien_Data_Collection_Db, 从类名就可以看出, 这个类提供了和数据库交互的功能。
可以猜测,collection 在读取数据时的逻辑是:

  1. 先检查是否使用缓存
  2. 如果不使用缓存,则直接查询数据库,并返回查询结果
  3. 如果使用缓存,则先查询缓存是否已经存在
  4. 如果缓存不存在,则查询数据库,并将查询结果写入缓存,然后返回数据
  5. 如果缓存存在,则直接读取缓存中的数据,并返回数据

下面分析具体代码,先看Varien_Data_Collection_Db::load()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Varien_Data_Collection_Db::load()
/**
 * Load data
 *
 * @param   bool $printQuery
 * @param   bool $logQuery
 *
 * @return  Varien_Data_Collection_Db
 */

public function load($printQuery = false, $logQuery = false)
{
    if ($this->isLoaded()) {
        return $this;
    }

    $this->_beforeLoad();

    $this->_renderFilters()
         ->_renderOrders()
         ->_renderLimit();

    $this->printLogQuery($printQuery, $logQuery);
    $data = $this->getData();
    $this->resetData();

    if (is_array($data)) {
        foreach ($data as $row) {
            $item = $this->getNewEmptyItem();
            if ($this->getIdFieldName()) {
                $item->setIdFieldName($this->getIdFieldName());
            }
            $item->addData($row);
            $this->addItem($item);
        }
    }

    $this->_setIsLoaded();
    $this->_afterLoad();
    return $this;
}

可以看出,Varien_Data_Collection_Db::load()方法里通过 $data = $this->getData() 获取数据。
再看 getData() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Varien_Data_Collection_Db::getData()
/**
 * Get all data array for collection
 *
 * @return array
 */

public function getData()
{
    if ($this->_data === null) {
        $this->_renderFilters()
             ->_renderOrders()
             ->_renderLimit();
        $this->_data = $this->_fetchAll($this->_select);
        $this->_afterLoadData();
    }
    return $this->_data;
}

Varien_Data_Collection_Db::getData()方法里面又封装了一层,再来看 Varien_Data_Collection_Db::_fetchAll()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Varien_Data_Collection_Db::_fetchAll()
/**
 * Fetch collection data
 *
 * @param   Zend_Db_Select $select
 * @return  array
 */

protected function _fetchAll($select)
{
    if ($this->_canUseCache()) {
        $data = $this->_loadCache($select);
        if ($data) {
            $data = unserialize($data);
        } else {
            $data = $this->getConnection()->fetchAll($select, $this->_bindParams);
            $this->_saveCache($data, $select);
        }
    } else {
        $data = $this->getConnection()->fetchAll($select, $this->_bindParams);
    }
    return $data;
}

可以看出,collection 是通过 Varien_Data_Collection_Db::_fetchAll() 来获取数据的,并且其逻辑和上面预测的逻辑几乎一致:
通过 Varien_Data_Collection_Db::_canUseCache() 判断是否使用缓存,
假如使用缓存,则通过 Varien_Data_Collection_Db::_loadCache() 方法获取缓存,取得数据后,检查数据是否为真,如果为真(缓存存在),则反序列化获取的字符串;如果数据为假(缓存不存在),则查询数据库,并将查询得到的数据保存为缓存;
假如不使用缓存,则直接查询数据库获取数据。
最后将取得的数据返回。

那么如何控制 collection 是否使用缓存?
具体逻辑在 Varien_Data_Collection_Db::_canUseCache()方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * Check if cache can be used for collection data
 *
 * @return bool
 */

protected function _canUseCache()
{
    return $this->_getCacheInstance();
}

/**
 * Retrieve cache instance
 *
 * @return Zend_Cache_Core
 */

protected function _getCacheInstance()
{
    if (isset($this->_cacheConf['object'])) {
        return $this->_cacheConf['object'];
    }
    return false;
}

Varien_Data_Collection_Db::_canUseCache() 方法又调用了 Varien_Data_Collection_Db::_getCacheInstance() 方法。
其中逻辑是: 如果 Varien_Data_Collection_Db->_cacheConf[‘object’] 经过初始化,则返回此值;否则为假。
Varien_Data_Collection_Db->_cacheConf 是在 Varien_Data_Collection_Db::initCache() 方法中初始化的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * Initialize collection cache
 *
 * @param $object
 * @param string $idPrefix
 * @param array $tags
 * @return Varien_Data_Collection_Db
 */

public function initCache($object, $idPrefix, $tags)
{
    $this->_cacheConf = array(
        'object'    => $object,
        'prefix'    => $idPrefix,
        'tags'      => $tags
    );
    return $this;
}

Varien_Data_Collection_Db::initCache() 是一个 public 方法,所以如果你有一个 collection 需要使用缓存,则只需要显示调用它的 initCache($object, $idPrefix, $tags) 方法,传入合适的参数即可。