日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java实现本地缓存、分布式缓存及多级缓存

發布時間:2023/12/10 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java实现本地缓存、分布式缓存及多级缓存 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

以下均為自己參考其它博主文章或自己理解整理而成,如有錯誤之處,歡迎在評論區批評指正!

0. 緩存簡介

? ? ? ?像MySql等傳統的關系型數據庫已經不能適用于所有的業務場景,比如電商系統的秒殺場景,APP首頁的訪問流量高峰場景,很容易造成關系型數據庫的癱瘓,隨著緩存技術的出現很好的解決了這個問題。

0.1 什么是緩存?

? ? ? ?就是把訪問量較高的熱點數據從傳統的關系型數據庫中加載到內存中,當用戶再次訪問熱點數據時是從內存中加載,減少了對數據庫的訪問量,解決了高并發場景下容易造成數據庫宕機的問題。

0.2 為什么要用緩存?

? ? ? ?針對于這個問題要從兩個方面去考慮,一個是應用系統的高并發場景,另一個就是應用系統的高性能情況。

0.2.1 高性能情況

? ? ? ?用戶第一次訪問數據時,緩存中沒有數據,要從數據庫中獲取數據,因為是從磁盤中拿數據,讀取數據的過程比較慢。拿到數據后將數據存儲在緩存中,用戶第二次訪問數據時,可以從緩存中直接獲取,因為緩存是直接操作內存的,訪問數據速度比較快。

0.2.2 高并發場景

? ? ? ?操作緩存能夠承受的并發訪問量是遠遠大于訪問數據庫的,比如redis,它的讀的速度是110000次/s,寫的速度是81000次/s。所以說將數據庫中訪問量高的數據存儲到緩存中,用戶請求的時候直接訪問數據庫,不必訪問數據庫,提高應用程序的并發量。

0.3 緩存分類

緩存基本上分為三類:本地緩存、分布式緩存、多級緩存。

? ? ? ?根據緩存和應用程序是否屬于同一個進程,將緩存分為本地緩存和分布式緩存。基于本地緩存和分布式緩存都有各自的優點和缺點,后面又出現了多級緩存的概念。

0.3.1 本地緩存

本地緩存:是指和應用程序在同一個進程內的內存空間去存儲數據,數據的讀寫都是在同一個進程內完成的。

優點:讀取速度快,但是不能進行大數據量存儲。

? ? ? ?本地緩存不需要遠程網絡請求去操作內存空間,沒有額外的性能消耗,所以讀取速度快。但是由于本地緩存占用了應用進程的內存空間,比如java進程的jvm內存空間,故不能進行大數據量存儲。

缺點:

  • 應用程序集群部署時,會存在數據更新問題(數據更新不一致)

? ? ? ?本地緩存一般只能被同一個應用進程的程序訪問,不能被其他應用程序進程訪問。在單體應用集群部署時,如果數據庫有數據需要更新,就要同步更新不同服務器節點上的本地緩存的數據來保證數據的一致性,但是這種操作的復雜度高,容易出錯。可以基于redis的發布/訂閱機制來實現各個部署節點的數據同步更新。

  • 數據會隨著應用程序的重啟而丟失

? ? ? ?因為本地緩存的數據是存儲在應用進程的內存空間的,所以當應用進程重啟時,本地緩存的數據會丟失。

實現:

  • 緩存存儲的數據一般都是key-value鍵值對的數據結構,在java語言中,常用的字典實現包括 HashMap 和 ConcurretHashMap。

  • 除了上面說的實現方式以外,也可以用Guava、Ehcache以及Caffeine等封裝好的工具包來實現本地緩存。

0.3.2 分布式緩存

分布式緩存:分布式緩存是獨立部署的服務進程,并且和應用程序沒有部署在同一臺服務器上,所以是需要通過遠程網絡請求來完成分布式緩存的讀寫操作,并且分布式緩存主要應用在應用程序集群部署的環境下。

優點:

  • 支持大數據量存儲

? ? ? ?分布式緩存是獨立部署的進程,擁有自身獨自的內存空間,不需要占用應用程序進程的內存空間,并且還支持橫向擴展的集群方式部署,所以可以進行大數據量存儲。

  • 數據不會隨著應用程序重啟而丟失

? ? ? ?分布式緩存和本地緩存不同,擁有自身獨立的內存空間,不會受到應用程序進程重啟的影響,在應用程序重啟時,分布式緩存的存儲數據仍然存在。

  • 數據集中存儲,保證數據的一致性

