java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java rediscache localcache 复合缓存

JAVA缓存的使用RedisCache、LocalCache、复合缓存的操作

作者:Demon_Hao

RedisCache是基于Redis的缓存,数据存储在内存中,并且可以被多个应用实例共享,属于分布式缓存,本文给大家介绍JAVA缓存的使用RedisCache、LocalCache、复合缓存的相关操作,感兴趣的朋友跟随小编一起看看吧

1️⃣RedisCache(分布式缓存)

概念
RedisCache 是基于 Redis 的缓存,数据存储在内存中,并且可以被多个应用实例共享,属于分布式缓存

优点

缺点(顺便提醒)

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
/**
 * Redis缓存工具类
 */
@Slf4j
@Component
public class RedisCacheTemplate {
    @Resource
    private StringRedisTemplate redisTemplate;
    @Resource
    private ObjectMapper objectMapper;
    /**
     * 指定TypeReference类型获取数据
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull TypeReference<T> clazz, @NonNull Supplier<T> supplier) {
        return getT(key, duration, supplier, strValue -> {
            try {
                return objectMapper.readValue(strValue, clazz);
            } catch (Exception e) {
                log.error("Redis反序列化失败,KEY:{}", key, e);
                throw new RuntimeException(e);
            }
        });
    }
    /**
     * Class类型获取数据
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull Class<T> clazz, @NonNull Supplier<T> supplier) {
        return getT(key, duration, supplier, strValue -> {
            try {
                return objectMapper.readValue(strValue, clazz);
            } catch (Exception e) {
                log.error("Redis反序列化失败,KEY:{}", key, e);
                throw new RuntimeException(e);
            }
        });
    }
    /**
     * 写入数据
     */
    public <T> void set(@NonNull String key, @NonNull Duration duration, @NonNull T object) {
        String redisKey = this.buildKey(key);
        try {
            String json = objectMapper.writeValueAsString(object);
            redisTemplate.opsForValue().set(redisKey, json, duration);
        } catch (JsonProcessingException e) {
            log.error("Redis序列化失败,KEY:{}", key, e);
            throw new RuntimeException(e);
        }
    }
    /**
     * 移除数据
     */
    public void del(@NonNull String key) {
        String redisKey = this.buildKey(key);
        redisTemplate.delete(redisKey);
    }
    /**
     * 公共业务代码处理
     */
    private <T> @Nullable T getT(@NotNull String key, @NotNull Duration duration, @NotNull Supplier<T> supplier, Function<String, T> deserialize) {
        String redisKey = this.buildKey(key);
        String strValue = redisTemplate.opsForValue().get(redisKey);
        if (Objects.isNull(strValue)) {
            T value = supplier.get();
            if (Objects.nonNull(value)) {
                this.set(key, duration, value);
            }
            return value;
        } else {
            return deserialize.apply(strValue);
        }
    }
    /**
     * Redis的KEY拼接
     */
    private String buildKey(String key) {
        return "test:cache:" + key;
    }
}
// 使用方式
public static void main(String[] args) {
    private static final TypeReference<List<String>> LIST_TEST = new TypeReference<>() {
    };
    @Resource
    private RedisCacheTemplate redisCacheTemplate;
    List<String> strings = redisCacheTemplate.get(RedisKey.TEST_LIST, Duration.ofMinutes(60), LIST_TEST, () -> {
        System.out.println("TEST");
        return List.of("Test");
    });
}

2️⃣LocalCache(本地缓存)

概念
LocalCache 是应用本地内存缓存,数据只存在于当前应用实例的内存里,常见实现有 Guava Cache、Caffeine

优点

缺点

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
/**
 * Caffeine本地缓存工具类
 */
