Spring Cache 多级缓存中 ZSet 类型 Redis 缓存的自定义实现与核心功能
需求背景:spring cache 使用多级缓存,缓存实现方式用redis时,默认存储的结构是string,结果,但项目中需要使用其他结果,于是想到自己定义一个实现类,然后根据类型进行切换不同的实现方式,于是乎有了下面的zset自定义实现代码
import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionUtils;
import org.springframework.data.redis.connection.Tuple;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;/*** 精简版 ZSet 缓存(仅保留核心方法)* 功能:存储 List 元素、读取所有元素、清空缓存、获取元素数量*/
public class ZSetRedisCache extends RedisCache {// 匹配后缀的正则(仅在允许重复时使用)private static final Pattern SUFFIX_PATTERN = Pattern.compile("_.+?_\\d+$");// 元素序列化器(纯 JSON)private static final RedisSerializer<Object> ELEMENT_SERIALIZER = new FastJsonRedisSerializer<>();private final Duration ttl;private final RedisConnectionFactory connectionFactory;private final boolean allowDuplicate;// 仅保留必要的构造器参数public ZSetRedisCache(String name,RedisCacheWriter cacheWriter,RedisCacheConfiguration config,RedisConnectionFactory connectionFactory,boolean allowDuplicate) {super(name, cacheWriter, config);this.ttl = config.getTtl();this.connectionFactory = connectionFactory;this.allowDuplicate = allowDuplicate;}/*** 核心:存储 List 元素(批量添加到 ZSet)* 入参:value 必须是 List 类型,key 可忽略*/@Overridepublic void put(Object key, Object value) {if (value == null || !(value instanceof List<?>)) {return;}List<?> dataList = (List<?>) value;if (dataList.isEmpty()) {return;}RedisConnection connection = getRedisConnection();try {byte[] zsetKey = getNameBytes();if (zsetKey == null) {return;}// 序列化元素为 byte[]List<byte[]> memberBytesList = dataList.stream().map(ELEMENT_SERIALIZER::serialize).filter(Objects::nonNull).collect(Collectors.toList());if (memberBytesList.isEmpty()) {return;}// 处理重复元素(添加唯一后缀)if (allowDuplicate) {memberBytesList = addUniqueSuffix(memberBytesList);}// 构建 Tuple 集合(适配底层方法)Set<Tuple> tuples = memberBytesList.stream().map(bytes -> new DefaultTuple(bytes, 0.0)).collect(Collectors.toSet());// 批量添加到 ZSetconnection.zAdd(zsetKey, tuples);// 设置过期时间if (!ttl.isNegative()) {connection.expire(zsetKey, ttl.getSeconds());}} finally {releaseRedisConnection(connection);}}/*** 核心:读取所有元素(自动处理重复元素的后缀)*/public List<Object> getAllElements() {RedisConnection connection = getRedisConnection();try {byte[] zsetKey = getNameBytes();if (zsetKey == null) {return Collections.emptyList();}// 获取所有元素Set<Tuple> tuples = connection.zRangeWithScores(zsetKey, 0, -1);if (tuples.isEmpty()) {return Collections.emptyList();}// 反序列化并处理后缀return tuples.stream().map(Tuple::getValue).filter(Objects::nonNull).map(bytes -> {String elementStr = new String(bytes, StandardCharsets.UTF_8);// 去除重复元素的后缀if (allowDuplicate) {elementStr = SUFFIX_PATTERN.matcher(elementStr).replaceAll("");}return ELEMENT_SERIALIZER.deserialize(elementStr.getBytes(StandardCharsets.UTF_8));}).filter(Objects::nonNull).collect(Collectors.toList());} finally {releaseRedisConnection(connection);}}/*** 核心:获取元素总数量*/public Long getTotalCount() {RedisConnection connection = getRedisConnection();try {byte[] zsetKey = getNameBytes();return zsetKey != null ? connection.zCard(zsetKey) : 0L;} finally {releaseRedisConnection(connection);}}/*** 核心:清空缓存*/@Overridepublic void clear() {RedisConnection connection = getRedisConnection();try {byte[] zsetKey = getNameBytes();if (zsetKey != null) {connection.del(zsetKey);}} finally {releaseRedisConnection(connection);}}// -------------------------- 仅保留必要的工具方法 --------------------------/*** 为重复元素添加唯一后缀(内部使用)*/private List<byte[]> addUniqueSuffix(List<byte[]> memberBytesList) {List<byte[]> uniqueMembers = new ArrayList<>();String suffix = "_" + System.currentTimeMillis() + "_";for (int i = 0; i < memberBytesList.size(); i++) {byte[] origin = memberBytesList.get(i);byte[] suffixBytes = (suffix + i).getBytes(StandardCharsets.UTF_8);byte[] unique = Arrays.copyOf(origin, origin.length + suffixBytes.length);System.arraycopy(suffixBytes, 0, unique, origin.length, suffixBytes.length);uniqueMembers.add(unique);}return uniqueMembers;}/*** 缓存名序列化(内部使用)*/private byte[] getNameBytes() {return getName().getBytes(StandardCharsets.UTF_8);}/*** Redis 连接管理(内部使用)*/private RedisConnection getRedisConnection() {return RedisConnectionUtils.getConnection(connectionFactory);}private void releaseRedisConnection(RedisConnection connection) {RedisConnectionUtils.releaseConnection(connection, connectionFactory);}// -------------------------- 必要的内部类 --------------------------/*** 序列化器(内部使用)*/private static class FastJsonRedisSerializer<T> implements RedisSerializer<T> {@Overridepublic byte[] serialize(T t) throws SerializationException {if (t == null) {return new byte[0];}return JSON.toJSONString(t).getBytes(StandardCharsets.UTF_8);}@Overridepublic T deserialize(byte[] bytes) throws SerializationException {if (bytes == null || bytes.length == 0) {return null;}return (T) JSON.parse(bytes);}}/*** Tuple 实现(适配底层方法,内部使用)*/private static class DefaultTuple implements Tuple {private final byte[] value;private final double score;public DefaultTuple(byte[] value, double score) {this.value = value;this.score = score;}@Overridepublic byte[] getValue() {return value;}@Overridepublic Double getScore() {return score;}}
}