虽然 Magento 非常慢,但是 Magento 开发团队对 Magento 的架构的优化却非常棒, 这篇文章记录了我研究和学习 Magento 缓存层的过程和心得。我一直想把 Magento 默认的缓存层改到 Memcached 或者 Redis 上试试,Magento 默认的缓存层是基于文件IO的,如果将缓存层改成基于NoSQL,性能应该可以再提高一点。
从 Magento 后台来看, Magento 默认的缓存类型有8种:
首先来看的 Collections Data 这个缓存类型:
所有 collection 的基类是 Varien_Data_Collection_Db, 从类名就可以看出, 这个类提供了和数据库交互的功能。
可以猜测,collection 在读取数据时的逻辑是:
先检查是否使用缓存
如果不使用缓存,则直接查询数据库,并返回查询结果
如果使用缓存,则先查询缓存是否已经存在
如果缓存不存在,则查询数据库,并将查询结果写入缓存,然后返回数据
如果缓存存在,则直接读取缓存中的数据,并返回数据
下面分析具体代码,先看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) 方法,传入合适的参数即可。