MacTalk:每個人都該懂點緩存
文/池建強
發布會在即,身體的疲勞和精神的亢奮差不多都到了一個臨界點(又不是你演講你至于嗎?至于吧……),于是決定寫篇文章歇歇腦子。寫什么呢,就寫寫發布會期間會大量用到的一種技術:緩存。是的,每個程序員都該懂點緩存。
緩存的英文名稱叫做 Cache,是個法國詞匯,每個字符里都流淌著高貴的血統。緩存的概念最早來源于 1967 年的一篇電子工程期刊論文,論文作者將「Cache」賦予了「safe keeping storage」的概念,用于計算機領域。
緩存最早出現主要是為了老大哥 CPU 服務的,為了減少 CPU 訪問內存所需平均時間,增加了緩存技術。隨著硬件技術和操作系統的發展,這部分技術牢牢地掌握在一些巨型芯片公司手里,那里的程序員每天游走在 CPU、Cache、SRAM、、write-through 和 write-back 之間,仿佛掌控著天地間每一個流動字節的意義,自信而優雅,我們把他們叫做文藝程序員。如果你想做一個文藝程序員,請用 Google 百度一下「CPU 緩存」,如果你不會用 Goolge,那么,最好離文藝遠一點。
普通程序員需要關注什么類型的緩存呢?比如磁盤緩存、Web 緩存、網絡緩存、分布式緩存,等等,這些是我們在開發系統軟件和互聯網服務時常常要用到的技術。那么,二逼程序員呢?哦,他們只要知道用 Hashtable 做緩存就夠了。
緩存的意義是什么?簡單來說,緩存是存儲數據的一個臨時場所,由于獲取原始數據的代價太大了,所以,我們會把一些使用頻繁的數據放到一個更容易 讀取,操作更快的池子(一般是內存)里,為這些數據分門別類,打上標簽,這樣用戶發來請求的時候,我們先在緩存池里進行快速檢索,如果拿到數據就直接返回 給用戶;如果沒有,再去數據庫或其他介質獲取原始數據返給用戶,同時把該數據打上標簽,放入緩存池。
就像你開了一個鞋店,店里永遠會存放一部分熱銷和新款的皮鞋,如果客戶每次看鞋買鞋的時候,你都要說等一哈,庫房就在五公里外,我去去就回。等你熱氣騰騰跑完一個十公里回來,你發現,連女消費者都走了。
網絡世界也是一樣,如果沒有緩存,用戶所有的請求都會直接穿透層層網絡,打到數據庫和磁盤 IO 上,隨著數據量的增加,用戶每次請求的時間會越來越長,這樣的后果是,磁盤不高興,數據庫不高興,用戶也不高興,然后就是數據庫率先罷工,用戶離你而去, 你,依然木有女朋友。
既然緩存這么重要,擁有大量用戶的互聯網應用都應該增加緩存服務了,那么是不是搞個 Hashtable 就可以了呢?我們可以先了解一些緩存的術語。
緩存命中
用戶發起了一個熱點數據的請求,系統接收到這個請求之后,就需要根據用戶的數據信息(key)去緩存池里尋找數據,如果根據用戶提供的 Key 找到了這個條目,并返回給用戶,這個過程叫一次緩存命中。
如果在緩存里沒找到需要的數據,在緩存空間還有空閑的情況下,系統會去原始數據源(一般是數據庫)獲取信息,返回給用戶,并把數據條目存儲到緩存中,以備不時之用。如果緩存空間已經達到上限,那么就要根據緩存替代策略,把舊的數據對象銷毀,把新的對象放入緩存池。
對于緩存服務的設計來說,命中率高的緩存系統,性能越好,命中率高,消耗的時間和資源越少。所以緩存服務并不是簡單的搭建一個 Memcached、Ehcache 或 Redis 就可以了,相關的技術,應用在合適的業務場景中,才能最大化的利用緩存的價值。
緩存成本
上述場景中,緩存沒有命中的時候,系統會從原始數據源中獲取數據,一般是數據庫或文件系統,然后再把數據放入緩存池中。這個過程需要的時間和空間,就是緩存成本。
一般為了避免緩存成本過高,系統初始化的時候,會同時進行緩存池的初始化,把我們需要的,已知的數據盡可能多的提前放入緩存池,這樣可以最大程度的提升緩存命中率,降低緩存成本。
緩存失效
當緩存中的數據需要更新的時候,說明緩存中的數據已經失效了,這時候就需要有相關的服務進行實時的數據更新,同時要保證數據的一致性,不能讓系統拿到已經失效的數據到處去招搖撞騙,這種情況,系統和用戶的內心,都是拒絕的。
替代策略
編程小球們初入江湖的時候,一般會覺得內存是可以無限使用了,看到服務器上標著 64G 內存這樣金光閃閃的配置之后,他們更覺得「廣闊天地,大有可為」,于是在系統里 new 出了一個又一個的 Hashtable,然后不停的往里面加入數據讀出數據,事實上,如果是三少系統(用戶少、數據少,功能少),這樣做一時半會還真出不了系統問題。如果是 系統級的緩存服務,要考慮的事情就比較多了。
每個緩存產品,一般都會有一個類似 maxmemory 的最大內存使用參數,這個參數肯定是小于物理內存的。一旦緩存數據達到上限,而又出現緩存沒有命中的情況時,系統就會踢出一些老弱病殘的緩存數據,加入新 條目。判斷老弱病殘的標準是什么呢?這就是替代策略。最理想的做法當然是把最沒用的數據踢出去,但是,做到最理想永遠是最難的,就像你永遠想找到團隊里最 沒用的那一個將其淘汰掉,但執行的時候總是極其艱難。因為除了數據,還有情緒。
幸好,數據沒情緒,我們可以通過算法搞定這件事。
常用的一些算法包括:FIFO、LFU、LRU、LRU2、ARC 等。
FIFO 就是先進先出,一種非常簡單的算法,緩存數據達到上限的時候,最先進入緩存的數據會最先被踢出去。很多老員工看到這一條都義憤填膺,所以,這個算法注定是 不被喜歡的,但是由于它簡單直接,很多開發者喜歡。嗯,有些企業老板也比較喜歡。Second Chance 和 CLock 是基于 FIFO 的改進,算法更加先進合理,也更復雜,微信里寫了也沒人看,感興趣的話,Google「緩存算法 CLock」等。
LFU 的全稱是 Least Frequently Used,最少使用算法,系統會為每個對象計算使用頻率,最不常用的緩存對象會被踢走,簡單粗暴。缺點是,一個傳統工業時代曾經被重用過的老員工,在互聯 網時代沒用了,由于其前期使用頻率很高,吃老本,所以數據會一直保存在緩存系統中,反而是后起之秀,往往會遭遇到誤殺的不公正待遇。差評。
LRU 的全稱是 Least Recently Used,也就是最近最少使用算法。基本思路是,如果一個數據最近一段時間被使用的頻率很少,那將來被用到的可能性也會很低。看到這兒,那些近期沒什么開發任務的童靴,你們要小心了。好評。
LRU2 和 ARC 都是基于 LRU 的改進,有興趣的可以上網查查。
很多我們耳熟能詳的緩存產品,比如 Memcached、Redis、Ehcache、OSCache 等,都參考了類似的算法,要么進行加強,要么進行簡化,目標就是提升緩存的命中率,降低緩存成本。
了解了這些夠不夠?當然不夠,你還需要進行大量的實踐和業務驗證,才能找到最合適你的系統的緩存策略。另外,當你的數據量、訪問量和可靠性要求 越來越高的時候,你還需要考慮分布式緩存,以提升緩存容量和擴展性。但是緩存分布了,又帶來更多的問題,比如單點失效,命中率,并發,數據同步等等。這樣 寫起來就木有完了是不是?所以給大家推薦兩篇文章:
一種基于「哨兵」的分布式緩存設計:http://blog.lichengwu.cn/architecture/2015/06/14/distributed-cache/
分布式緩存的一起問題:http://timyang.net/data/cache-failure/
本來只想做個普及,沒想到還是寫了這么多字,年齡大了就是羅嗦,如有錯誤,你知道就行了……