虽然 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) 方法,传入合适的参数即可。