快取 | 從本地快取到分散式快取
阿新 • • 發佈:2020-10-10
# 從本地快取到分散式快取
---
[TOC]
---
> 本文件中部分程式碼不保證可以執行
雖然標題為快取,但在這裡不僅僅會涉及快取,還會涉及一些其他提高應用效能的方案。
在程式設計中,經常能聽到的就是`以時間換空間`和`以空間換時間`。`快取`作為一種能加快程式效能的銀彈,它是典型的後者(`以空間換時間`).
隨著使用者數和訪問量越來越大,我們的應用需要支撐更多的併發量,同時我們的應用伺服器和資料庫伺服器所做的計算也越來越多。但是往往我們的應用伺服器資源是有限的,資料庫每秒能接受的請求次數也是有限的(或者檔案的讀寫也是有限的),如何能夠有效利用有限的資源來提供儘可能大的吞吐量?一個有效的辦法就是引入快取,每個環節中請求可以從快取中直接獲取目標資料並返回,從而減少計算量,有效提升響應速度,讓有限的資源服務更多的使用者。
> 快取並不是包治百病的銀彈
# 第一次接觸快取`MAP`
我第一次接觸快取的時候是在大三開始出去工作的時候。在一個系統中,基本每個介面都有可能要獲取一次使用者資訊和一些使用者配置,當時我們的系統查多改少,這也註定快取可以大大提高我們的效能,當時的做法是維護一個全域性的單例的`Map`作為快取儲存.記得當時的類名叫`DBMirror`
**大致如下:**
```java
class DBMirror {
private static Map 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)
最近使用策略,根據元素最後一次被使用的時間戳,清除最遠使用時間戳的元素釋放空間。
## 其他
1. 為快取元素設定過期時間,清理超過過時時間的元素
1. 隨機清理
1. 優先清理大物件
# 快取簡單分類
**本地快取**:指的是在應用中的快取元件,其最大的優點是應用和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,提供了靈活的開箱即用的解決方案.
# 注意事項
在使用快取的過程中,我們還要注意快取不一致、快取穿透、快取擊穿與快取雪崩等問題,每種問題都是不小的問題
這篇寫的並不長,每種都是簡單介紹了一下,馬上分幾篇分別介紹一下各自的具體使用方法,敬請期待
# 參考
1. spring cache
2. https://github.com/google/guava/wiki/CachesExplained
3. https://github.com/ben-manes/caffeine
4. https://www.memcached.org/
5. https://redis.io/
> 如果覺得還都湊合,記得 點贊、分享哦
![白色兔子公眾號圖片](https://img2020.cnblogs.com/blog/1246875/202008/1246875-20200822203040972-11913124