@Slf4j
@Component
public class LocalCacheTemplate {
    /**
     * JVM 全局缓存
     * 存储 CacheValue 包装对象,支持每条缓存独立 TTL
     */
    private static final Cache<String, CacheValue<Object>> CACHE = Caffeine.newBuilder()
        // LRU 淘汰:数量满了移除长时间未访问或最早的数据
        .maximumSize(30000)
        // TTL 过期:按时间清理
        .expireAfter(new Expiry<String, CacheValue<Object>>() {
            @Override
            public long expireAfterCreate(@NotNull String key, @NotNull CacheValue<Object> value, long currentTime) {
                return value.ttlNanos();
            }
            @Override
            public long expireAfterUpdate(@NotNull String key, @NotNull CacheValue<Object> value, long currentTime, long currentDuration) {
                return value.ttlNanos();
            }
            @Override
            public long expireAfterRead(@NotNull String key, @NotNull CacheValue<Object> value, long currentTime, long currentDuration) {
                // 读取不改变 TTL
                return currentDuration;
            }
        }).build();
    @Resource
    private ObjectMapper objectMapper;
    /**
     * 判断KEY是否存在(非强一致,仅用于弱判断)
     */
    public boolean exists(@NonNull String key) {
        String redisKey = this.buildKey(key);
        return CACHE.getIfPresent(redisKey) != null;
    }
    /**
     * 获取KEY数据
     */
    public <T> T get(@NonNull String key, @NonNull Class<T> clazz) {
        String redisKey = this.buildKey(key);
        CacheValue<Object> wrapper = CACHE.getIfPresent(redisKey);
        return getT(key, clazz, wrapper);
    }
    /**
     * Class类型获取数据,没有就写入
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull Class<T> clazz, @NonNull Supplier<T> supplier) {
        String redisKey = this.buildKey(key);
        CacheValue<Object> wrapper = CACHE.get(redisKey, k -> new CacheValue<>(supplier.get(), duration.toNanos()));
        return getT(key, clazz, wrapper);
    }
    /**
     * 指定TypeReference类型获取数据,没有就写入
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull TypeReference<T> clazz, @NonNull Supplier<T> supplier) {
        String redisKey = this.buildKey(key);
        CacheValue<Object> wrapper = CACHE.get(redisKey, k -> new CacheValue<>(supplier.get(), duration.toNanos()));
        if (wrapper == null || wrapper.value() == null) {
            return null;
        }
        return getSerialize(key, wrapper.value(), strValue -> objectMapper.convertValue(strValue, clazz));
    }
    /**
     * 写入数据
     */
    public void set(@NonNull String key, @NonNull Duration duration, @NonNull Object value) {
        String redisKey = this.buildKey(key);
        CACHE.put(redisKey, new CacheValue<>(value, duration.toNanos()));
    }
    /**
     * 移除数据
     */
    public void del(@NonNull String key) {
        String redisKey = this.buildKey(key);
        CACHE.invalidate(redisKey);
    }
    /**
     * 公共业务代码处理
     */
    private <T> @Nullable T getT(@NotNull String key, @NotNull Class<T> clazz, CacheValue<Object> wrapper) {
        if (wrapper == null || wrapper.value() == null) {
            return null;
        }
        Object value = wrapper.value();
        // 已经是目标类型
        if (clazz.isInstance(value)) {
            return clazz.cast(value);
        }
        return getSerialize(key, value, strValue -> objectMapper.convertValue(strValue, clazz));
    }
    /**
     * 序列化操作
     */
    private <T> @Nullable T getSerialize(@NotNull String key, @NotNull Object object, Function<Object, T> deserialize) {
        try {
            return deserialize.apply(object);
        } catch (Exception e) {
            log.error("本地缓存序列化失败,KEY:{}", key, e);
            throw new RuntimeException(e);
        }
    }
    /**
     * Redis的KEY拼接
     */
    private String buildKey(String key) {
        return "test:cache:" + key;
    }
    /**
     * 存储数据和过期时间
     *
     * @param value    内容
     * @param ttlNanos 过期时间
     */
    public record CacheValue<T>(T value, long ttlNanos) {
    }
}
// 使用方式
public static void main(String[] args) {
    private static final TypeReference<List<String>> LIST_TEST = new TypeReference<>() {
    };
    @Resource
    private LocalCacheTemplate localCacheTemplate;
    List<String> strings = localCacheTemplate.get(RedisKey.TEST_LIST, Duration.ofMinutes(60), LIST_TEST, () -> {
        System.out.println("TEST");
        return List.of("Test");
    });
}

3️⃣复合缓存(Composite Cache / Two-level Cache)

概念
复合缓存结合了 LocalCache + RedisCache,常见模式是:

优点

缺点

import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.NonNull;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.function.Supplier;
/**
 * 本地缓存和Redis缓存实现复合缓存工具类
 */
@Slf4j
@Component
public class CompoundCacheTemplate {
    // 默认10分钟
    public static Duration duration = Duration.ofMinutes(10);
    @Resource
    private RedisCacheTemplate redisCacheTemplate;
    @Resource
    private LocalCacheTemplate localCacheTemplate;
    /**
     * 获取KEY数据
     */
    public <T> T get(@NonNull String key, @NonNull Class<T> clazz) {
        // 先查本地缓存
        T value = localCacheTemplate.get(key, clazz);
        if (value != null) {
            return value;
        }
        // 再查Redis
        value = redisCacheTemplate.get(key, duration, clazz, () -> null);
        if (value != null) {
            // 回写本地缓存
            localCacheTemplate.set(key, Duration.ofMinutes(10), value);
        }
        return value;
    }
    /**
     * Class类型获取数据,没有就写入
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull Class<T> clazz, @NonNull Supplier<T> supplier) {
        // 先查本地缓存
        T value = localCacheTemplate.get(key, clazz);
        if (value != null) {
            return value;
        }
        // 查Redis,如果没有就调用业务逻辑
        value = redisCacheTemplate.get(key, duration, clazz, supplier);
        if (value != null) {
            // 回写本地缓存
            localCacheTemplate.set(key, duration, value);
        }
        return value;
    }
    /**
     * 指定TypeReference类型获取数据,没有就写入
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull TypeReference<T> clazz, @NonNull Supplier<T> supplier) {
        // 先查本地缓存
        T value = localCacheTemplate.get(key, duration, clazz, () -> null);
        if (value != null) {
            return value;
        }
        // 查Redis,如果没有就调用业务逻辑
        value = redisCacheTemplate.get(key, duration, clazz, supplier);
        if (value != null) {
            // 回写本地缓存
            localCacheTemplate.set(key, duration, value);
        }
        return value;
    }
    /**
     * 写入数据
     */
    public void set(@NonNull String key, @NonNull Duration duration, @NonNull Object value) {
        // 同步写入本地缓存和Redis
        localCacheTemplate.set(key, duration, value);
        redisCacheTemplate.set(key, duration, value);
    }
    /**
     * 移除数据
     */
    public void del(@NonNull String key) {
        // 同步删除本地缓存和Redis
        localCacheTemplate.del(key);
        redisCacheTemplate.del(key);
    }
}
// 使用方式
public static void main(String[] args) {
    private static final TypeReference<List<String>> LIST_TEST = new TypeReference<>() {
    };
    @Resource
    private CompoundCacheTemplate compoundCacheTemplate;
    List<String> strings = compoundCacheTemplate.get(RedisKey.TEST_LIST, Duration.ofMinutes(60), LIST_TEST, () -> {
        System.out.println("TEST");
        return List.of("Test");
    });
}

到此这篇关于JAVA缓存的使用RedisCache、LocalCache、复合缓存的操作的文章就介绍到这了,更多相关java rediscache localcache 复合缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文