? ? ? ?當應用程序采用集群方式部署時,集群的每個部署節點都有一個統一的分布式緩存進行數據的讀寫操作,所以不會存在像本地緩存中數據更新問題,保證了不同服務器節點的數據一致性。

  • 數據讀寫分離,高性能,高可用

? ? ? ?分布式緩存一般支持數據副本機制,實現讀寫分離,可以解決高并發場景中的數據讀寫性能問題。而且在多個緩存節點冗余存儲數據,提高了緩存數據的可用性,避免某個緩存節點宕機導致數據不可用問題。

缺點:

  • 數據跨網絡傳輸,讀寫性能不如本地緩存

? ? ? ?分布式緩存是一個獨立的服務進程,并且和應用程序進程不在同一臺機器上,所以數據的讀寫要通過遠程網絡請求,這樣相對于本地緩存的數據讀寫,性能要低一些。

分布式緩存的實現:典型實現包括 MemCached 和 Redis。

0.3.3 多級緩存

? ? ? ?基于本地緩存和分布式緩存的優缺點,多級緩存應運而生,在實際的業務開發中一般也是采用多級緩存。注意:本地緩存一般存儲更新頻率低,訪問頻率高數據,分布式緩存一般存儲更新頻率很高的數據。

? ? ? ?多級緩存的請求流程:本地緩存作為一級緩存,分布式緩存作為二級緩存。當用戶獲取數據時,先從一級緩存中獲取數據,如果一級緩存有數據則返回數據,否則從二級緩存中獲取數據。如果二級緩存中有數據則更新一級緩存,然后將數據返回客戶端。如果二級緩存沒有數據則去數據庫查詢數據,然后更新二級緩存,接著再更新一級緩存,最后將數據返回給客戶端。

多級緩存的實現:可以使用Guava或者Caffeine作為一級緩存,Redis作為二級緩存。

? ? ? ?注意:在應用程序集群部署時,如果數據庫的數據有更新的情況,一級緩存的數據更新容易出現數據不一致的情況。因為是集群部署,多個部署節點實現一級緩存數據更新難度比較大,不過我們可以通過Redis的消息發布/訂閱機制來實現多個節點緩存數據一致性問題。

1. 本地緩存詳細介紹及具體實現

1.1 介紹

參考鏈接:本地緩存:為什么要用本地緩存?用它會有什么問題?_Gimtom的博客-CSDN博客_本地緩存

? ? ? ?對于緩存的作用不言而喻,可以提高查詢效率,比去數據庫查詢的速度要快。項目中我們經常會使用Nosql數據庫,如Redis等做緩存。但是對于數據量很小的,訪問非常頻繁的,我們也可以存在本地緩存中。

? ? ? ?在高性能的服務架構設計中,緩存是一個不可或缺的環節。在實際的項目中,我們通常會將一些熱點數據存儲到Redis或Memcached 這類緩存中間件中,只有當緩存的訪問沒有命中時再查詢數據庫。在提升訪問速度的同時,也能降低數據庫的壓力。

? ? ? ?隨著不斷的發展,這一架構也產生了改進,在一些場景下可能單純使用Redis類的遠程緩存已經不夠了,還需要進一步配合本地緩存使用,例如Guava cache或Caffeine,從而再次提升程序的響應速度與服務性能。于是,就產生了使用本地緩存作為一級緩存,再加上遠程緩存作為二級緩存的兩級緩存架構。

? ? ? ?在先不考慮并發等復雜問題的情況下,兩級緩存的訪問流程可以用下面這張圖來表示:

本地緩存:在客戶端本地的物理內存中劃出一部分空間來緩存客戶端回寫到服務器的數據,當本地回寫緩存達到緩存閾值時,將數據寫入到服務器中。

特點:

  • 本地緩存是基于本地環境的內存,訪問速度非常快,對于一些變更頻率低、實時性要求低的數據,可以放在本地緩存中,提升訪問速度。

  • 使用本地緩存能夠減少和Redis類的遠程緩存間的數據交互,減少網絡I/O開銷,降低這一過程中在網絡通信上的耗時。

  • 本地回寫緩存功能可以在很大程度上降低服務器讀寫能力和網絡負載,一般將本地緩存作為一級緩存,遠程緩存作為二級緩存。

