一、緩存的概念
在服務端編程當中,緩存主要是指將數據庫的數據加載到內存中,之后對該數據的訪問都在內存中完成,從而減少了對數據庫的訪問,解決了高并發場景中數據庫容易成為性能瓶頸的問題;以及基于內存的訪問速度高于磁盤的訪問速度的原理(數據庫讀取數據一般需要從磁盤讀取),提高了數據的訪問速度和程序性能。
根據緩存是否與應用進程屬于同一進程,可以將內存分為本地緩存和分布式緩存。本地緩存是在同一個進程內的內存空間中緩存數據,數據讀寫都是在同一個進程內完成;而分布式緩存是一個獨立部署的進程并且一般都是與應用進程部署在不同的機器,故需要通過網絡來完成分布式緩存數據讀寫操作的數據傳輸。
二、本地緩存
本地緩存的優缺點
-
訪問速度快,但無法進行大數據存儲
本地緩存相對于分布式緩存的好處是,由于數據不需要跨網絡傳輸,故性能更好,但是由于占用了應用進程的內存空間,如 Java 進程的 JVM 內存空間,故不能進行大數據量的數據存儲。 -
集群的數據更新問題
與此同時,本地緩存只支持被該應用進程訪問,一般無法被其他應用進程訪問,故在應用進程的集群部署當中,如果對應的數據庫數據,存在數據更新,則需要同步更新不同部署節點的本地緩存的數據來包保證數據一致性,復雜度較高并且容易出錯,如基于 Redis 的發布訂閱機制來同步更新各個部署節點。 -
數據隨應用進程的重啟而丟失
由于本地緩存的數據是存儲在應用進程的內存空間的,所以當應用進程重啟時,本地緩存的數據會丟失。所以對于需要持久化的數據,需要注意及時保存,否則可能會造成數據丟失。
適用場景
所以本地緩存一般適合于緩存只讀數據,如統計類數據。或者每個部署節點獨立的數據,如長連接服務中,每個部署節點由于都是維護了不同的連接,每個連接的數據都是獨立的,并且隨著連接的斷開而刪除。如果數據在集群的不同部署節點需要共享和保持一致,則需要使用分布式緩存來統一存儲,實現應用集群的所有應用進程都在該統一的分布式緩存中進行數據存取即可。
本地緩存的實現
緩存一般是一種key-value的鍵值對數據結構,所以需要使用字典數據結構來實現,在 Java 編程中,常用的字典實現包括 HashMap 和 ConcurretHashMap。
與此同時,本地緩存由于需要被不同的服務端線程并發讀寫,故需要保證線程安全。由于 HashMap 不是線程安全的,而 ConcurrentHashMap 是線程安全的,故一般會使用 ConcurrentHashMap 來作為 Java 編程中的本地緩存實現。除此之外,也有其他更加智能的本地緩存實現,如可以定時失效,訪問重新加載等特性,典型實現包括 Google 的 guava 工具包的 Cache 實現,這些也是線程安全的。
三、分布式緩存
分布式緩存的優缺點
-
支持大數據量存儲,不受應用進程重啟影響
分布式緩存由于是獨立部署的進程,擁有自身獨立的內存空間,不會受到應用進程重啟的影響,在應用進程重啟時,分布式緩存的數據依然存在。同時對于數據量而言,由于不需要占用應用進程的內存空間,并且一般支持以集群的方式拓展,故可以進行大數據量的數據緩存。 -
數據集中存儲,保證數據一致性
當應用進程采用集群方式部署時,集群的每個部署節點都通過一個統一的分布式緩存進行數據存取操作,故不存在本地緩存中的數據更新問題,保證了不同節點的應用進程的數據一致性問題。 -
數據讀寫分離,高性能,高可用
分布式緩存一般支持數據副本機制,可以實現讀寫分離,故可以解決高并發場景中的數據讀寫性能問題。并且由于在多個緩存節點冗余存儲數據,提高了緩存數據的可用性,避免某個緩存節點宕機導致數據不可用問題。 -
數據跨網絡傳輸,性能低于本地緩存
由于分布式緩存是獨立部署的進程,并且一般都是與應用進程位于不同的機器,故需要通過網絡來進行數據傳輸,這樣相對于本地緩存的進程內部的數據讀取操作,性能會較低。
分布式緩存的實現
分布式緩存的典型實現包括 MemCached 和 Redis。
- MemCached
MemCached 相對于本地緩存的主要差別是以獨立進程方式存在,數據集中存儲,數據不隨應用程序的重啟而丟失。而 key-value 鍵值對的 value 也是一個簡單的對象類型,即 value 可以是任意格式的數據,如簡單的數字、字符串、對象等,也可以是文件、圖像、視頻等復雜格式的數據,但是不支持數據結構的特性。
所以 MemCached 進程相當于是在內存維護了一個非常大的哈希表來存儲數據,對應的數據操作復雜度都是 O(1),即常量級別,這也是 MemCached 高性能的一個實現方式,鍵值對存取速度都非常快。 - Redis
Redis 是在此基礎上,更一步豐富了key-value 鍵值對的 value 的數據結構類型,即可以在 Redis 中完成 value 的相關數據操作,如 Set 集合去重、有序集合 ZSet 實現數據排序等,這樣就不需要在應用程序額外進行這些操作,實現了開箱即用。并且 Redis 是單線程的,不存在并發數據讀寫的線程安全問題,以及更重要的是保證的數據讀寫操作的順序性。
除此之外,Redis 支持主從同步(讀寫分離)、集群分片拓展、數據持久化等特性,這也是 MemCached 不支持的。所以在高并發場景并且數據能夠容忍極端情況下的少量丟失,或者說丟失后可以恢復,如通過日志或者重新計算等, Redis 也可以作為數據庫來使用,提高高并發場景中的訪問性能。