Java实现本地缓存、分布式缓存及多级缓存
以下均為自己參考其它博主文章或自己理解整理而成,如有錯誤之處,歡迎在評論區(qū)批評指正!
0. 緩存簡介
? ? ? ?像MySql等傳統(tǒng)的關(guān)系型數(shù)據(jù)庫已經(jīng)不能適用于所有的業(yè)務(wù)場景,比如電商系統(tǒng)的秒殺場景,APP首頁的訪問流量高峰場景,很容易造成關(guān)系型數(shù)據(jù)庫的癱瘓,隨著緩存技術(shù)的出現(xiàn)很好的解決了這個問題。
0.1 什么是緩存?
? ? ? ?就是把訪問量較高的熱點(diǎn)數(shù)據(jù)從傳統(tǒng)的關(guān)系型數(shù)據(jù)庫中加載到內(nèi)存中,當(dāng)用戶再次訪問熱點(diǎn)數(shù)據(jù)時是從內(nèi)存中加載,減少了對數(shù)據(jù)庫的訪問量,解決了高并發(fā)場景下容易造成數(shù)據(jù)庫宕機(jī)的問題。
0.2 為什么要用緩存?
? ? ? ?針對于這個問題要從兩個方面去考慮,一個是應(yīng)用系統(tǒng)的高并發(fā)場景,另一個就是應(yīng)用系統(tǒng)的高性能情況。
0.2.1 高性能情況
? ? ? ?用戶第一次訪問數(shù)據(jù)時,緩存中沒有數(shù)據(jù),要從數(shù)據(jù)庫中獲取數(shù)據(jù),因?yàn)槭菑拇疟P中拿數(shù)據(jù),讀取數(shù)據(jù)的過程比較慢。拿到數(shù)據(jù)后將數(shù)據(jù)存儲在緩存中,用戶第二次訪問數(shù)據(jù)時,可以從緩存中直接獲取,因?yàn)榫彺媸侵苯硬僮鲀?nèi)存的,訪問數(shù)據(jù)速度比較快。
0.2.2 高并發(fā)場景
? ? ? ?操作緩存能夠承受的并發(fā)訪問量是遠(yuǎn)遠(yuǎn)大于訪問數(shù)據(jù)庫的,比如redis,它的讀的速度是110000次/s,寫的速度是81000次/s。所以說將數(shù)據(jù)庫中訪問量高的數(shù)據(jù)存儲到緩存中,用戶請求的時候直接訪問數(shù)據(jù)庫,不必訪問數(shù)據(jù)庫,提高應(yīng)用程序的并發(fā)量。
0.3 緩存分類
緩存基本上分為三類:本地緩存、分布式緩存、多級緩存。
? ? ? ?根據(jù)緩存和應(yīng)用程序是否屬于同一個進(jìn)程,將緩存分為本地緩存和分布式緩存。基于本地緩存和分布式緩存都有各自的優(yōu)點(diǎn)和缺點(diǎn),后面又出現(xiàn)了多級緩存的概念。
0.3.1 本地緩存
本地緩存:是指和應(yīng)用程序在同一個進(jìn)程內(nèi)的內(nèi)存空間去存儲數(shù)據(jù),數(shù)據(jù)的讀寫都是在同一個進(jìn)程內(nèi)完成的。
優(yōu)點(diǎn):讀取速度快,但是不能進(jìn)行大數(shù)據(jù)量存儲。
? ? ? ?本地緩存不需要遠(yuǎn)程網(wǎng)絡(luò)請求去操作內(nèi)存空間,沒有額外的性能消耗,所以讀取速度快。但是由于本地緩存占用了應(yīng)用進(jìn)程的內(nèi)存空間,比如java進(jìn)程的jvm內(nèi)存空間,故不能進(jìn)行大數(shù)據(jù)量存儲。
缺點(diǎn):
-
應(yīng)用程序集群部署時,會存在數(shù)據(jù)更新問題(數(shù)據(jù)更新不一致)
? ? ? ?本地緩存一般只能被同一個應(yīng)用進(jìn)程的程序訪問,不能被其他應(yīng)用程序進(jìn)程訪問。在單體應(yīng)用集群部署時,如果數(shù)據(jù)庫有數(shù)據(jù)需要更新,就要同步更新不同服務(wù)器節(jié)點(diǎn)上的本地緩存的數(shù)據(jù)來保證數(shù)據(jù)的一致性,但是這種操作的復(fù)雜度高,容易出錯。可以基于redis的發(fā)布/訂閱機(jī)制來實(shí)現(xiàn)各個部署節(jié)點(diǎn)的數(shù)據(jù)同步更新。
-
數(shù)據(jù)會隨著應(yīng)用程序的重啟而丟失
? ? ? ?因?yàn)楸镜鼐彺娴臄?shù)據(jù)是存儲在應(yīng)用進(jìn)程的內(nèi)存空間的,所以當(dāng)應(yīng)用進(jìn)程重啟時,本地緩存的數(shù)據(jù)會丟失。
實(shí)現(xiàn):
-
緩存存儲的數(shù)據(jù)一般都是key-value鍵值對的數(shù)據(jù)結(jié)構(gòu),在java語言中,常用的字典實(shí)現(xiàn)包括 HashMap 和 ConcurretHashMap。
-
除了上面說的實(shí)現(xiàn)方式以外,也可以用Guava、Ehcache以及Caffeine等封裝好的工具包來實(shí)現(xiàn)本地緩存。
0.3.2 分布式緩存
分布式緩存:分布式緩存是獨(dú)立部署的服務(wù)進(jìn)程,并且和應(yīng)用程序沒有部署在同一臺服務(wù)器上,所以是需要通過遠(yuǎn)程網(wǎng)絡(luò)請求來完成分布式緩存的讀寫操作,并且分布式緩存主要應(yīng)用在應(yīng)用程序集群部署的環(huán)境下。
優(yōu)點(diǎn):
-
支持大數(shù)據(jù)量存儲
? ? ? ?分布式緩存是獨(dú)立部署的進(jìn)程,擁有自身獨(dú)自的內(nèi)存空間,不需要占用應(yīng)用程序進(jìn)程的內(nèi)存空間,并且還支持橫向擴(kuò)展的集群方式部署,所以可以進(jìn)行大數(shù)據(jù)量存儲。
-
數(shù)據(jù)不會隨著應(yīng)用程序重啟而丟失
? ? ? ?分布式緩存和本地緩存不同,擁有自身獨(dú)立的內(nèi)存空間,不會受到應(yīng)用程序進(jìn)程重啟的影響,在應(yīng)用程序重啟時,分布式緩存的存儲數(shù)據(jù)仍然存在。
-
數(shù)據(jù)集中存儲,保證數(shù)據(jù)的一致性
? ? ? ?當(dāng)應(yīng)用程序采用集群方式部署時,集群的每個部署節(jié)點(diǎn)都有一個統(tǒng)一的分布式緩存進(jìn)行數(shù)據(jù)的讀寫操作,所以不會存在像本地緩存中數(shù)據(jù)更新問題,保證了不同服務(wù)器節(jié)點(diǎn)的數(shù)據(jù)一致性。
-
數(shù)據(jù)讀寫分離,高性能,高可用
? ? ? ?分布式緩存一般支持?jǐn)?shù)據(jù)副本機(jī)制,實(shí)現(xiàn)讀寫分離,可以解決高并發(fā)場景中的數(shù)據(jù)讀寫性能問題。而且在多個緩存節(jié)點(diǎn)冗余存儲數(shù)據(jù),提高了緩存數(shù)據(jù)的可用性,避免某個緩存節(jié)點(diǎn)宕機(jī)導(dǎo)致數(shù)據(jù)不可用問題。
缺點(diǎn):
-
數(shù)據(jù)跨網(wǎng)絡(luò)傳輸,讀寫性能不如本地緩存
? ? ? ?分布式緩存是一個獨(dú)立的服務(wù)進(jìn)程,并且和應(yīng)用程序進(jìn)程不在同一臺機(jī)器上,所以數(shù)據(jù)的讀寫要通過遠(yuǎn)程網(wǎng)絡(luò)請求,這樣相對于本地緩存的數(shù)據(jù)讀寫,性能要低一些。
分布式緩存的實(shí)現(xiàn):典型實(shí)現(xiàn)包括 MemCached 和 Redis。
0.3.3 多級緩存
? ? ? ?基于本地緩存和分布式緩存的優(yōu)缺點(diǎn),多級緩存應(yīng)運(yùn)而生,在實(shí)際的業(yè)務(wù)開發(fā)中一般也是采用多級緩存。注意:本地緩存一般存儲更新頻率低,訪問頻率高數(shù)據(jù),分布式緩存一般存儲更新頻率很高的數(shù)據(jù)。
? ? ? ?多級緩存的請求流程:本地緩存作為一級緩存,分布式緩存作為二級緩存。當(dāng)用戶獲取數(shù)據(jù)時,先從一級緩存中獲取數(shù)據(jù),如果一級緩存有數(shù)據(jù)則返回數(shù)據(jù),否則從二級緩存中獲取數(shù)據(jù)。如果二級緩存中有數(shù)據(jù)則更新一級緩存,然后將數(shù)據(jù)返回客戶端。如果二級緩存沒有數(shù)據(jù)則去數(shù)據(jù)庫查詢數(shù)據(jù),然后更新二級緩存,接著再更新一級緩存,最后將數(shù)據(jù)返回給客戶端。
多級緩存的實(shí)現(xiàn):可以使用Guava或者Caffeine作為一級緩存,Redis作為二級緩存。
? ? ? ?注意:在應(yīng)用程序集群部署時,如果數(shù)據(jù)庫的數(shù)據(jù)有更新的情況,一級緩存的數(shù)據(jù)更新容易出現(xiàn)數(shù)據(jù)不一致的情況。因?yàn)槭羌翰渴?#xff0c;多個部署節(jié)點(diǎn)實(shí)現(xiàn)一級緩存數(shù)據(jù)更新難度比較大,不過我們可以通過Redis的消息發(fā)布/訂閱機(jī)制來實(shí)現(xiàn)多個節(jié)點(diǎn)緩存數(shù)據(jù)一致性問題。
1. 本地緩存詳細(xì)介紹及具體實(shí)現(xiàn)
1.1 介紹
參考鏈接:本地緩存:為什么要用本地緩存?用它會有什么問題?_Gimtom的博客-CSDN博客_本地緩存
? ? ? ?對于緩存的作用不言而喻,可以提高查詢效率,比去數(shù)據(jù)庫查詢的速度要快。項(xiàng)目中我們經(jīng)常會使用Nosql數(shù)據(jù)庫,如Redis等做緩存。但是對于數(shù)據(jù)量很小的,訪問非常頻繁的,我們也可以存在本地緩存中。
? ? ? ?在高性能的服務(wù)架構(gòu)設(shè)計(jì)中,緩存是一個不可或缺的環(huán)節(jié)。在實(shí)際的項(xiàng)目中,我們通常會將一些熱點(diǎn)數(shù)據(jù)存儲到Redis或Memcached 這類緩存中間件中,只有當(dāng)緩存的訪問沒有命中時再查詢數(shù)據(jù)庫。在提升訪問速度的同時,也能降低數(shù)據(jù)庫的壓力。
? ? ? ?隨著不斷的發(fā)展,這一架構(gòu)也產(chǎn)生了改進(jìn),在一些場景下可能單純使用Redis類的遠(yuǎn)程緩存已經(jīng)不夠了,還需要進(jìn)一步配合本地緩存使用,例如Guava cache或Caffeine,從而再次提升程序的響應(yīng)速度與服務(wù)性能。于是,就產(chǎn)生了使用本地緩存作為一級緩存,再加上遠(yuǎn)程緩存作為二級緩存的兩級緩存架構(gòu)。
? ? ? ?在先不考慮并發(fā)等復(fù)雜問題的情況下,兩級緩存的訪問流程可以用下面這張圖來表示:
本地緩存:在客戶端本地的物理內(nèi)存中劃出一部分空間來緩存客戶端回寫到服務(wù)器的數(shù)據(jù),當(dāng)本地回寫緩存達(dá)到緩存閾值時,將數(shù)據(jù)寫入到服務(wù)器中。
特點(diǎn):
-
本地緩存是基于本地環(huán)境的內(nèi)存,訪問速度非常快,對于一些變更頻率低、實(shí)時性要求低的數(shù)據(jù),可以放在本地緩存中,提升訪問速度。
-
使用本地緩存能夠減少和Redis類的遠(yuǎn)程緩存間的數(shù)據(jù)交互,減少網(wǎng)絡(luò)I/O開銷,降低這一過程中在網(wǎng)絡(luò)通信上的耗時。
-
本地回寫緩存功能可以在很大程度上降低服務(wù)器讀寫能力和網(wǎng)絡(luò)負(fù)載,一般將本地緩存作為一級緩存,遠(yuǎn)程緩存作為二級緩存。
本地緩存應(yīng)該具有的功能:
超過最大限制有對應(yīng)淘汰策略,如LRU、LFU
過期時間淘汰,如定時、懶式、定期;
持久化
統(tǒng)計(jì)監(jiān)控
1.2 本地緩存方案選型
1.2.1 使用ConcurrentHashMap實(shí)現(xiàn)本地緩存
? ? ? ?緩存的本質(zhì)就是存儲在內(nèi)存中的KV數(shù)據(jù)結(jié)構(gòu),對應(yīng)的就是jdk中線程安全的ConcurrentHashMap,但是要實(shí)現(xiàn)緩存,還需要考慮淘汰、最大限制、緩存過期時間淘汰等等功能。
優(yōu)點(diǎn):實(shí)現(xiàn)簡單,不需要引入第三方包,比較適合一些簡單的業(yè)務(wù)場景。缺點(diǎn)是如果需要更多的特性,需要定制化開發(fā),成本會比較高,并且穩(wěn)定性和可靠性也難以保障。對于比較復(fù)雜的場景,建議使用比較穩(wěn)定的開源工具。
1.2.2 基于Guava Cache實(shí)現(xiàn)本地緩存
? ? ? ?Guava是Google團(tuán)隊(duì)開源的一款 Java 核心增強(qiáng)庫,包含集合、并發(fā)原語、緩存、IO、反射等工具箱,性能和穩(wěn)定性上都有保障,應(yīng)用十分廣泛。Guava Cache支持很多特性:
-
支持最大容量限制
-
支持兩種過期刪除策略(插入時間和訪問時間)
-
支持簡單的統(tǒng)計(jì)功能
-
基于LRU算法實(shí)現(xiàn)
引入依賴如下:
? <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) ? ? // 最大緩存數(shù),超出淘汰.expireAfterWrite(60, TimeUnit.SECONDS) // 過期時間.build();?String orderId = String.valueOf(123456789);// 獲取orderInfo,如果key不存在,callable中調(diào)用getInfo方法返回數(shù)據(jù)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");?// 當(dāng)redis緩存不存在查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}}1.2.3 基于Caffeine實(shí)現(xiàn)本地緩存
? ? ? ?Caffeine是基于java8實(shí)現(xiàn)的新一代緩存工具,緩存性能接近理論最優(yōu),可以看作是Guava Cache的增強(qiáng)版,功能上兩者類似,不同的是Caffeine采用了一種結(jié)合LRU、LFU優(yōu)點(diǎn)的算法:W-TinyLFU,在性能上有明顯的優(yōu)越性。
引入依賴如下:
?<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)//設(shè)置寫緩存后n秒鐘過期.expireAfterWrite(60, TimeUnit.SECONDS)//設(shè)置讀寫緩存后n秒鐘過期,實(shí)際很少用到,類似于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");?// 當(dāng)redis緩存不存在查dblog.info("get data from mysql");info = String.format("{orderId=%s}", orderId);return info;}}1.2.4 基于Encache實(shí)現(xiàn)本地緩存
? ? ? ?Encache是一個純Java的進(jìn)程內(nèi)緩存框架,具有快速、精干等特點(diǎn),是Hibernate中默認(rèn)的CacheProvider。同Caffeine和Guava Cache相比,Encache的功能更加豐富,擴(kuò)展性更強(qiáng)。
優(yōu)點(diǎn):
-
支持多種緩存淘汰算法,包括LRU、LFU和FIFO
-
緩存支持堆內(nèi)存儲、堆外存儲、磁盤存儲(支持持久化)三種
-
支持多種集群方案,解決數(shù)據(jù)共享問題
引入依賴如下:
? <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()// 創(chuàng)建cache實(shí)例.withCache(ORDER_CACHE, CacheConfigurationBuilder// 聲明一個容量為20的堆內(nèi)緩存.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20))).build(true);// 獲取cache實(shí)例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");?// 當(dāng)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功能類似,都是只支持堆內(nèi)緩存,Encache相比功能更為豐富。
-
從性能上進(jìn)行比較,Caffeine最優(yōu)、GuavaCache次之,Encache最差(下圖是三者的性能對比結(jié)果)。
? ? ? ?對于本地緩存的方案中,我比較推薦Caffeine,性能上遙遙領(lǐng)先。雖然Encache功能更為豐富,甚至提供了持久化和集群的功能,但是這些功能完全可以依靠其他方式實(shí)現(xiàn)。真實(shí)的業(yè)務(wù)工程中,建議使用Caffeine作為本地緩存,另外使用redis或者memcache作為分布式緩存,構(gòu)造多級緩存體系,保證性能和可靠性。
1.2 本地緩存問題及解決
1.2.1 緩存一致性
? ? ? ?兩級緩存與數(shù)據(jù)庫的數(shù)據(jù)要保持一致,一旦數(shù)據(jù)發(fā)生了修改,在修改數(shù)據(jù)庫的同時,本地緩存、遠(yuǎn)程緩存應(yīng)該同步更新。
1.2.1.1 解決方案1: MQ
? ? ? ?一般現(xiàn)在部署都是集群部署,有多個不同節(jié)點(diǎn)的本地緩存,可以使用MQ的廣播模式,當(dāng)數(shù)據(jù)修改時向MQ發(fā)送消息,節(jié)點(diǎn)監(jiān)聽并消費(fèi)消息,刪除本地緩存,達(dá)到最終一致性。
1.2.1.2 解決方案2:Canal + MQ
? ? ? ?如果你不想在你的業(yè)務(wù)代碼發(fā)送MQ消息,還可以適用近幾年比較流行的方法:訂閱數(shù)據(jù)庫變更日志,再操作緩存。Canal 訂閱Mysql的 Binlog日志,當(dāng)發(fā)生變化時向MQ發(fā)送消息,進(jìn)而也實(shí)現(xiàn)數(shù)據(jù)一致性。
1.2.2 如何提供本地緩存命中率
待完善。。。。。。
1.3 本地緩存詳細(xì)代碼實(shí)現(xiàn)過程
1.3.1 基于ConcurrentHashMap實(shí)現(xiàn)的本地緩存
參考鏈接:Java實(shí)現(xiàn)本地緩存_柳落青的博客-CSDN博客_java 本地緩存
? ? ? ?本地緩存一般使用鍵值對方式的存儲,那么在Java中肯定是選用Map,由于concurrentHashMap的線程安全性,所以就選擇了這個。過期策略采用的定時清除,實(shí)現(xiàn)方式可以后臺啟動一個線程去掃,也可以用定時器,本例子使用的是定時器。
?package localcache;?import java.util.Map;import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.ConcurrentHashMap;?/*** 基于ConcurrentHashMap定義的本地緩存工具類*/public class ConcurrentHashMapLocalCacheUtil {// 默認(rèn)大小private static final int DEFAULT_CAPACITY = 1024;?// 最大緩存大小private static final int MAX_CAPACITY = 10000;?// 默認(rèn)緩存過期時間private static final long DEFAULT_TIMEOUT = 3600;?// 1000毫秒private static final long SECOND_TIME = 1000;?// 存儲緩存的Mapprivate static final ConcurrentHashMap<String, Object> map;?// 采用定時器進(jìn)行定時private static final Timer timer;?static {// 創(chuàng)建指定容量為DEFAULT_CAPACITY大小的ConcurrentHashMapmap = new ConcurrentHashMap<>(DEFAULT_CAPACITY);// 新建定時器timer = new Timer();}?// 私有化構(gòu)造方法private ConcurrentHashMapLocalCacheUtil() {?}?/*** 利用定時器任務(wù)類實(shí)現(xiàn)的緩存任務(wù)清除類*/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()) {// 有剩余空間時存入數(shù)據(jù)map.put(key, object);// 通過定時器指定任務(wù)的默認(rèn)超時時間timer.schedule(new ClearTask(key), DEFAULT_TIMEOUT);return true;}return false;}?/*** 可指定延時時間的添加緩存*/public static boolean put(String key, Object object, int time_out) {// 添加前檢查緩存大小if (checkCapacity()) {// 有剩余空間時存入數(shù)據(jù)map.put(key, object);// 通過定時器指定任務(wù)的超時時間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) {// 有剩余空間時存入數(shù)據(jù)map.putAll(map);// 為每個緩存添加任務(wù)及超時時間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() {// 判斷緩存中是否還有數(shù)據(jù)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簡單實(shí)現(xiàn)本地緩存-pudn.com
?package localcache;?import java.util.Map;import java.util.concurrent.ConcurrentHashMap;?/*** @ClassName SimpleCache* @Description Java簡單實(shí)現(xiàn)本地緩存,注意:此代碼不適合集群部署環(huán)境* @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;}?/*** 存放一個緩存對象,默認(rèn)保存時間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);// 設(shè)置緩存失效時間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* 若存在,檢查其是否已過有效期,如果已經(jīng)過了則刪除該緩存并返回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;}}?注意:以上代碼還未經(jīng)過正式測試。
1.3.2 基于Guava Cache實(shí)現(xiàn)的本地緩存
待完善。。。。。。
1.3.3 基于Caffeine實(shí)現(xiàn)的本地緩存
待完善。。。。。。
1.3.4 基于Encache實(shí)現(xiàn)的本地緩存
待完善。。。。。。
2. 分布式緩存詳細(xì)介紹及具體實(shí)現(xiàn)
待完善。。。。。。
3.多級緩存詳細(xì)介紹及具體實(shí)現(xiàn)
待完善。。。。。。
總結(jié)
以上是生活随笔為你收集整理的Java实现本地缓存、分布式缓存及多级缓存的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: docker镜像无法删除 Error:N
- 下一篇: 关于JS中的定时器!!!