緩存永遠不是性能優化

摘要:緩存,一個讓程序員愛恨交織的東西。它可以加快你的訪問速度,但也可能會導致你的敏感信息丟失。

在完美的編程世界中,緩存這個詞常常在系統中扮演着差和低效的角色。使用它可能會給系統帶來一些從未見過的麻煩,但世界就是這麼搞笑,在某些時候,緩存卻是必須存在的。真的說愛你很難,恨你也很難!

還記得用匯編和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&^%$建築,你希望通過粉刷來隱藏掉那些僞劣的工藝和材質嗎?是的,你可能會當一切都沒有發生過,等到你被抓住以後,遊戲就玩完啦!

我們能做些什麼?

下面我會提供一些非常好的文章給你參考,至於如何做,這完全取決於你。

你能做的最好事情是在你代碼裏面應用童子軍規則

如果不能把代碼寫的很好,那代碼必須要一直保持清潔。我們已經見過許多代碼隨着時間的推移在腐爛和流失,所以我們必須積極預防這種退化。

美國童子軍有一個簡單的規則可以應用到我們的專業裏:每次優化都應該讓代碼更整潔


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