緩存競爭
對於互聯網業務來說,傳統的直接訪問資料庫方式,主要通過數據分片、一主多從等方式來扛住讀寫流量,但隨著數據量的積累和流量的激增,僅依賴資料庫來承接所有流量,不僅成本高、效率低、而且還伴隨著穩定性降低的風險。
鑒於大部分業務通常是讀多寫少(讀取頻率遠遠高於更新頻率),甚至存在讀操作數量高出寫操作多個數量級的情況。因此, 在架構設計中,常採用增加緩存層來提高系統的響應能力 ,提升數據讀寫性能、減少資料庫訪問壓力,從而提升業務的穩定性和訪問體驗。
根據 CAP 原理,分布式系統在可用性、一致性和分區容錯性上無法兼得,通常由於分區容錯無法避免,所以一致性和可用性難以同時成立。對於緩存系統來說, 如何保證其數據一致性是一個在應用緩存的同時不得不解決的問題 。
需要明確的是,緩存系統的數據一致性通常包括持久化層和緩存層的一致性、以及多級緩存之間的一致性,這里我們僅討論前者。持久化層和緩存層的一致性問題也通常被稱為雙寫一致性問題,「雙寫」意為數據既在資料庫中保存一份,也在緩存中保存一份。
對於一致性來說,包含強一致性和弱一致性 ,強一致性保證寫入後立即可以讀取,弱一致性則不保證立即可以讀取寫入後的值,而是盡可能的保證在經過一定時間後可以讀取到,在弱一致性中應用最為廣泛的模型則是最終一致性模型,即保證在一定時間之後寫入和讀取達到一致的狀態。對於應用緩存的大部分場景來說,追求的則是最終一致性,少部分對數據一致性要求極高的場景則會追求強一致性。
為了達到最終一致性,針對不同的場景,業界逐步形成了下面這幾種應用緩存的策略。
— 1 —
Cache-Aside
Cache-Aside 意為旁路緩存模式,是應用最為廣泛的一種緩存策略。下面的圖示展示了它的讀寫流程,來看看它是如何保證最終一致性的。在讀請求中,首先請求緩存,若緩存命中(cache hit),則直接返回緩存中的數據;若緩存未命中(cache miss),則查詢資料庫並將查詢結果更新至緩存,然後返回查詢出的數據(demand-filled look-aside )。在寫請求中,先更新資料庫,再刪除緩存(write-invalidate)。
1、為什麼刪除緩存,而不是更新緩存?
在 Cache-Aside 中,對於讀請求的處理比較容易理解,但在寫請求中,可能會有讀者提出疑問,為什麼要刪除緩存,而不是更新緩存?站在符合直覺的角度來看,更新緩存是一個容易被理解的方案,但站在性能和安全的角度,更新緩存則可能會導致一些不好的後果。
首先是性能 ,當該緩存對應的結果需要消耗大量的計算過程才能得到時,比如需要訪問多張資料庫表並聯合計算,那麼在寫操作中更新緩存的動作將會是一筆不小的開銷。同時,當寫操作較多時,可能也會存在剛更新的緩存還沒有被讀取到,又再次被更新的情況(這常被稱為緩存擾動),顯然,這樣的更新是白白消耗機器性能的,會導致緩存利用率不高。
而等到讀請求未命中緩存時再去更新,也符合懶載入的思路,需要時再進行計算。刪除緩存的操作不僅是冪等的,可以在發生異常時重試,而且寫-刪除和讀-更新在語義上更加對稱。
其次是安全 ,在並發場景下,在寫請求中更新緩存可能會引發數據的不一致問題。參考下面的圖示,若存在兩個來自不同線程的寫請求,首先來自線程 1 的寫請求更新了資料庫(step 1),接著來自線程 2 的寫請求再次更新了資料庫(step 3),但由於網路延遲等原因,線程 1 可能會晚於線程 2 更新緩存(step 4 晚於 step 3),那麼這樣便會導致最終寫入資料庫的結果是來自線程 2 的新值,寫入緩存的結果是來自線程 1 的舊值,即緩存落後於資料庫,此時再有讀請求命中緩存(step 5),讀取到的便是舊值。
2、為什麼先更新資料庫,而不是先刪除緩存?
另外,有讀者也會對更新資料庫和刪除緩存的時序產生疑問,那麼為什麼不先刪除緩存,再更新資料庫呢?在單線程下,這種方案看似具有一定合理性,這種合理性體現在刪除緩存成功。
但更新資料庫失敗的場景下,盡管緩存被刪除了,下次讀操作時,仍能將正確的數據寫回緩存,相對於 Cache-Aside 中更新資料庫成功,刪除緩存失敗的場景來說,先刪除緩存的方案似乎更合理一些。那麼,先刪除緩存有什麼問題呢?
問題仍然出現在並發場景下,首先來自線程 1 的寫請求刪除了緩存(step 1),接著來自線程 2 的讀請求由於緩存的刪除導致緩存未命中,根據 Cache-Aside 模式,線程 2 繼而查詢資料庫(step 2),但由於寫請求通常慢於讀請求,線程 1 更新資料庫的操作可能會晚於線程 2 查詢資料庫後更新緩存的操作(step 4 晚於 step 3),那麼這樣便會導致最終寫入緩存的結果是來自線程 2 中查詢到的舊值,而寫入資料庫的結果是來自線程 1 的新值,即緩存落後於資料庫,此時再有讀請求命中緩存( step 5 ),讀取到的便是舊值。
另外,先刪除緩存,由於緩存中數據缺失,加劇資料庫的請求壓力,可能會增大緩存穿透出現的概率。
3、如果選擇先刪除緩存,再更新資料庫,那如何解決一致性問題呢?
為了避免「先刪除緩存,再更新資料庫」這一方案在讀寫並發時可能帶來的緩存臟數據,業界又提出了延時雙刪的策略,即在更新資料庫之後,延遲一段時間再次刪除緩存,為了保證第二次刪除緩存的時間點在讀請求更新緩存之後,這個延遲時間的經驗值通常應稍大於業務中讀請求的耗時。
延遲的實現可以在代碼中 sleep 或採用延遲隊列。顯而易見的是,無論這個值如何預估,都很難和讀請求的完成時間點准確銜接,這也是延時雙刪被詬病的主要原因。
4、那麼 Cache-Aside 存在數據不一致的可能嗎?
在 Cache-Aside 中,也存在數據不一致的可能性。在下面的讀寫並發場景下,首先來自線程 1 的讀請求在未命中緩存的情況下查詢資料庫(step 1),接著來自線程 2 的寫請求更新資料庫(step 2),但由於一些極端原因,線程 1 中讀請求的更新緩存操作晚於線程 2 中寫請求的刪除緩存的操作(step 4 晚於 step 3),那麼這樣便會導致最終寫入緩存中的是來自線程 1 的舊值,而寫入資料庫中的是來自線程 2 的新值,即緩存落後於資料庫,此時再有讀請求命中緩存(step 5),讀取到的便是舊值。
這種場景的出現,不僅需要緩存失效且讀寫並發執行,而且還需要讀請求查詢資料庫的執行早於寫請求更新資料庫,同時讀請求的執行完成晚於寫請求。足以見得,這種 不一致場景產生的條件非常嚴格,在實際的生產中出現的可能性較小 。
除此之外,在並發環境下,Cache-Aside 中也存在讀請求命中緩存的時間點在寫請求更新資料庫之後,刪除緩存之前,這樣也會導致讀請求查詢到的緩存落後於資料庫的情況。
雖然在下一次讀請求中,緩存會被更新,但如果業務層面對這種情況的容忍度較低,那麼可以採用加鎖在寫請求中保證「更新資料庫&刪除緩存」的串列執行為原子性操作(同理也可對讀請求中緩存的更新加鎖)。 加鎖勢必會導致吞吐量的下降,故採取加鎖的方案應該對性能的損耗有所預期。
— 2 —
補償機制
我們在上面提到了,在 Cache-Aside 中可能存在更新資料庫成功,但刪除緩存失敗的場景,如果發生這種情況,那麼便會導致緩存中的數據落後於資料庫,產生數據的不一致的問題。
其實,不僅 Cache-Aside 存在這樣的問題,在延時雙刪等策略中也存在這樣的問題。針對可能出現的刪除失敗問題,目前業界主要有以下幾種補償機制。
1、刪除重試機制
由於同步重試刪除在性能上會影響吞吐量,所以常通過引入消息隊列,將刪除失敗的緩存對應的 key 放入消息隊列中,在對應的消費者中獲取刪除失敗的 key ,非同步重試刪除。這種方法在實現上相對簡單,但由於刪除失敗後的邏輯需要基於業務代碼的 trigger 來觸發 ,對業務代碼具有一定入侵性。
鑒於上述方案對業務代碼具有一定入侵性,所以需要一種更加優雅的解決方案,讓緩存刪除失敗的補償機制運行在背後,盡量少的耦合於業務代碼。一個簡單的思路是通過後台任務使用更新時間戳或者版本作為對比獲取資料庫的增量數據更新至緩存中,這種方式在小規模數據的場景可以起到一定作用,但其擴展性、穩定性都有所欠缺。
一個相對成熟的方案是基於 MySQL 資料庫增量日誌進行解析和消費,這里較為流行的是阿里巴巴開源的作為 MySQL binlog 增量獲取和解析的組件 canal(類似的開源組件還有 Maxwell、Databus 等)。
canal sever 模擬 MySQL slave 的交互協議,偽裝為 MySQL slave,向 MySQL master 發送 mp 協議,MySQL master 收到 mp 請求,開始推送 binary log 給 slave (即 canal sever ),canal sever 解析 binary log 對象(原始為 byte 流),可由 canal client 拉取進行消費,同時 canal server 也默認支持將變更記錄投遞到 MQ 系統中,主動推送給其他系統進行消費。
在 ack 機制的加持下,不管是推送還是拉取,都可以有效的保證數據按照預期被消費。當前版本的 canal 支持的 MQ 有 Kafka 或者 RocketMQ。另外, canal 依賴 ZooKeeper 作為分布式協調組件來實現 HA ,canal 的 HA 分為兩個部分:
那麼,針對緩存的刪除操作便可以在 canal client 或 consumer 中編寫相關業務代碼來完成。這樣,結合資料庫日誌增量解析消費的方案以及 Cache-Aside 模型,在讀請求中未命中緩存時更新緩存(通常這里會涉及到復雜的業務邏輯),在寫請求更新資料庫後刪除緩存,並基於日誌增量解析來補償資料庫更新時可能的緩存刪除失敗問題,在絕大多數場景下,可以有效的保證緩存的最終一致性。
另外需要注意的是,還應該隔離事務與緩存,確保資料庫入庫後再進行緩存的刪除操作。 比如考慮到資料庫的主從架構,主從同步及讀從寫主的場景下,可能會造成讀取到從庫的舊數據後便更新了緩存,導致緩存落後於資料庫的問題,這就要求對緩存的刪除應該確保在資料庫操作完成之後。所以,基於 binlog 增量日誌進行數據同步的方案,可以通過選擇解析從節點的 binlog,來避免主從同步下刪除緩存過早的問題。
3、數據傳輸服務 DTS
— 3 —
Read-Through
Read-Through 意為讀穿透模式,它的流程和 Cache-Aside 類似,不同點在於 Read-Through 中多了一個訪問控制層,讀請求只和該訪問控制層進行交互,而背後緩存命中與否的邏輯則由訪問控制層與數據源進行交互,業務層的實現會更加簡潔,並且對於緩存層及持久化層交互的封裝程度更高,更易於移植。
— 4 —
Write-Through
Write-Through 意為直寫模式,對於 Write-Through 直寫模式來說,它也增加了訪問控制層來提供更高程度的封裝。不同於 Cache-Aside 的是,Write-Through 直寫模式在寫請求更新資料庫之後,並不會刪除緩存,而是更新緩存。
這種方式的 優勢在於讀請求過程簡單 ,不需要查詢資料庫更新緩存等操作。但其劣勢也非常明顯,除了上面我們提到的更新資料庫再更新緩存的弊端之外,這種方案還會造成更新效率低,並且兩個寫操作任何一次寫失敗都會造成數據不一致。
如果要使用這種方案, 最好可以將這兩個操作作為事務處理,可以同時失敗或者同時成功,支持回滾,並且防止並發環境下的不一致 。另外,為了防止緩存擾動的頻發,也可以給緩存增加 TTL 來緩解。
站在可行性的角度,不管是 Write-Through 模式還是 Cache-Aside 模式,理想狀況下都可以通過分布式事務保證緩存層數據與持久化層數據的一致性,但在實際項目中,大多都對一致性的要求存在一些寬容度,所以在方案上往往有所折衷。
Write-Through 直寫模式適合寫操作較多,並且對一致性要求較高的場景,在應用 Write-Through 模式時,也需要通過一定的補償機制來解決它的問題。首先,在並發環境下,我們前面提到了先更新資料庫,再更新緩存會導致緩存和資料庫的不一致,那麼先更新緩存,再更新資料庫呢?
這樣的操作時序仍然會導致下面這樣線程 1 先更新緩存,最後更新資料庫的情況,即由於線程 1 和 線程 2 的執行不確定性導致資料庫和緩存的不一致。這種由於線程競爭導致的緩存不一致,可以通過分布式鎖解決,保證對緩存和資料庫的操作僅能由同一個線程完成。對於沒有拿到鎖的線程,一是通過鎖的 timeout 時間進行控制,二是將請求暫存在消息隊列中順序消費。
在下面這種並發執行場景下,來自線程 1 的寫請求更新了資料庫,接著來自線程 2 的讀請求命中緩存,接著線程 1 才更新緩存,這樣便會導致線程 2 讀取到的緩存落後於資料庫。同理,先更新緩存後更新資料庫在寫請求和讀請求並發時,也會出現類似的問題。面對這種場景,我們也可以加鎖解決。
另在,在 Write-Through 模式下,不管是先更新緩存還是先更新資料庫,都存在更新緩存或者更新資料庫失敗的情況,上面提到的重試機制和補償機制在這里也是奏效的。
— 5 —
Write-Behind
Write behind 意為非同步回寫模式,它也具有類似 Read-Through/Write-Through 的訪問控制層,不同的是,Write behind 在處理寫請求時,只更新緩存而不更新資料庫,對於資料庫的更新,則是通過批量非同步更新的方式進行的,批量寫入的時間點可以選在資料庫負載較低的時間進行。
在 Write-Behind 模式下,寫請求延遲較低,減輕了資料庫的壓力,具有較好的吞吐性。但資料庫和緩存的一致性較弱,比如當更新的數據還未被寫入資料庫時,直接從資料庫中查詢數據是落後於緩存的。同時,緩存的負載較大,如果緩存宕機會導致數據丟失,所以需要做好緩存的高可用。顯然,Write behind 模式下適合大量寫操作的場景,常用於電商秒殺場景中庫存的扣減。
— 6 —
Write-Around
如果一些非核心業務,對一致性的要求較弱,可以選擇在 cache aside 讀模式下增加一個緩存過期時間,在寫請求中僅僅更新資料庫,不做任何刪除或更新緩存的操作,這樣,緩存僅能通過過期時間失效。這種方案實現簡單,但緩存中的數據和資料庫數據一致性較差,往往會造成用戶的體驗較差,應慎重選擇。
— 7 —
總結
在解決緩存一致性的過程中,有多種途徑可以保證緩存的最終一致性,應該根據場景來設計合適的方案,讀多寫少的場景下,可以選擇採用「Cache-Aside 結合消費資料庫日誌做補償」的方案,寫多的場景下,可以選擇採用「Write-Through 結合分布式鎖」的方案 ,寫多的極端場景下,可以選擇採用「Write-Behind」的方案。
2. 硬碟緩存16m的與32m的差別大嗎
2M緩存硬碟讀寫性能和使用壽命都更好一些。
硬碟緩存的主要作用是將經常需要讀寫用到的數據存儲在緩存之中,在用的時候計算機優先使用緩存中的數據,當緩存中的數據到達一定量時再寫入到硬碟中,這樣可以減少實際的磁碟操作,有效的保護磁碟免於重復的讀寫操作而導致的損壞,也能減少寫入所需的時間。
32M緩存比16M緩存要大,相對來說對硬碟的保護也更多一些,所以在不考慮其它因素的影響下,32M緩存的硬碟使用壽命會比16M緩存的硬碟壽命更久。
磁碟緩存減少CPU透過I/O讀取磁碟機的次數,提升磁碟I/O的效率,用一塊內存來儲存存取較頻繁的磁碟內容;因為內存的存取是電子動作,而磁碟的存取是I/O動作,感覺上磁碟I/O變得較為快速。
正是由於大緩存帶來的讀寫速度提升和為了應對固態硬碟的競爭,傳統機械硬碟廠家推出了混合硬碟,也就是機械硬碟+大緩存+大容量快閃記憶體的組合。這樣做的優點很明顯,第一大容量快閃記憶體使得系統運行速度提升,第二在數據傳輸過程中數據以固態硬碟的速度傳輸到快閃記憶體中然後再轉到緩存最後連續讀寫到機械硬碟中,大容量快閃記憶體起到了一定的緩沖作用(只要傳輸的數據不是遠大於快閃記憶體容量的情況下傳輸速度能夠與固態硬碟看齊)。
3. 怎樣提高IIS伺服器性能,加快伺服器速度
1、應該分配和釋放多個對象
你應該盡量避免過量分配內存,因為內存分配可能是代價高昂的。釋攔行放內存塊可能更昂貴,因為大多數分配算符總是企圖連接臨近的已釋放的內存塊成為更大的塊。直到Windows NT? 4.0 service pack 4.0,在多線程處理中,系統堆通常都運行得很糟。堆被一個全局鎖保護,並且在多處理器系統上是不可擴展的。
2.不應該考慮使用處理器高速緩存
大多數人都知道由虛擬內存子系統導致的hard 頁錯誤代價很高,最好避免。但是許多人認為其他內存訪問方法沒有什麼區別。自從80486以後,這一觀點就不對了。現代的CPUs比RAM要快得多,RAM至少需要兩級內存緩存 ,高速L1 緩存能保存8KB數據和8KB指令,而較慢的L2 緩存能保存幾百KB的數據和代碼,這些數據和代碼混合在一起。L1 緩存中內存區域的一個引用需要一個時鍾周期,L2 緩存的引用需要4到7個時鍾周期,而主內存的引用需要許多個處理器時鍾周期。後一數字不久將會超過100個時鍾周期。在許多方面,緩存像一個小型的,高速的,虛擬內存系統。
至於和緩存有關的基本內存單元不是位元組而是緩存列。Pentium 緩存列有32個位元組寬。Alpha 緩存列有64個位元組寬。這意味著在L1 緩存中只有512個slot給代碼和數據。如果多個數據一起使用(時間位置)而並不存儲在一起(空間位置),性能會很差。數組的空間位置很好,而相互連接的列表和其他基於指針的數據結構的位置往往很差。
把數據打包到同一個緩存列中通常會有利於提高性能,但是它也會破壞多處理器系統的性能。內存子系統很難協調處理器間的緩存。如果一個被所有處理器使用的只讀數據,和一個由一個處理器使用並頻繁更新的數據共享一個緩存 列,那麼緩存將會花費很長時間更新這個緩存列的拷貝。這個Ping-Pong高速游戲通常被稱為"緩存 sloshing"。如果只讀數據在一個不同的緩存 列中,就可以避免sloshing。
對代碼進行空間優化比進行速度優化效率更高。代碼越少,代碼所佔的頁也越少,這樣需要的運行設置和產生的頁錯誤也會更少,同時占據的緩存 列也會更少。然而,某些核心函數應該進行速度優化。可以利用profiler去識別這些函數。
3.決不要緩存頻繁使用的數據。
軟體緩存可以被各種應用程序使用。當一個計算代價很高時,你會保存結果的一個拷貝。這是一個典型的時空折中方法:犧牲一些存儲空間以節省時間。如果做得好,這種方法可能非常有效。
你必須正確地進行緩存。如果緩存了錯誤數據,就會浪費存儲空間。如果緩存得太多,其他操作可以使用的內存將會很少。如果緩存得太少,效率又會很低,因為你必須重新計算被緩存 遺漏的數據。如果將時間敏感數據緩存得時間過長,這些數據將會過時。一般,伺服器更關心的是速度而不是空間,所以他們要比桌面系統進行更多的緩存。一定要定期去除不用的緩存,否則將會有運行設置問題。
4.應該創建多個線程,越多越好。
調整伺服器中起作用的線程數目是很重要的。如果線程是I/O-bound的,將會花費很多時間用來等待I/O的完成-一個被阻塞的線程就是一個不做任何有用工作的線程。加入額外的線程可以增加通量,但裂擾是加入過多的線程將會降低伺服器的性能,因為上下文交換將會成為一個重大的overhead。上下文交換速度應該低的原因有三個:上下文交換是單純的overhead,對應用程序的工作沒有任何益處;上下文交換用盡了寶貴的時鍾周期;最糟的是,上下文交換將處理器的緩存填滿了沒用的數據,替換這些數據是代價高昂的。
有很多事情是依靠你的線程化結構的。每個客戶端一個線程是絕對不合適的。因為對於大量用戶端,它的擴展性不好。上下文交換變得難以忍受,Windows NT用盡了資源。線程池模型會工作得更好,在這種方法中一個工人線程池將處理一條請求列,因為Windows 2000提供了相應的APIs,如QueueUserWorkItem。
5.應該對數據結構使用全局鎖
使數據線程安全的最簡單方法是把它套上一把大鎖。為簡單起見,所有的東西都用同一把鎖。這種方法會有一個問題:序列化。為了得到鎖,每一個要處理數據的線程都必須排隊等候。如果線程被一把鎖阻塞,它沒有在做任何有用的事。當伺服器的負載較輕時,簡源嘩這個問題並不常見,因為一次可能只有一個線程需要鎖。在負載很重的情況下,對鎖的激烈爭奪可能就會成為一個大問題。
設想在多車道高速公路上發生了一個意外事故,這條高速公路上的所有車輛都被轉向一條狹窄的道路。如果車輛很少,這一轉換對交通流的速率的影響可以忽略。如果車輛很多,當車輛慢慢並入那條單通道時,交通阻塞會延伸幾英里。
有幾種技術能夠減少鎖競爭。
· 不要過分保護,也就是說,不是非常必要不要鎖住數據。只有需要時才去持有鎖,而且時間不要過長。不要在大段代碼周圍或頻繁執行的代碼中沒必要地使用鎖,這一點很重要。
· 對數據進行分割,使它能夠用一套獨立的鎖保護。例如,一個符號表可以按標識符的第一個字母分割,這樣在修改名字以Q開頭的符號的值時,就不會去讀名字以H開頭的符號的值。
· 使用APIs的Interlocked 系列(InterlockedIncrement,等)自動修改數據而不需要鎖。
· 當數據不是經常被修改時可以使用多讀者/單作者(multi-reader/single-writer)鎖。你將獲得更好的並發性,盡管鎖操作的代價將更高並且你可能會冒餓死作者的危險。
· 在關鍵部分使用循環計數器。參見Windows NT 4.0 service pack 3中的SetCriticalSectionSpinCount API。
· 如果你不能得到鎖,使用TryEnterCriticalSection並做一些其他的有用的工作。
高競爭導致serialization,serialization導致降低CPU的利用率,這促使用戶加入更多的線程,結果事情變得更糟。
6.不必注意多處理器機器
你的代碼在多處理器系統上比在單處理器系統上運行得還要糟,這可能是件令人惡心的事。一個很自然的想法是,在一個N維系統上運行N次會更好。性能很差的原因是競爭:鎖競爭,匯流排競爭,和/或緩存列競爭。處理器都在是爭奪共享資源的所有權,而不是做更多的工作。
如果你一定要編寫多線程應用程序的話,你應該在多處理器盒上對你的應用程序進行強度測試和性能測試。單處理器系統通過時間分片地執行線程而提供一個並發性的假象。多處理器盒具有真正的並發性,競爭環境和競爭更容易發生。
7.應該始終使用模塊化調用;他們很有趣。
利用同步模塊化調用來執行I/O操作對大多數桌面應用程序來說是合適的。但是,他們不是使用伺服器上的CPU(s)的好方法。I/O操作要花費上百萬個時鍾周期來完成,這些時鍾周期本來可以被更好地利用。利用非同步I/O你能得到顯著提高的用戶請求率和I/O通量,不過增加了額外的復雜性。
如果你有需要花費很長時間的模塊化調用或I/O操作,你應該考調撥多少資源給他們。你想使用所有的線程還是有個限制?一般地,使用有限的幾個線程要好些。構建一個小的線程池和隊列,利用隊列來安排線程的工作完成模塊化調用。這樣,其他線程就可以拾取和處理你的非模塊化的請求。
8.不要進行測量
當你能夠測量你所談論的事情並用數字表達它時,這就表示你對他有了一定的了解;但是如果你不能用數字表達時,你的知識是貧瘠的不能令人滿意的;這可能是知識的開始,但這時你簡直不可能將你的思想提高到科學的水平。
- Lord Kelvin (William Thomson)
如果不測量你就不能了解應用程序的特性。你在黑暗中摸索,一半是靠猜測。如果不識別性能問題,你就不能做任何改進或做出工作量計劃。
測量包括黑匣子測量和profiling。黑匣子測量的意思是收集由性能計數器(內存使用,上下文交換,CPU利用等)和外部檢測工具(通量,反映時間等)所顯示的數據。為了profile你的代碼,你編譯代碼的一個工具版,然後在各種條件下運行它,並收集關於執行時間和過程調用頻率的統計數據。
測量如果不用於分析的話就一點用都沒有。測量將不僅告訴你有問題,而且甚至能幫助你找到問題發生在哪,但它不能告訴你為什麼會有問題。對問題進行分析以便你能正確地改正他們。要從根本上解決問題而不是停留在表面現象。
當你進行改動後,要重新測量。你要知道你的改動是否有效。改動也可能會暴露其他性能問題,測量-分析-改正-再測量的循環就會重新開始。你也必須要有規律地進行測量,以便發現性能衰退問題。
9.應該使用單一用戶,單一請求的測試方法。
書寫ASP和ISAPI應用程序的一個通病是只用一個瀏覽器去測試應用程序。當他們在Internet上應用他們的程序時,他們才發現他們的應用程序不能處理高負載,並且通量和反應時間另人可憐。
用一個瀏覽器測試是必要的但是不夠的。如果瀏覽器反應得不夠快,你就知道你有麻煩了。但即使它在使用一個瀏覽器時很快,你也不知道它處理負載的能力如何。如果十幾個用戶同時請求會發生什麼事?一百個呢?你的應用程序能容忍什麼樣的通量?它能提供什麼樣的反應時間?在輕載時這些數字會怎樣?中等負載呢?重載呢?在多處理器機器上你的應用程序會如何?對你的應用程序進行強度測試,這對於找出bugs發現性能問題來說是基本的。
類似的負載測試考慮適用於所有的伺服器應用程序。
10.不應使用實際環境。
人們往往只在幾個特定的,人工的環境(如下benchmarks)下調整應用程序。選擇和實際情況相對應的各種情況,並為針對各種操作進行優化,這一點很重要。如果你不這樣做,你的用戶和評論家一定會這樣做,並且他們將依此來評判你的應用程序的好壞。
4. 什麼是高速緩存,作用是什麼
什麼是高速緩存技術:
高速緩存英文是cache。一種特殊的存儲器子系統,其中復制了頻繁使用的數據,以利於CPU快速訪問。存儲器的高速緩沖存儲器存儲了頻繁訪問的 RAM 位置的內容及這些數據項的存儲地址。當處理器引用存儲器中的某地址時,高速緩沖存儲器便檢查是否存有該地址。如果存有該地址,則將數據返回處理器;如果沒有保存該地址,則進行常規的存儲器訪問。因為高速緩沖存儲器總是比主RAM 存儲器速度快,所以當 RAM 的訪問速度低於微處理器的速度時,常使用高速緩沖存儲器。
高速緩存的作用:
在CPU開始執行任何指令之前,都會首先從內存中取得該條指令以及其它一些相關數據和信息。為了加快CPU的運行速度,幾乎所有的晶元都採用兩種不同類型的內部存儲器,即高速緩存。緩存被用來臨時存放一些經常被使用的程序片段或數據。
一級高速緩存是性能最好緩存類型,與解釋指令和執行算術運算的處理單元一到構成CPU的核心。CPU可以在全速運行的狀態下讀取存放在一級高速緩存中的指令或數據。Intel的處理器產品一般都會具有32K的一級緩存,而象AMD或Via這種競爭對手的產品則會使用更多的一級緩存。
如果在一級緩存中沒有找到所需要的指令或數據,處理器會查看容量更大的二級緩存。二級緩存既可以被集成到CPU晶元內部,也可以作為外部緩存。Pentium II處理器具有512K的二級緩存,工作速度相當於CPU速度的一半。Celeron以及更新的Pentium III晶元則分別具有128K和256K的在片二級緩存,能夠在處理器全速下運行。
對於存放在速度較慢的二級緩存中的指令或數據,處理器往往需要等待2到4個時鍾周期。為了充分利用計算資源,CPU可以在這段時間內查看和執行其它正在等候處理,但不需要使用額外數據的指令,從而提高整個系統的速度,把空閑時間降低到最低程度。
5. 高速緩存的運算速度高於內存而底於CPU。對嗎
首先應該明確,緩存是不參與計算的,所以沒法與處理器相比較的,但可以有頻率與處理器相比,比處理器慢。比內存存儲速度快這個是沒有錯的。