废话不多说直接上代码:

1.依赖
         <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.9.3</version>
        </dependency>

这里版本java8所以用的2.9.3

2.配置类:过期时间redis30分钟+caffeine3分钟
package com.hmdp.config;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Scheduler;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;

@Configuration
@Slf4j
public class CacheConfig {

    @Autowired
    private ShopMapper shopMapper;   // 使用MyBatis-Plus Mapper

    @Autowired
    private StringRedisTemplate stringRedisTemplate;  //注意stringRedisTemplate要配置序列化器

    @Autowired
    private Cache<Long, Shop> caffeineCache;  // 上面定义的Bean
    @Bean
    public Cache<Long, Shop> caffeineCache() {
        return Caffeine.newBuilder()
                // 初始容量
                .initialCapacity(100)
                // 最大条目数(超过会按策略淘汰)
                .maximumSize(10_000)
                // 写入后过期时间(防雪崩关键1:本地TTL较短)
                .expireAfterWrite(3, TimeUnit.MINUTES)
                // 可选:定时清理过期条目
                .scheduler(Scheduler.systemScheduler())
                // 记录统计信息(用于监控)
                .recordStats()
                .build();
    }

    @PostConstruct
    public void preloadHotShops() {
        log.info("开始预热店铺缓存...");
        // 一次性查出所有店铺(10条,不会有性能问题)
        List<Shop> shops = shopMapper.selectList(null);  //我这里数据库中只有十条数据,如果数据量过大,取hotK就行,不然小心内存爆掉
        for (Shop shop : shops) {  //遍历添加
            if (shop != null) {  //如果为空报空指针异常
                caffeineCache.put(shop.getId(), shop);  
                String key = CACHE_SHOP_KEY + shop.getId();
                stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);  //注意反编译,
            }
        }
        log.info("缓存预热完成,共 {} 家店铺", shops.size());


    }

}
3.使用,这里直接写业务层
1.先从内存中查:
//从caffeine中查数据
        Shop shop = caffeineCache.getIfPresent(id);
        if (shop != null) {
            return Result.ok(shop);
        }
2.再从redis中查,能查到再回填内存
//从redis中查询店铺    shopstr是string类型的json字符串
        String shopStr = stringRedisTemplate.opsForValue().get(key);
        //存在,直接返回
        if (StrUtil.isNotBlank(shopStr)){
            caffeineCache.put(id, JSONUtil.toBean(shopStr, Shop.class));   //回填caffeine
            return Result.ok(JSONUtil.toBean(shopStr, Shop.class));  //转换成对象
        }
3.都没查到,从数据库中查,然后回填redis跟caffeine
//不存在,从数据库中查询
        shop = getById(id);  从数据库中查询
        if (shop == null){  //为什么还要判断,因为前面布隆过滤器可能误判 本来这个数据就不存在
            //缓存穿透,,添加null值
            stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
            //数据库中不存在:返回错误
            return null;
        }
        //数据库中存在,返回店铺,,,保存到redis中
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
        stringRedisTemplate.expire(key, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //保存到caffeine中
        caffeineCache.put(shop.getId(), shop);
        return Result.ok(shop);
4.总结思路,使用caffeine基于JVM本地缓存,再用redis做二次缓存,即使redis挂了还有caffeine不会打到db,一般来说这种都会有缓存数据的一致性问题,所以不适合强一致性业务
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