搭建Caffeine+Redis多級(jí)緩存機(jī)制
本地緩存的簡(jiǎn)單實(shí)現(xiàn)方案有HashMap,CucurrentHashMap,成熟的本地緩存方案有Guava 與 Caffeine ,企業(yè)級(jí)應(yīng)用推薦下面說(shuō)下兩者的區(qū)別
0. 核心異同對(duì)比
| 特性 | Guava Cache | Caffeine |
|---|---|---|
| 誕生背景 | Google Guava 庫(kù)的一部分(2011年) | 基于 Guava Cache 重構(gòu)的現(xiàn)代緩存庫(kù)(2015+) |
| 性能 | 中等(鎖競(jìng)爭(zhēng)較多) | 極高(優(yōu)化并發(fā)設(shè)計(jì),吞吐量提升5~10倍) |
| 內(nèi)存管理 | 基于 LRU 算法 | 結(jié)合 W-TinyLFU 算法(高命中率) |
| 過(guò)期策略 | 支持 expireAfterWrite/access | 支持 expireAfterWrite/access + refresh |
| 緩存回收 | 同步阻塞 | 異步非阻塞(后臺(tái)線程) |
| 監(jiān)控統(tǒng)計(jì) | 基礎(chǔ)統(tǒng)計(jì)(命中率等) | 詳細(xì)統(tǒng)計(jì)(命中率、加載時(shí)間等) |
| 依賴 | 需引入整個(gè) Guava 庫(kù) | 輕量(僅依賴 Caffeine) |
| 社區(qū)維護(hù) | 維護(hù)模式(新功能少) | 活躍更新(Java 17+ 兼容) |
從上面的比較可知, Caffeine 各方面是優(yōu)于Guava的,因此在搭建多級(jí)緩存機(jī)制時(shí),建議使用Caffeine+Redis的組合方案。
業(yè)務(wù)執(zhí)行流程:
- 請(qǐng)求優(yōu)先讀取 Caffeine 本地緩存(超快,減少網(wǎng)絡(luò)IO)。
- 本地緩存未命中 → 讀取 Redis 分布式緩存。
- Redis 未命中 → 查詢數(shù)據(jù)庫(kù),并回填到兩級(jí)緩存。
下面介紹下實(shí)現(xiàn)方式
注意:下面的實(shí)現(xiàn)方式是基于Springboot 2.4+,版本不同,配置上會(huì)略有差異
1.maven中引入下面的依賴
<!-- Caffeine 本地緩存 --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <!-- 緩存抽象層 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- redis 緩存操作 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
2.application中進(jìn)行配置
spring:
cache:
caffeine:
spec: maximumSize=1000,expireAfterWrite=10m # 本地緩存
redis:
time-to-live: 1h # Redis緩存過(guò)期時(shí)間
# redis 配置
redis:
# 地址
host: 127.0.0.1
# 端口,默認(rèn)為6379
port: 6379
# 數(shù)據(jù)庫(kù)索引
database: 0
# 密碼
password: abc123
# 連接超時(shí)時(shí)間
timeout: 6000ms # 連接超時(shí)時(shí)長(zhǎng)(毫秒)
jedis:
pool:
max-active: 1000 # 連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制)
max-wait: -1ms # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制)
max-idle: 10 # 連接池中的最大空閑連接
min-idle: 5 # 連接池中的最小空閑連接3.自定義多級(jí)緩存管理器
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration cacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES);
}
@Bean
@Primary // 添加 @Primary 注解指定 CaffeineCacheManager 作為默認(rèn)的緩存管理器
public CacheManager caffeineCacheManager(Caffeine<Object, Object> caffeine) {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(caffeine);
return manager;
}
@Bean
public RedisCacheManager redisCacheManager(
RedisConnectionFactory redisConnectionFactory,
RedisCacheConfiguration cacheConfiguration) {
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
@Bean
public CacheManager compositeCacheManager(
@Qualifier("caffeineCacheManager") CacheManager caffeineCacheManager,
@Qualifier("redisCacheManager") CacheManager redisCacheManager) {
return new CompositeCacheManager(
caffeineCacheManager,
redisCacheManager
);
}
}
4.業(yè)務(wù)邏輯層調(diào)用
使用示例:
@Service
public class ProductService {
@Autowired
private ProductRepository repository;
// 優(yōu)先讀本地緩存,其次Redis,最后數(shù)據(jù)庫(kù)
@Cacheable(cacheNames = "product", key = "#id")
public Product getProductById(Long id) {
return repository.findById(id).orElseThrow();
}
// 更新數(shù)據(jù)時(shí)清除兩級(jí)緩存
@CacheEvict(cacheNames = "product", key = "#product.id")
public Product updateProduct(Product product) {
return repository.save(product);
}
}手動(dòng)控制多級(jí)緩存
@Service
public class CacheService {
@Autowired
private CacheManager cacheManager;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Product getProductWithManualControl(Long id) {
// 1. 先查本地緩存
Cache caffeineCache = cacheManager.getCache("product");
Product product = caffeineCache.get(id, Product.class);
if (product != null) {
return product;
}
// 2. 查Redis緩存
product = (Product) redisTemplate.opsForValue().get("product:" + id);
if (product != null) {
// 回填本地緩存
caffeineCache.put(id, product);
return product;
}
// 3. 查數(shù)據(jù)庫(kù)
product = repository.findById(id).orElseThrow();
// 回填兩級(jí)緩存
redisTemplate.opsForValue().set("product:" + id, product, Duration.ofHours(1));
caffeineCache.put(id, product);
return product;
}
}緩存一致性
- 使用
@CacheEvict或Redis Pub/Sub同步失效兩級(jí)緩存。 - 示例:通過(guò) Redis 消息通知其他節(jié)點(diǎn)清理本地緩存。
防止緩存擊穿
- Caffeine 配置
refreshAfterWrite:
Caffeine.newBuilder()
.refreshAfterWrite(5, TimeUnit.MINUTES)
.build(key -> loadFromRedisOrDb(key));監(jiān)控統(tǒng)計(jì):
- Caffeine 統(tǒng)計(jì):cache.getNativeCache().stats()
- Redis 統(tǒng)計(jì):INFO commandstats
驗(yàn)證多級(jí)緩存
- 本地緩存生效:連續(xù)調(diào)用同一接口,觀察第二次響應(yīng)時(shí)間驟降。
- Redis 緩存生效:重啟應(yīng)用后,首次請(qǐng)求仍快速返回(數(shù)據(jù)來(lái)自Redis)。
到此這篇關(guān)于搭建Caffeine+Redis多級(jí)緩存機(jī)制的文章就介紹到這了,更多相關(guān)Caffeine Redis緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Redis實(shí)現(xiàn)搶紅包和發(fā)紅包功能
搶紅包是我們生活常用的社交功能, 這個(gè)功能最主要的特點(diǎn)就是用戶的并發(fā)請(qǐng)求高, 在系統(tǒng)設(shè)計(jì)上, 可以使用非常多的辦法來(lái)扛住用戶的高并發(fā)請(qǐng)求, 在本文中簡(jiǎn)要介紹使用Redis緩存中間件來(lái)實(shí)現(xiàn)搶紅包算法,需要的朋友可以參考下2024-04-04
關(guān)于Redis未授權(quán)訪問(wèn)的問(wèn)題
這篇文章主要介紹了Redis未授權(quán)訪問(wèn)的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
redis中使用bloomfilter的白名單功能解決緩存穿透問(wèn)題
本文主要介紹了redis中使用bloomfilter的白名單功能解決緩存穿透問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
RedisTemplate集成+封裝RedisUtil過(guò)程
本文介紹了如何搭建一個(gè)多模塊的Redis項(xiàng)目,包括項(xiàng)目搭建、配置和測(cè)試,通過(guò)使用父項(xiàng)目管理多個(gè)子模塊,可以實(shí)現(xiàn)單點(diǎn)構(gòu)建、統(tǒng)一版本管理和清晰的項(xiàng)目結(jié)構(gòu),文章還提供了在Spring Boot項(xiàng)目中集成RedisTemplate的示例,并解決了編碼問(wèn)題2024-12-12
Redis集群指定主從關(guān)系及動(dòng)態(tài)增刪節(jié)點(diǎn)方式
這篇文章主要介紹了Redis集群指定主從關(guān)系及動(dòng)態(tài)增刪節(jié)點(diǎn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
redis實(shí)現(xiàn)存儲(chǔ)帖子的點(diǎn)贊狀態(tài)和數(shù)量的示例代碼
使用Redis來(lái)實(shí)現(xiàn)點(diǎn)贊功能是一種高效的選擇,因?yàn)镽edis是一個(gè)內(nèi)存數(shù)據(jù)庫(kù),適用于處理高并發(fā)的數(shù)據(jù)操作,這篇文章主要介紹了redis實(shí)現(xiàn)存儲(chǔ)帖子的點(diǎn)贊狀態(tài)和數(shù)量的示例代碼,需要的朋友可以參考下2023-09-09
Redis中SDS簡(jiǎn)單動(dòng)態(tài)字符串詳解
Redis中的SDS(Simple?Dynamic?String)是一種自動(dòng)擴(kuò)容的字符串實(shí)現(xiàn)方式,它可以提供高效的字符串操作,并且支持二進(jìn)制安全。SDS的設(shè)計(jì)使得它可以在O(1)時(shí)間內(nèi)實(shí)現(xiàn)字符串長(zhǎng)度的獲取和修改,同時(shí)也可以在O(N)的時(shí)間內(nèi)進(jìn)行字符串的拼接和截取。2023-04-04
淺析Redis中紅鎖RedLock的實(shí)現(xiàn)原理
RedLock?是一種分布式鎖的實(shí)現(xiàn)算法,由?Redis?的作者?Salvatore?Sanfilippo(也稱為?Antirez)提出,本文主要為大家詳細(xì)介紹了紅鎖RedLock的實(shí)現(xiàn)原理,感興趣的可以了解下2024-02-02

