java實(shí)現(xiàn)本地緩存的示例代碼
在高性能服務(wù)架構(gòu)設(shè)計(jì)中,緩存是不可或缺的環(huán)節(jié)。在實(shí)際項(xiàng)目中,我們通常會(huì)將一些熱點(diǎn)數(shù)據(jù)存儲(chǔ)在Redis或Memcached等緩存中間件中,只有在緩存訪問(wèn)未命中時(shí)才查詢(xún)數(shù)據(jù)庫(kù)。
在提高訪問(wèn)速度的同時(shí),還可以減輕數(shù)據(jù)庫(kù)的壓力。
為什么要使用本地緩存
隨著不斷的發(fā)展,這個(gè)架構(gòu)也得到了完善。在某些場(chǎng)景下,僅僅使用Redis類(lèi)的遠(yuǎn)程緩存可能還不夠。需要進(jìn)一步與本地緩存配合使用,比如Guava或者Caffeine,從而再次提高程序的響應(yīng)速度和服務(wù)性能。
由此,形成了以本地緩存作為一級(jí)緩存、遠(yuǎn)程緩存作為二級(jí)緩存的二級(jí)緩存架構(gòu)。
總結(jié):
- 本地緩存基于本地環(huán)境的內(nèi)存,訪問(wèn)速度非??臁?duì)于一些變化頻率不高、實(shí)時(shí)性要求不高的數(shù)據(jù),可以放在本地緩存中,以提高訪問(wèn)速度。
- 使用本地緩存可以減少與Redis類(lèi)的遠(yuǎn)程緩存的數(shù)據(jù)交互,減少網(wǎng)絡(luò)I/O開(kāi)銷(xiāo),減少這個(gè)過(guò)程中網(wǎng)絡(luò)通信的耗時(shí)。
本地存儲(chǔ)的基本功能
- 它可以存儲(chǔ)、讀取和寫(xiě)入。
- 原子操作(線程安全),例如ConcurrentHashMap。
- 可以設(shè)置緩存的最大限制。
- 超過(guò)最大限制有相應(yīng)的淘汰策略,如LRU、LFU。
- 統(tǒng)計(jì)監(jiān)控。
方案選擇
1.使用ConcurrentHashMap
緩存的本質(zhì)是KV存儲(chǔ)在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),對(duì)應(yīng)JDK中的線程安全ConcurrentHashMap,但是要實(shí)現(xiàn)緩存,需要考慮消除、最大限制、消除緩存過(guò)期時(shí)間等功能。
優(yōu)點(diǎn)ConcurrentHashMap是實(shí)現(xiàn)簡(jiǎn)單,不需要引入第三方包,所以比較適合一些簡(jiǎn)單的業(yè)務(wù)場(chǎng)景。
缺點(diǎn)是如果需要更多的功能,需要定制開(kāi)發(fā),成本會(huì)比較高,穩(wěn)定性和可靠性難以保證。
對(duì)于更復(fù)雜的場(chǎng)景,建議使用相對(duì)穩(wěn)定的開(kāi)源工具。
2. 使用Guava緩存
Guava是Google團(tuán)隊(duì)開(kāi)源的一個(gè)Java核心增強(qiáng)庫(kù)。它包括集合、并發(fā)原語(yǔ)、緩存、IO、反射和其他工具箱。性能和穩(wěn)定性有保證,應(yīng)用廣泛。
- Guava Cache 支持許多功能:
- 支持最大容量限制。
- 支持兩種過(guò)期刪除策略。
- 支持簡(jiǎn)單的統(tǒng)計(jì)功能。 它是基于LRU算法實(shí)現(xiàn)的。
示例代碼如下
<dependency><dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
@Slf4j
public class GuavaCacheTest {
public static void main(String[] args) throws ExecutionException {
Cache<String, String> cache = CacheBuilder.newBuilder()
.initialCapacity(5) // 初始容量
.maximumSize(10) // 緩存的最大數(shù)量,超過(guò)該數(shù)量將被淘汰
.expireAfterWrite(60, TimeUnit.SECONDS) // 過(guò)期時(shí)間
.build();
String orderId = String.valueOf(123456789);
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不存在 查db
log.info("get data from mysql");
info = String.format("{orderId=%s}", orderId);
return info;
}
}
3. 使用Encache
Encache是?一個(gè)純Java進(jìn)程內(nèi)緩存框架,快速且精簡(jiǎn)。它是 Hibernate 中默認(rèn)的 CacheProvider。 與Caffeine和Guava Cache相比,Encache功能更豐富,可擴(kuò)展性更強(qiáng)。 支持LRU、LFU、FIFO等多種緩存淘汰算法。
緩存支持三種類(lèi)型:堆內(nèi)存儲(chǔ)、堆外存儲(chǔ)、磁盤(pán)存儲(chǔ)(支持持久化)。
支持多種集群方案,解決數(shù)據(jù)共享問(wèn)題。
使用方法如下:
<dependency><dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.7</version>
</dependency>
@Slf4j
public class EhcacheTest {
private static final String ORDER_CACHE = "orderCache";
public static void main(String[] args) {
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
// 創(chuàng)建實(shí)例
.withCache(ORDER_CACHE, CacheConfigurationBuilder
// 聲明一個(gè)容量為20的堆內(nèi)緩存
.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20)))
.build(true);
// 得到緩存實(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");
// 不存在 查db
log.info("get data from mysql");
info = String.format("{orderId=%s}", orderId);
return info;
}
}
本地緩存常見(jiàn)問(wèn)題
緩存一致性
二級(jí)緩存和數(shù)據(jù)庫(kù)中的數(shù)據(jù)必須一致。一旦數(shù)據(jù)被修改,本地緩存和遠(yuǎn)程緩存應(yīng)在數(shù)據(jù)庫(kù)修改的同時(shí)同步更新。
解決方案1:使用MQ。
目前的部署一般都是集群部署,不同節(jié)點(diǎn)有多個(gè)本地緩存。 可以利用MQ的廣播模式,在數(shù)據(jù)修改時(shí)向MQ發(fā)送消息,由節(jié)點(diǎn)監(jiān)聽(tīng)并消費(fèi)該消息,刪除本地緩存,達(dá)到最終一致性。
解決方案二:Canal+MQ。
如果你的業(yè)務(wù)代碼中不想發(fā)送MQ消息,也可以應(yīng)用近年來(lái)比較流行的方法:訂閱數(shù)據(jù)庫(kù)變更日志,然后操作緩存。 Canal訂閱Mysql的Binlog日志,當(dāng)發(fā)生變化時(shí)向MQ發(fā)送消息,從而也實(shí)現(xiàn)了數(shù)據(jù)的一致性。
如何提高緩存命中率
根據(jù)業(yè)務(wù)場(chǎng)景設(shè)計(jì)合理的時(shí)效性。
緩存適用于讀多寫(xiě)少的場(chǎng)景,盡可能關(guān)注訪問(wèn)頻率高、時(shí)效性要求不高的熱點(diǎn)業(yè)務(wù)。 訪問(wèn)頻率越高,點(diǎn)擊率越高。 時(shí)效性越低,緩存時(shí)間越長(zhǎng),相同key、相同請(qǐng)求數(shù)下命中率越高。
設(shè)計(jì)合理的緩存粒度。
緩存的粒度越小,緩存命中率越高。 單個(gè)key緩存的數(shù)據(jù)單元越小,被改變的可能性就越小。
設(shè)計(jì)合理的緩存過(guò)期策略。
這里的緩存過(guò)期策略并不是redis內(nèi)置的定期刪除和惰性刪除策略,而是根據(jù)業(yè)務(wù)場(chǎng)景優(yōu)化了key的過(guò)期時(shí)間。 例如,如果用戶(hù)的關(guān)鍵信息同時(shí)過(guò)期,那么當(dāng)多個(gè)用戶(hù)同時(shí)查詢(xún)時(shí),都會(huì)落入數(shù)據(jù)庫(kù),也就是說(shuō)避免緩存同時(shí)失效。
合理的緩存預(yù)加載。
redis緩存必須從數(shù)據(jù)庫(kù)加載,所以當(dāng)?shù)谝淮问褂脭?shù)據(jù)時(shí),redis需要從數(shù)據(jù)庫(kù)加載數(shù)據(jù)。 我們可以在用戶(hù)訪問(wèn)之前將需要的數(shù)據(jù)提前加載到緩存中,這樣用戶(hù)第一次訪問(wèn)時(shí)就可以直接去緩存而不用去查詢(xún)數(shù)據(jù)庫(kù)。
防止緩存崩潰和擊穿。
緩存擊穿和崩潰也會(huì)影響緩存命中率。當(dāng)然,如果發(fā)生的話,應(yīng)用失敗的可能性很大。
設(shè)計(jì)合理的緩存容量。
注意緩存容量,如果太小,會(huì)觸發(fā)redis內(nèi)存淘汰機(jī)制。線上redis一般配置maxmemory-policy allkeys-lru算法進(jìn)行內(nèi)存消除。這樣就會(huì)刪除一些key,造成緩存穿透,從而降低緩存命中率,所以需要合理配置緩存容量。
到此這篇關(guān)于java實(shí)現(xiàn)本地緩存的示例代碼的文章就介紹到這了,更多相關(guān)java本地緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何使用Spring MVC的消息轉(zhuǎn)換器設(shè)置日期格式
這篇文章主要介紹了如何使用Spring MVC的消息轉(zhuǎn)換器設(shè)置日期格式,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
Spring Boot整合MyBatis-Flex全過(guò)程
這篇文章主要介紹了Spring Boot整合MyBatis-Flex全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
在java中 利用匿名內(nèi)部類(lèi)進(jìn)行較簡(jiǎn)潔的雙括弧初始化的方法
本篇文章小編將為大家介紹,關(guān)于在java中 利用匿名內(nèi)部類(lèi)進(jìn)行較簡(jiǎn)潔的雙括弧初始化的方法,有需要的朋友可以參考一下2013-04-04
Java游戲服務(wù)器系列之Netty相關(guān)知識(shí)總結(jié)
今天帶大家來(lái)學(xué)習(xí)Java游戲服務(wù)器的相關(guān)知識(shí),文中對(duì)Netty作了非常詳細(xì)的介紹,對(duì)正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05
JDK13.0.1安裝與環(huán)境變量的配置教程圖文詳解(Win10平臺(tái)為例)
這篇文章主要介紹了JDK13.0.1安裝與環(huán)境變量的配置教程圖文詳解(Win10平臺(tái)為例),本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01
Java微信公眾平臺(tái)開(kāi)發(fā)(14) 微信web開(kāi)發(fā)者工具使用
這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺(tái)開(kāi)發(fā)第十四步,微信web開(kāi)發(fā)者工具的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
線程池調(diào)用kafka發(fā)送消息產(chǎn)生的內(nèi)存泄漏問(wèn)題排查解決
這篇文章主要為大家介紹了線程池調(diào)用kafka發(fā)送消息產(chǎn)生的內(nèi)存泄漏問(wèn)題排查解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08

