缓存永远不是性能优化

摘要:缓存,一个让程序员爱恨交织的东西。它可以加快你的访问速度,但也可能会导致你的敏感信息丢失。

在完美的编程世界中,缓存这个词常常在系统中扮演着差和低效的角色。使用它可能会给系统带来一些从未见过的麻烦,但世界就是这么搞笑,在某些时候,缓存却是必须存在的。真的说爱你很难,恨你也很难!

还记得用汇编和C语言来编写代码的那段日子,我不得不做一套自己的内存管理系统,这样我编写的代码可以在4K的RAM上运行,虽然这样,仍然是令人称赞的。如今,有许多人喜欢拼凑代码片段然后再进行调用,我曾也是这种黑客里面的一员,但后来我决定要“改过自新”,好好学习编程知识。如今我已经学习许多概念、设计模型,当我回去看以前那种面条式代码时,真心让我反胃。

在你被这种思想祸害之前,我有非常合理的理由告诉你,缓存与性能优化是不同的。事实上,它是完全相反的——缓存是避免重复工作(这是不一样的自动化)并且也是隐藏错误和阻止服务器重载的一种非常得体的做法。

下面让我们来回答一些显而易见的问题:为什么不同?这很简单,“一个欠佳的应用程序缓存仍然是欠佳的,但因为有缓存执行时间将大大减少而效率却保持不变”。

性能优化是一门艺术。它通过分析项目中遇到的瓶颈和删除它们来实现,对那些差的代码进行重构。总而言之,性能优化会让你的代码更好!

虽然缓存不是性能优化,但并不是说缓存就很差劲,我们要一分为二的去看。在对其进行优化之前我们需要理解缓存是什么?用在哪?尤其是应用在黑客和一些购物性的网站。

下面让我们看看Wikipedia是如何描述缓存的:

在计算机系统中,高速缓存是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。

当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

缓存之所以有效,主要是因为程序运行时对内存的访问呈现局部性(Locality)特征。这种局部性既包括空间局部性(Spatial Locality),也包括时间局部性(Temporal Locality)。有效利用这种局部性,缓存可以达到极高的命中率。

在处理器看来,缓存是一个透明部件。因此,程序员通常无法直接干预对缓存的操作。但是,确实可以根据缓存的特点对程序代码实施特定优化,从而更好地利用缓存。

所以,未来的请求可以更快?那为什么不让它们快点开始呢?这是个与应用程序和语言无关的概念,这里没有任何单一的应用程序或语言可以给高速缓存带来提升。

但这似乎并没有什么意义!下面让我们看几个例子。

  1. $sql = "SELECT * FROM `database`.`table_name`"

你会认为在MySQL中的查询语句、Memcache或者其他一些高速缓存存储会让它更快吗?当然不是,无论怎样,它仅仅是一个在系统中的变量存储,在优化时,缓存这些东西根本不会有任何影响。

下面让我们以Magento为例。使用Magento EE 1.12配置且使用nginx和Memcache来禁止全页缓存。

下面SQL语句在生成一个分类页面时使用:

  1. SELECT  `e` . * ,  `cat_index`.`position` AS  `cat_index_position` ,  `price_index`.`price` ,  `price_index`.`tax_class_id` ,  `price_index`.`final_price` , IF( price_index.tier_price IS NOT NULL , LEAST( price_index.min_price, price_index.tier_price ) , price_index.min_price ) AS  `minimal_price` , `price_index`.`min_price` ,  `price_index`.`max_price` ,  `price_index`.`tier_price`   
  2. FROM  `catalog_product_entity` AS  `e`   
  3. INNER JOIN  `catalog_category_product_index` AS  `cat_index` ON cat_index.product_id = e.entity_id  
  4. AND cat_index.store_id =1 
  5. AND cat_index.visibility  
  6. IN ( 2, 4 )   
  7. AND cat_index.category_id =  '10' 
  8. AND cat_index.is_parent =1 
  9. INNER JOIN  `catalog_product_index_price` AS  `price_index` ON price_index.entity_id = e.entity_id  
  10. AND price_index.website_id =  '1' 
  11. AND price_index.customer_group_id =0 
  12. ORDER BY  `cat_index`.`position` ASC   
  13. LIMIT 15 

显示行0 - 6(共7个,查询花费0.0017秒)

  1. SELECT ‘e’.sku, 'cat_index'.'position' AS 'cat_index_position', 'price_index'.'price', 'price_index'.'tax_class_id', 'price_index'.'final_price', IF(price_index.tier_price IS NOT NULL, LEAST(price_index.min_price, price_index.tier_price), price_index.min_price) AS 'minimal_price', 'rice_index'.'min_price', 'price_index'.'max_price', `price_index`.`tier_price` FROM `catalog_product_entity` AS `e` INNER JOIN `catalog_category_product_index` AS `cat_index` ON cat_index.product_id=e.entity_id AND cat_index.store_id=1 AND cat_index.visibility IN(2, 4) AND cat_index.category_id='10' AND cat_index.is_parent=1 INNER JOIN `catalog_product_index_price` AS `price_index` ON price_index.entity_id = e.entity_id AND price_index.website_id = '1' AND price_index.customer_group_id = 0 ORDER BY `cat_index`.`position` ASC LIMIT 15 

