Java精選面試題(微信小程序):5000+道面試題和選擇題,真實(shí)面經(jīng),簡歷模版,包含Java基礎(chǔ)、并發(fā)、JVM、線程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架構(gòu)設(shè)計(jì)、大廠真題等,在線隨時(shí)刷題!
背景
在高性能的服務(wù)架構(gòu)設(shè)計(jì)中,緩存是一個不可或缺的環(huán)節(jié)。在實(shí)際的項(xiàng)目中,我們通常會將一些熱點(diǎn)數(shù)據(jù)存儲到Redis或Memcached 這類緩存中間件中,只有當(dāng)緩存的訪問沒有命中時(shí)再查詢數(shù)據(jù)庫。在提升訪問速度的同時(shí),也能降低數(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ù)雜問題的情況下,兩級緩存的訪問流程可以用下面這張圖來表示:

為什么要使用本地緩存
本地緩存基于本地環(huán)境的內(nèi)存,訪問速度非常快,對于一些變更頻率低、實(shí)時(shí)性要求低的數(shù)據(jù),可以放在本地緩存中,提升訪問速度。
使用本地緩存能夠減少和Redis類的遠(yuǎn)程緩存間的數(shù)據(jù)交互,減少網(wǎng)絡(luò)I/O開銷,降低這一過程中在網(wǎng)絡(luò)通信上的耗時(shí)。
設(shè)計(jì)一個本地內(nèi)存需要有什么功能
存儲,并可以讀、寫;
原子操作(線程安全),如ConcurrentHashMap;
可以設(shè)置緩存的最大限制;
超過最大限制有對應(yīng)淘汰策略,如LRU、LFU;
過期時(shí)間淘汰,如定時(shí)、懶式、定期;
持久化;
統(tǒng)計(jì)監(jiān)控。
1. 使用ConcurrentHashMap實(shí)現(xiàn)本地緩存
緩存的本質(zhì)就是存儲在內(nèi)存中的KV數(shù)據(jù)結(jié)構(gòu),對應(yīng)的就是jdk中線程安全的ConcurrentHashMap,但是要實(shí)現(xiàn)緩存,還需要考慮淘汰、最大限制、緩存過期時(shí)間淘汰等等功能;
優(yōu)點(diǎn)是實(shí)現(xiàn)簡單,不需要引入第三方包,比較適合一些簡單的業(yè)務(wù)場景。缺點(diǎn)是如果需要更多的特性,需要定制化開發(fā),成本會比較高,并且穩(wěn)定性和可靠性也難以保障。對于比較復(fù)雜的場景,建議使用比較穩(wěn)定的開源工具。
2. 基于Guava Cache實(shí)現(xiàn)本地緩存
Guava是Google團(tuán)隊(duì)開源的一款 Java 核心增強(qiáng)庫,包含集合、并發(fā)原語、緩存、IO、反射等工具箱,性能和穩(wěn)定性上都有保障,應(yīng)用十分廣泛。Guava Cache支持很多特性:
支持最大容量限制
支持兩種過期刪除策略(插入時(shí)間和訪問時(shí)間)
支持簡單的統(tǒng)計(jì)功能
基于LRU算法實(shí)現(xiàn)
使用代碼如下:
com.google.guava guava 31.1-jre @Slf4j public class GuavaCacheTest { public static void main(String[] args) throws ExecutionException { Cache cache = CacheBuilder.newBuilder() .initialCapacity(5) // 初始容量 .maximumSize(10) // 最大緩存數(shù),超出淘汰 .expireAfterWrite(60, TimeUnit.SECONDS) // 過期時(shí)間 .build(); String orderId = String.valueOf(123456789); // 獲取orderInfo,如果key不存在,callable中調(diào)用getInfo方法返回?cái)?shù)據(jù) String orderInfo = cache.get(orderId, () -> getInfo(orderId)); log.info("orderInfo = {}", orderInfo); } private static String getInfo(String orderId) { String info = ""; // 先查詢r(jià)edis緩存 log.info("get data from
3. Caffeine
Caffeine是基于java8實(shí)現(xiàn)的新一代緩存工具,緩存性能接近理論最優(yōu)??梢钥醋魇荊uava Cache的增強(qiáng)版,功能上兩者類似,不同的是Caffeine采用了一種結(jié)合LRU、LFU優(yōu)點(diǎn)的算法:W-TinyLFU,在性能上有明顯的優(yōu)越性
使用代碼如下:
com.github.ben-manes.caffeine caffeine 2.9.3 @Slf4j public class CaffeineTest { public static void main(String[] args) { Cache cache = Caffeine.newBuilder() .initialCapacity(5) // 超出時(shí)淘汰 .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 = ""; // 先查詢r(jià)edis緩存 log.info("get data from redis"); // 當(dāng)redis緩存不存在查db log.info("get data from
4. Encache
Encache是一個純Java的進(jìn)程內(nèi)緩存框架,具有快速、精干等特點(diǎn),是Hibernate中默認(rèn)的CacheProvider。同Caffeine和Guava Cache相比,Encache的功能更加豐富,擴(kuò)展性更強(qiáng):
支持多種緩存淘汰算法,包括LRU、LFU和FIFO
緩存支持堆內(nèi)存儲、堆外存儲、磁盤存儲(支持持久化)三種
支持多種集群方案,解決數(shù)據(jù)共享問題
使用代碼如下:
org.ehcache ehcache 3.9.7 @Slf4j public 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 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 = ""; // 先查詢r(jià)edis緩存 log.info("get data from redis"); // 當(dāng)redis緩存不存在查db log.info("get data from mysql"); info = String.format("{orderId=%s}", orderId); return info; } }
本地緩存問題及解決1. 緩存一致性
兩級緩存與數(shù)據(jù)庫的數(shù)據(jù)要保持一致,一旦數(shù)據(jù)發(fā)生了修改,在修改數(shù)據(jù)庫的同時(shí),本地緩存、遠(yuǎn)程緩存應(yīng)該同步更新。
解決方案1: MQ
一般現(xiàn)在部署都是集群部署,有多個不同節(jié)點(diǎn)的本地緩存; 可以使用MQ的廣播模式,當(dāng)數(shù)據(jù)修改時(shí)向MQ發(fā)送消息,節(jié)點(diǎn)監(jiān)聽并消費(fèi)消息,刪除本地緩存,達(dá)到最終一致性;

解決方案2:Canal + MQ
如果你不想在你的業(yè)務(wù)代碼發(fā)送MQ消息,還可以適用近幾年比較流行的方法:訂閱數(shù)據(jù)庫變更日志,再操作緩存。Canal 訂閱Mysql的 Binlog日志,當(dāng)發(fā)生變化時(shí)向MQ發(fā)送消息,進(jìn)而也實(shí)現(xiàn)數(shù)據(jù)一致性。

2. 如何提高本地緩存命中率
參考:如何提高緩存命中率
3. 本地內(nèi)存的技術(shù)選型問題
從易用性角度,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)造多級緩存體系,保證性能和可靠性。
來源:https://blog.csdn.net/One_hundred_nice/article/details/123950638
公眾號“Java精選”所發(fā)表內(nèi)容注明來源的,版權(quán)歸原出處所有(無法查證版權(quán)的或者未注明出處的均來自網(wǎng)絡(luò),系轉(zhuǎn)載,轉(zhuǎn)載的目的在于傳遞更多信息,版權(quán)屬于原作者。如有侵權(quán),請聯(lián)系,筆者會第一時(shí)間刪除處理!
最近有很多人問,有沒有讀者交流群!加入方式很簡單,公眾號Java精選,回復(fù)“加群”,即可入群!
文章有幫助的話,點(diǎn)在看,轉(zhuǎn)發(fā)吧!
熱門跟貼