1. 程式人生 > >WEB 應用快取解析以及使用 Redis 實現分散式快取

WEB 應用快取解析以及使用 Redis 實現分散式快取


## 什麼是快取? 快取就是資料交換的緩衝區,用於臨時儲存資料(使用頻繁的資料)。當用戶請求資料時,首先在快取中尋找,如果找到了則直接返回。如果找不到,則去資料庫中查詢。快取的本質就是用空間換時間,犧牲資料的實時性,從而減輕資料庫壓力,儘可能提高吞吐量,有效提升響應速度。
## 快取的分類 快取的應用範圍十分廣泛,常見的有檔案快取、瀏覽器快取、資料庫快取等等。但今天我們著重關注的是 WEB 應用服務領域,根據快取與應用的耦合度,可以分為本地快取和分散式快取: - 本地快取 指在應用中的快取元件,最大的優點是應用和快取是在同一個程序內部,請求快取速度快;同時,它的缺點也是因為快取跟應用程式耦合,多個應用程式無法直接共享快取,各應用或叢集的各節點都需要維護自己的單獨快取 - 分散式快取 指的是與應用分離的快取元件或服務,最大的優點是自身就是一個獨立的應用,與本地應用隔離,多個應用可直接共享快取
## 快取的特點 快取也是一個數據模型物件,那麼必然有它的一些特徵: - 命中率 命中率 = 返回正確結果數 / 請求快取次數,命中率問題是快取中的一個非常重要的問題,它是衡量快取有效性的重要指標。命中率越高,表明快取的使用率越高。 - 最大元素 快取中可以存放的最大元素的數量,一旦快取中元素數量超過這個值,將會觸發快取清空策略。根據不同的場景合理設定最大元素值,可以在一定程度上提高快取的命中率,從而更有效的利用快取。
## 快取清空策略 快取的儲存空間有限制,當快取空間被用滿時,就需要快取清空策略來處理。常見的一般策略有: - 先進先出策略 先進入快取的資料,在快取空間不足時會被優先被清理掉,以騰出新的空間接受新的資料。先進先出策略主要比較快取元素的建立時間,在資料實效性要求較高的場景下可選擇該類策略,優先保障最新資料可用 - 最少使用策略 無論是否過期,根據元素被使用的次數判斷,清除使用次數較少的元素。最少使用策略主要比較元素的命中次數,在保證高頻資料有效性場景下,可選擇該類策略 - 最近最少使用策略 無論是否過期,根據元素最後一次被使用的時間戳,清除最遠使用時間戳的元素。策略演算法主要比較元素最近一次被使用的時間。適用於熱點資料場景,優先保證熱點資料的有效性 此外,還有一些簡單策略,比如: - 根據過期時間判斷,清理過期時間最長的元素 - 根據過期時間判斷,清理最近要過期的元素 - 隨機清理 - 根據關鍵字(或元素內容)清理等等
## Redis 實現分散式快取 可以利用 Mybatis 自帶的本地快取,結合 Redis 實現分散式快取。主要思路是將 Mybatis 二級快取的存放地點從本地改為配置了 Redis 的遠端伺服器。 第一步,建立一個 SpringBoot 工程,整合 MyBatis 和 Redis,在 Mapper 檔案中加入 `` 標籤開啟二級快取。 `` 標籤預設採用 PrepetualCache,該類是 Cache 介面的實現類,維護一個 Map 來儲存資料。我們要作改造,就要自定義一個實現類並替換 `` ![](https://img2020.cnblogs.com/blog/1759254/202009/1759254-20200922182419388-1867938814.png) 實現自定義 RedisCache ```java public class RedisCache implements Cache { // 當前放入快取的 mapper 的 namespace,也是快取的唯一標識 private final String id; public RedisCache(String id) { System.out.println("id:" + id); this.id = id; } /** * 返回 cache 的唯一標識 */ @Override public String getId() { return this.id; } /** * 快取放入值 */ @Override public void putObject(Object key, Object value) { System.out.println("放入快取"); // 通過工具類獲取 redisTemplate RedisTemplate redisTemplate = getRedisTemplate(); // 使用 redishash 型別作為快取儲存模型 redisTemplate.opsForHash().put(id.toString(), key.toString(), value); } /** * 獲取快取中的值 */ @Override public Object getObject(Object key) { System.out.println("獲得快取"); // 通過工具類獲取 redisTemplate RedisTemplate redisTemplate = getRedisTemplate(); // 根據 key 從 redis 的 hash 型別中獲取資料 return redisTemplate.opsForHash().get(id.toString(), key.toString()); } /** * 根據指定的 key 刪除快取 * 該方法為 mybatis 保留方法,預設沒有實現 */ @Override public Object removeObject(Object key) { System.out.println("根據指定的 key 刪除快取"); return null; } @Override public void clear() { System.out.println("清空快取"); // 通過工具類獲取 redisTemplate RedisTemplate redisTemplate = getRedisTemplate(); // 清空 namespace redisTemplate.delete(id.toString()); } /** * 計算快取數量 */ @Override public int getSize() { RedisTemplate redisTemplate = getRedisTemplate(); return redisTemplate.opsForHash().size(id.toString()).intValue(); } /** * 獲取 redisTemplate */ private RedisTemplate getRedisTemplate() { RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate; } } ``` 到此為止,使用 Redis 實現分散式快取的目標就完成了。還有一點要注意的是,涉及到多表查詢時,結果會包含另一個表的物件資訊。由於 Mybatis 二級快取清理只會清理自身 namespace 的快取,所以被包含的物件資訊不會被清理。如果此時表資訊發生改變,將導致資料不一致。解決辦法是每個 namespace 都使用同一個快取 ```java
```
## 快取穿透(擊穿) 客戶端查詢了一個數據庫中沒有的資料,導致快取在這種情況下無法利用(資料庫都沒有則快取更不可能有了)。此情況下可繞過快取直接攻擊資料庫。 對於這種惡意訪問,一種思路是先做校驗,對惡意資料直接過濾掉,不要傳送至資料庫層;第二種思路是快取空結果,就是對查詢不存在的資料也記錄在快取中,這樣就可以有效的減少查詢資料庫的次數。MyBatis 正是使用了第二種方式。
## 快取雪崩 在系統執行的某一時刻,快取全部失效,恰好這一時刻湧來大量客戶端請求,導致資料庫阻塞或掛起。導致快取失效的原因有很多,常見的是快取到了失效時間(所有的快取設定了同樣的過期時間),而沒有作合適的處理。 要解決這個問題,一種方式是設定快取永久儲存(不推薦),另一種方式是針對不同業務資料設定不同的超時時間,防止集體失效