显示行0 - 6(共7个,查询花费 0.0015秒)

你有注意到其中的区别吗?该脚本大致使用相同的时间来生成但通过使用索引或定制MySQL可以有很大的提升。

你看到了吗?性能提升正如你看到的这样简单。下面让我们来另外一个例子:

如果你打开Mage_Core_Model_Resource_Db_Abstract

让我们来看看加载方法和实现一些缓存来提升性能:

  1.  /**  
  2. * Fetches an object from the cache storage  
  3. * @param Mage_Core_Model_Abstract $object  
  4. * @return boolean | object  
  5.     */  
  6.    protected function isThisObjectCached(Mage_Core_Model_Abstract $object) {  
  7.        $key = md5(json_encode($object));  
  8.        $helper = Mage::helper('performance');  
  9.  
  10.        if ($cachedObject = $helper->fetchFromCache($key)) {  
  11.            return $cachedObject;  
  12.        }  
  13.  
  14.        return false;  
  15.    }  
  16.  
  17.    /**  
  18. * Saves an object in cache  
  19. * @param Mage_Core_Model_Abstract $object  
  20. * @return \Mage_Core_Model_Resource_Db_Abstract  
  21.     */  
  22.    protected function saveThisObjectInCache(Mage_Core_Model_Abstract $object) {  
  23.        $key = md5(json_encode($object));  
  24.        $helper = Mage::helper('performance');  
  25.  
  26.        $helper->saveObjectInCache($key, $object);  
  27.  
  28.        return $this;  
  29.    }  
  30.  
  31. /**  
  32. * Load an object  
  33.     *  
  34. * @param Mage_Core_Model_Abstract $object  
  35. * @param mixed $value  
  36. * @param string $field field to load by (defaults to model id)  
  37. * @return Mage_Core_Model_Resource_Db_Abstract  
  38.     */  
  39.    public function load(Mage_Core_Model_Abstract $object, $value, $field = null) {  
  40.        if ($cachedObject = $this->isThisObjectCached($object)) {  
  41.            $data = $cachedObject->getData();  
  42.            if ($data) {  
  43.                $object->setData($data);  
  44.            }  
  45.        } else {  
  46.            if (is_null($field)) {  
  47.                $field = $this->getIdFieldName();  
  48.            }  
  49.  
  50.            $read = $this->_getReadAdapter();  
  51.            if ($read && !is_null($value)) {  
  52.                $select = $this->_getLoadSelect($field, $value, $object);  
  53.                $data = $read->fetchRow($select);  
  54.  
  55.                if ($data) {  
  56.                    $object->setData($data);  
  57.                }  
  58.            }  
  59.        }  
  60.        $this->unserializeFields($object);  
  61.        $this->_afterLoad($object);  
  62.        $this->saveThisObjectInCache($object);  
  63.        return $this;  
  64.    } 

点击这里查看输出结果

正如你看到的,并没有太多的提高。甚至可以说,有了缓存,性能反而下降啦!

这到底是什么意思呢?

这意味着通过添加一个额外的层(缓存),你实际上是在增加总的处理时间,因为现在有额外的检查对象和缓存数据需要读取或保存。

在这种情况下:Memcache vs. MySQL,对我来说,性能至少不受捕获数据影响。

你为什么还会对缓存喋喋不休呢?

这到底是为什么呢?我是一个强烈倡导反向代理和缓存的程序员,不是因为我懒而是因为我是一个性能优化狂热者。每当我想到优化时,我会去增强和重构代码,而不是去隐藏错误或使用那些考虑欠佳的代码。

建筑与工程有许多共同点。在施工之前,你需要选择合适的材料、现货和会建的人去构建它。因此,如果你构建一个sh&^%$建筑,你希望通过粉刷来隐藏掉那些伪劣的工艺和材质吗?是的,你可能会当一切都没有发生过,等到你被抓住以后,游戏就玩完啦!

我们能做些什么?

下面我会提供一些非常好的文章给你参考,至于如何做,这完全取决于你。

你能做的最好事情是在你代码里面应用童子军规则

如果不能把代码写的很好,那代码必须要一直保持清洁。我们已经见过许多代码随着时间的推移在腐烂和流失,所以我们必须积极预防这种退化。

美国童子军有一个简单的规则可以应用到我们的专业里:每次优化都应该让代码更整洁


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章