记一次Redis缓存组件开发
背景
IM 系统需要高效存储和管理海量聊天数据,并通过 Redis 进行缓存,同时设定过期时间。为了确保业务连续性,当查询某个会话的历史消息时,如果数据在 Redis 中已过期,则需自动从持久化存储中加载。缓存组件需支持 String、Set、SortedSet 至少三种 Redis 数据结构,以匹配不同的 IM 业务需求,并具备智能加载和数据同步能力。
部分源码展示
/**
* SortSet 数据结构 缓存策略处理类
* @param <K>
* @param <V>
* @param <V2>
*/
public class SortSetCache<K extends String, V, V2> extends AbstractRedisCache<K, V> implements SetQueryCache<K, V2>, SaveCache<K, V> {
/**
*
*/
Class<V2> v2Class;
private SortSetQueryCondition sortSetQueryCondition;
SortSetCache(RedisCache redisCache, RedisService redisService, Class<V2> v2Class, SortSetQueryCondition condition) {
super(redisCache, redisService);
this.v2Class = v2Class;
sortSetQueryCondition = condition;
}
@Override
public Set<V2> get(K key) {
Set<V2> set = new HashSet<>();
Direction direction = sortSetQueryCondition.getDirection();
long count = sortSetQueryCondition.getCount();
if (direction == Direction.ASC) {
//顺序查找
double max = sortSetQueryCondition.getReference() + count + 1;
set = redisService.zSetRangeByScore(key, sortSetQueryCondition.getReference() + 1, Double.MAX_VALUE, sortSetQueryCondition.getCount());
} else {
//倒序查找
double min = sortSetQueryCondition.getReference() - count - 1;
min = min < 0 ? 0 : min;
set = redisService.zSetReverseRangeByScore(key, Double.MIN_VALUE, sortSetQueryCondition.getReference() - 1, sortSetQueryCondition.getCount());
}
if (CollectionUtil.isNotEmpty(set)) {
Object o = set.stream().findFirst().orElse(null);
if (isPrimitiveOrWrapper(o) && !isPrimitiveOrWrapperClass(v2Class)) {
//常规类型 需要转换
set = set.stream().map(item -> {
V2 v2 = null;
try {
v2 = v2Class.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
setRealValue(v2Class, v2, transformRealKey(key), RedisKey.class);
setRealValue(v2Class, v2, item, RealValue.class);
return v2;
}).collect(Collectors.toSet());
}
}
return set;
}
@Override
public boolean expire(K key, long timeout, TimeUnit unit) {
return super.expire(key, timeout, unit);
}
@Override
public List<K> getValidKey(Collection<K> keys) {
return super.getValidKey(keys);
}
@Override
public void save(K key, V value) {
if (isPrimitiveOrWrapper(value)) {
throw new RuntimeException(" SortSetCache save V 返回值不允许基本数据类型");
}
Object sort = getRealValue(value, RedisSort.class);
Object realVale = getRealValue(value, RealValue.class);
if (realVale == null) {
realVale = value;
}
redisService.zSetScore(key, realVale, Double.valueOf(sort.toString()));
}
@Override
public void saveBatch(Collection<K> paramKeys, Collection<V> paramValues) {
//
if (CollectionUtil.isEmpty(paramKeys) || CollectionUtil.isEmpty(paramValues)) {
return;
}
List<K> keys = collectonToList(paramKeys);
V v = paramValues.stream().findFirst().orElse(null);
if (isPrimitiveOrWrapper(v)) {
//常规数据类型
throw new RuntimeException("SortSetCache saveBatch 不允许存储常规数据类型(无法指定排序值) ");
} else {
//
Set<V> values = collectonToSet(paramValues);
try {
Map<K, Set<V>> kListMap = getSetGroupByKey(values);
Map<K, Set<V>> finalKListMap = kListMap;
kListMap = keys.stream()
// 过滤出 keys 中在 tempKvMap 的键中存在对应关系的元素
.filter(key -> finalKListMap.containsKey(transformRealKey(key)))
// 将符合条件的 keys 转换成新 Map
.collect(Collectors.toMap(
key -> key, // 保留原始 key 作为新 Map 的键
key -> finalKListMap.get(transformRealKey(key)) // 使用转换后的 key 获取值
));
kListMap.forEach((key, valueList) -> {
Set<ZSetOperations.TypedTuple<Object>> collect = valueList.stream().map(item -> {
Object value = getRealValue(item, RedisSort.class);
Double score = Double.valueOf(value.toString());
return new ZSetOperations.TypedTuple<Object>() {
@Override
public int compareTo(ZSetOperations.TypedTuple<Object> o) {
return 0;
}
@Override
public Object getValue() {
Object realValue = getRealValue(item, RealValue.class);
if (realValue == null) {
return item;
} else {
return realValue;
}
}
@Override
public Double getScore() {
return score;
}
};
})
.collect(Collectors.toSet());
redisService.zMultiSetScore(key, collect);
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
Redis 缓存应用场景
//String 类型的缓存
String MESSAGE_CONTENT = "im:message:{sessionId}:{messageId}";
@RedisCache(cacheKey = RedisConstant.MESSAGE_CONTENT)
public List<MessageVo> selectMessageVoByMessageIds(@RedisKey(key = "sessionId") List<Long> sessionIds, @RedisKey(key = "messageId", isValidKey = true) List<Long> messageIds) {
//查询逻辑
}
//SortSet类型的缓存
@RedisCache(cacheKey = RedisConstant.MESSAGE_SORT_SET, cacheType = CacheType.SORTED_SET)
public List<MessageSortDto> selectMessageIdsBySessionId(PullMessageCondition messageCondition) {
//MySQL 数据库查询逻辑
}
//Set类型的缓存
@RedisCache(cacheKey = "im:permission", cacheType = CacheType.SET)
public List<String> selectTestPermission(Long sessionId) {
//查询逻辑
}
部分源码截图
总结:
该Redis公共组件提供了注解式开发能力,业务代码不用关注缓存是否失效,简化了开发流程。