本地緩存應該具有的功能:

  • 超過最大限制有對應淘汰策略,如LRU、LFU

  • 過期時間淘汰,如定時、懶式、定期;

  • 持久化

  • 統計監控

  • 1.2 本地緩存方案選型

    1.2.1 使用ConcurrentHashMap實現本地緩存

    ? ? ? ?緩存的本質就是存儲在內存中的KV數據結構,對應的就是jdk中線程安全的ConcurrentHashMap,但是要實現緩存,還需要考慮淘汰、最大限制、緩存過期時間淘汰等等功能。

    優點:實現簡單,不需要引入第三方包,比較適合一些簡單的業務場景。缺點是如果需要更多的特性,需要定制化開發,成本會比較高,并且穩定性和可靠性也難以保障。對于比較復雜的場景,建議使用比較穩定的開源工具。

    1.2.2 基于Guava Cache實現本地緩存

    ? ? ? ?Guava是Google團隊開源的一款 Java 核心增強庫,包含集合、并發原語、緩存、IO、反射等工具箱,性能和穩定性上都有保障,應用十分廣泛。Guava Cache支持很多特性:

    • 支持最大容量限制

    • 支持兩種過期刪除策略(插入時間和訪問時間)

    • 支持簡單的統計功能

    • 基于LRU算法實現

    引入依賴如下:

    ? <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version></dependency>

    測試代碼:

    ?@Slf4jpublic class GuavaCacheTest {public static void main(String[] args) throws ExecutionException {Cache<String, String> cache = CacheBuilder.newBuilder().initialCapacity(5) // 初始容量.maximumSize(10) ? ? // 最大緩存數,超出淘汰.expireAfterWrite(60, TimeUnit.SECONDS) // 過期時間.build();?String orderId = String.valueOf(123456789);// 獲取orderInfo,如果key不存在,callable中調用getInfo方法返回數據String orderInfo = cache.get(orderId, () -> getInfo(orderId));log.info("orderInfo = {}", orderInfo);?}?private static String getInfo(String orderId) {String info = "";// 先查詢redis緩存log.info("get data from redis");?// 當redis緩存不存在查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}}

    1.2.3 基于Caffeine實現本地緩存

    ? ? ? ?Caffeine是基于java8實現的新一代緩存工具,緩存性能接近理論最優,可以看作是Guava Cache的增強版,功能上兩者類似,不同的是Caffeine采用了一種結合LRU、LFU優點的算法:W-TinyLFU,在性能上有明顯的優越性。

    引入依賴如下:

    ?<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.9.3</version></dependency>

    測試代碼如下:

    ?@Slf4jpublic class CaffeineTest {public static void main(String[] args) {Cache<String, String> cache = Caffeine.newBuilder().initialCapacity(5)// 超出時淘汰.maximumSize(10)//設置寫緩存后n秒鐘過期.expireAfterWrite(60, TimeUnit.SECONDS)//設置讀寫緩存后n秒鐘過期,實際很少用到,類似于expireAfterWrite//.expireAfterAccess(17, TimeUnit.SECONDS).build();?String orderId = String.valueOf(123456789);String orderInfo = cache.get(orderId, key -> getInfo(key));System.out.println(orderInfo);}?private static String getInfo(String orderId) {String info = "";// 先查詢redis緩存log.info("get data from redis");?// 當redis緩存不存在查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}}

    1.2.4 基于Encache實現本地緩存

    ? ? ? ?Encache是一個純Java的進程內緩存框架,具有快速、精干等特點,是Hibernate中默認的CacheProvider。同Caffeine和Guava Cache相比,Encache的功能更加豐富,擴展性更強。

    優點:

    • 支持多種緩存淘汰算法,包括LRU、LFU和FIFO

    • 緩存支持堆內存儲、堆外存儲、磁盤存儲(支持持久化)三種

    • 支持多種集群方案,解決數據共享問題

    引入依賴如下:

    ? <dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.9.7</version></dependency>

    測試代碼如下:

    ?@Slf4jpublic class EhcacheTest {private static final String ORDER_CACHE = "orderCache";public static void main(String[] args) {CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()// 創建cache實例.withCache(ORDER_CACHE, CacheConfigurationBuilder// 聲明一個容量為20的堆內緩存.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20))).build(true);// 獲取cache實例Cache<String, String> cache = cacheManager.getCache(ORDER_CACHE, String.class, String.class);?String orderId = String.valueOf(123456789);String orderInfo = cache.get(orderId);if (StrUtil.isBlank(orderInfo)) {orderInfo = getInfo(orderId);cache.put(orderId, orderInfo);}log.info("orderInfo = {}", orderInfo);}?private static String getInfo(String orderId) {String info = "";// 先查詢redis緩存log.info("get data from redis");?// 當redis緩存不存在查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}}

    1.2.5 選型方案對比

    • 從易用性角度,Guava Cache、Caffeine和Encache都有十分成熟的接入方案,使用簡單。

    • 從功能性角度,Guava Cache和Caffeine功能類似,都是只支持堆內緩存,Encache相比功能更為豐富。

    • 從性能上進行比較,Caffeine最優、GuavaCache次之,Encache最差(下圖是三者的性能對比結果)。

    ? ? ? ?對于本地緩存的方案中,我比較推薦Caffeine,性能上遙遙領先。雖然Encache功能更為豐富,甚至提供了持久化和集群的功能,但是這些功能完全可以依靠其他方式實現。真實的業務工程中,建議使用Caffeine作為本地緩存,另外使用redis或者memcache作為分布式緩存,構造多級緩存體系,保證性能和可靠性。

    1.2 本地緩存問題及解決

    1.2.1 緩存一致性

    ? ? ? ?兩級緩存與數據庫的數據要保持一致,一旦數據發生了修改,在修改數據庫的同時,本地緩存、遠程緩存應該同步更新。

    1.2.1.1 解決方案1: MQ

    ? ? ? ?一般現在部署都是集群部署,有多個不同節點的本地緩存,可以使用MQ的廣播模式,當數據修改時向MQ發送消息,節點監聽并消費消息,刪除本地緩存,達到最終一致性。

    1.2.1.2 解決方案2:Canal + MQ

    ? ? ? ?如果你不想在你的業務代碼發送MQ消息,還可以適用近幾年比較流行的方法:訂閱數據庫變更日志,再操作緩存。Canal 訂閱Mysql的 Binlog日志,當發生變化時向MQ發送消息,進而也實現數據一致性。

    1.2.2 如何提供本地緩存命中率

    待完善。。。。。。

    1.3 本地緩存詳細代碼實現過程

    1.3.1 基于ConcurrentHashMap實現的本地緩存

    參考鏈接:Java實現本地緩存_柳落青的博客-CSDN博客_java 本地緩存

    ? ? ? ?本地緩存一般使用鍵值對方式的存儲,那么在Java中肯定是選用Map,由于concurrentHashMap的線程安全性,所以就選擇了這個。過期策略采用的定時清除,實現方式可以后臺啟動一個線程去掃,也可以用定時器,本例子使用的是定時器。

    ?package localcache;?import java.util.Map;import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.ConcurrentHashMap;?/*** 基于ConcurrentHashMap定義的本地緩存工具類*/public class ConcurrentHashMapLocalCacheUtil {// 默認大小private static final int DEFAULT_CAPACITY = 1024;?// 最大緩存大小private static final int MAX_CAPACITY = 10000;?// 默認緩存過期時間private static final long DEFAULT_TIMEOUT = 3600;?// 1000毫秒private static final long SECOND_TIME = 1000;?// 存儲緩存的Mapprivate static final ConcurrentHashMap<String, Object> map;?// 采用定時器進行定時private static final Timer timer;?static {// 創建指定容量為DEFAULT_CAPACITY大小的ConcurrentHashMapmap = new ConcurrentHashMap<>(DEFAULT_CAPACITY);// 新建定時器timer = new Timer();}?// 私有化構造方法private ConcurrentHashMapLocalCacheUtil() {?}?/*** 利用定時器任務類實現的緩存任務清除類*/static class ClearTask extends TimerTask {private String key;?public ClearTask(String key) {this.key = key;}?@Overridepublic void run() {ConcurrentHashMapLocalCacheUtil.remove(key);}}?//==================緩存的增刪改查========================/*** 判斷容量大小*/public static boolean checkCapacity() {return map.size() < MAX_CAPACITY;}?/*** 添加緩存*/public static boolean put(String key, Object object) {// 添加前先檢查緩存大小if (checkCapacity()) {// 有剩余空間時存入數據map.put(key, object);// 通過定時器指定任務的默認超時時間timer.schedule(new ClearTask(key), DEFAULT_TIMEOUT);return true;}return false;}?/*** 可指定延時時間的添加緩存*/public static boolean put(String key, Object object, int time_out) {// 添加前檢查緩存大小if (checkCapacity()) {// 有剩余空間時存入數據map.put(key, object);// 通過定時器指定任務的超時時間timer.schedule(new ClearTask(key), time_out * SECOND_TIME);return true;}return false;}?/*** 批量增加緩存*/public static boolean put(Map<String, Object> batchMap, int time_out) {// 添加前檢查緩存大小if (map.size() + batchMap.size() <= MAX_CAPACITY) {// 有剩余空間時存入數據map.putAll(map);// 為每個緩存添加任務及超時時間for (String key : batchMap.keySet()) {timer.schedule(new ClearTask(key), time_out * SECOND_TIME);}return true;}return false;}?/*** 刪除緩存*/public static void remove(String key) {map.remove(key);}?/*** 清除所有緩存*/public void clearAll() {// 判斷緩存中是否還有數據if (map.size() > 0) {// 清空mapmap.clear();}// 終止定時器timer.cancel();}?/*** 獲取緩存*/public static Object get(String key) {return map.get(key);}?/*** 判斷是否包含某個緩存*/public static boolean isContain(String key) {return map.contains(key);}?}?

    或者

    參考鏈接:Java簡單實現本地緩存-pudn.com

    ?package localcache;?import java.util.Map;import java.util.concurrent.ConcurrentHashMap;?/*** @ClassName SimpleCache* @Description Java簡單實現本地緩存,注意:此代碼不適合集群部署環境* @Author Jiangnan Cui* @Date 2022/9/25 20:16* @Version 1.0*/public class SimpleCache {?/*** 有效時間30分鐘:30 * 60 * 1000*/private static final long CACHE_HOLD_TIME_30M = 1800000L;?/*** 有效時間key后綴*/private static final String SECONDS = "_seconds";?private static volatile SimpleCache cache;?private static Map<String, Object> CACHE_MAP;?private SimpleCache() {CACHE_MAP = new ConcurrentHashMap<>();}?public static SimpleCache getInstance() {if (cache == null) {synchronized (SimpleCache.class) {if (cache == null) {cache = new SimpleCache();}}}return cache;}?/*** 存放一個緩存對象,默認保存時間30分鐘** @param cacheName 緩存名稱* @param obj ? ? ? 緩存對象*/public void put(String cacheName, Object obj) {put(cacheName, obj, CACHE_HOLD_TIME_30M);}?/*** 存放一個緩存對象,保存時間為holdTime** @param cacheName 緩存名稱* @param obj ? ? ? 緩存對象* @param seconds ? 時間*/public void put(String cacheName, Object obj, long seconds) {CACHE_MAP.put(cacheName, obj);// 設置緩存失效時間CACHE_MAP.put(cacheName + SECONDS,System.currentTimeMillis() + seconds);}?/*** 取出一個緩存對象** @param cacheName 緩存名稱* @return 緩存對象*/public Object get(String cacheName) {if (checkCacheName(cacheName)) {return CACHE_MAP.get(cacheName);}return null;}?/*** 刪除某個緩存** @param cacheName 緩存名稱*/public void remove(String cacheName) {CACHE_MAP.remove(cacheName);CACHE_MAP.remove(cacheName + SECONDS);}?/*** 檢查緩存對象是否存在,* 若不存在,則返回false* 若存在,檢查其是否已過有效期,如果已經過了則刪除該緩存并返回false** @param cacheName 緩存名稱* @return 緩存對象是否存在*/public boolean checkCacheName(String cacheName) {Long seconds = (Long) CACHE_MAP.get(cacheName + SECONDS);if (seconds == null || seconds == 0L) {return false;}if (seconds < System.currentTimeMillis()) {remove(cacheName);return false;}return true;}}?

    注意:以上代碼還未經過正式測試。

    1.3.2 基于Guava Cache實現的本地緩存

    待完善。。。。。。

    1.3.3 基于Caffeine實現的本地緩存

    待完善。。。。。。

    1.3.4 基于Encache實現的本地緩存

    待完善。。。。。。

    2. 分布式緩存詳細介紹及具體實現

    待完善。。。。。。

    3.多級緩存詳細介紹及具體實現

    待完善。。。。。。

    總結

    以上是生活随笔為你收集整理的Java实现本地缓存、分布式缓存及多级缓存的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。