從本地緩存到分布式緩存
本文檔中部分代碼不保證可以運行
雖然標題為緩存,但在這里不僅僅會涉及緩存,還會涉及一些其他提高應用性能的方案。
在程序設計中,經常能聽到的就是以時間換空間
和以空間換時間
。緩存
作為一種能加快程序性能的銀彈,它是典型的后者(以空間換時間
).
隨着用戶數和訪問量越來越大,我們的應用需要支撐更多的並發量,同時我們的應用服務器和數據庫服務器所做的計算也越來越多。但是往往我們的應用服務器資源是有限的,數據庫每秒能接受的請求次數也是有限的(或者文件的讀寫也是有限的),如何能夠有效利用有限的資源來提供盡可能大的吞吐量?一個有效的辦法就是引入緩存,每個環節中請求可以從緩存中直接獲取目標數據並返回,從而減少計算量,有效提升響應速度,讓有限的資源服務更多的用戶。
緩存並不是包治百病的銀彈
第一次接觸緩存MAP
我第一次接觸緩存的時候是在大三開始出去工作的時候。在一個系統中,基本每個接口都有可能要獲取一次用戶信息和一些用戶配置,當時我們的系統查多改少,這也注定緩存可以大大提高我們的性能,當時的做法是維護一個全局的單例的Map
作為緩存存儲.記得當時的類名叫DBMirror
大致如下:
class DBMirror {
private static Map<String, User> userCache = new HashMap<>();
public static void putUser(String key, User user) {
userCache.put(key, user);
}
public static User getUser(String key) {
return userCache.get(key);
}
private DBMirror() {}
}
代碼很簡單,基本滿足了當時系統的要求,減少了很多數據庫讀寫操作,在當時也是第一次開始意識到 數據庫
並不是唯一的存儲. 原來 Map
還能這樣使用
但是上面的代碼有個很大的缺點,隨着用戶的增多,里面並沒有合適的剔除算法,會導致 Map
越來越大,極端情況會導致內存溢出
常見淘汰策略
如上所述,如果不使用剔除算法,會導致內存占用越來越大,且無法回收,那下面講一下常見的淘汰策略
FIFO(first in first out)
先進先出策略,最先進入緩存的數據在緩存空間不夠的情況下(超出最大元素限制)會被優先被清除掉,以騰出新的空間接受新的數據。
LFU(less frequently used)
最少使用策略,根據元素的被使用次數判斷,清除使用次數較少的元素釋放空間。
LRU(least recently used)
最近使用策略,根據元素最后一次被使用的時間戳,清除最遠使用時間戳的元素釋放空間。
其他
- 為緩存元素設置過期時間,清理超過過時時間的元素
- 隨機清理
- 優先清理大對象
緩存簡單分類
本地緩存:指的是在應用中的緩存組件,其最大的優點是應用和cache是在同一個進程內部,請求緩存非常快速,沒有過多的網絡開銷等,在單應用不需要集群支持或者集群情況下各節點無需互相通知的場景下使用本地緩存較合適;同時,它的缺點也是應為緩存跟應用程序耦合,多個應用程序無法直接的共享緩存,各應用或集群的各節點都需要維護自己的單獨緩存,對內存是一種浪費。
分布式緩存:指的是與應用分離的緩存組件或服務,其最大的優點是自身就是一個獨立的應用,與本地應用隔離,多個應用可直接的共享緩存。缺點是:優點也就是缺點,因為自身是一個獨立的應用,本地節點都需要與其進行通信,導致依賴網絡,同時如果緩存服務崩潰可能會影響所有依賴節點
對於一些單個實例的服務,或者數據基本不會變化的數據都可以使用本地緩存來提高性能,反之可以使用分布式緩存
技術方案本身沒有最好的,只有最合適的.
緩存的使用
Java集合類
在上面提供了一個簡單的例子,DBMirror
使用Map
來時間一個簡單的內存緩存,同時Set
、List
都可以達到內存緩存的功能,根據並發情況可以選擇不同的實現類,例如HashMap
、LinkedHashMap
、TreeMap
、LinkedTreeMap
、ConcurrentHashMap
... 總有一個滿足你
這樣實現很簡單,但是也致命缺點:無法回收不常用的緩存
Guava Cache
說起 Guava, 很多人都不會陌生,它是 Google 提供的一個非常好用的 Java 工具包。Guava Cache 是 Guava 中的一個本地緩存實現,基於LRU算法實現,並提供了多種緩存過期策略,過期時間、容量等. 簡化了緩存的使用,方便我們更加大膽的使用緩存
Caffeine
Caffeine是一個基於 Java8 開發的提供了近乎最佳命中率的高性能的緩存庫。
在本地緩存方面,SpringFramework5.0(SpringBoot2.0)放棄了Google的GuavaCache,選擇了「Caffeine」(Drop Guava caching - superseded by Caffeine [SPR-13797] #18370)。足以見證其在性能和可靠性上的優勢.
其性能測試可以查看 https://github.com/ben-manes/caffeine/wiki/Benchmarks
Ehcache
Ehcache是純Java開源緩存框架,配置簡單、結構清晰、功能強大,是一個非常輕量級的緩存實現,我們常用的Hibernate里面就集成了相關緩存功能。
在早期開發的時候也用過這個,現在不知道是否還在使用
Memcached
一個高性能的、分布式的基於內存的key-value對象存儲系統,用來存儲小塊的任意數據(字符串、對象)
通過訪問其來較少數據庫的讀寫壓力
Redis
Redis 同樣是一個高性能的基於內存中數據結構存儲,用作數據庫,緩存和消息代理。
它支持更多的數據結構,例如 strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes。
Redis具有內置的復制,Lua腳本,LRU逐出,事務和不同級別的磁盤持久性,並通過Redis Sentinel和Redis Cluster自動分區提供高可用性
Spring Cache
Spring Cache 並不是緩存的實現,而是一個緩存管理的抽象解決方案,這種方案消除了樣板方法的使用,屏蔽了緩存的使用細節,而這是 Spring 最擅長干的.
Spring 的緩存技術還具備相當的靈活性,可以使用 SpEL 來定義緩存的 key 和各種 condition,提供了靈活的開箱即用的解決方案.
注意事項
在使用緩存的過程中,我們還要注意緩存不一致、緩存穿透、緩存擊穿與緩存雪崩等問題,每種問題都是不小的問題
這篇寫的並不長,每種都是簡單介紹了一下,馬上分幾篇分別介紹一下各自的具體使用方法,敬請期待
參考
- spring cache
- https://github.com/google/guava/wiki/CachesExplained
- https://github.com/ben-manes/caffeine
- https://www.memcached.org/
- https://redis.io/
如果覺得還都湊合,記得 點贊、分享哦