思路:用multiGet批量获取缓存数据,但是有一个问题,multiGet指令对缓存中没有的数据会插入null值,因此查完需要过滤掉null值。然后对不在缓存中的数据,进行Mysql查询,采用多线程+本地内存聚合方式。查完后把数据写入缓存。
@Service
public class UserServiceImpl implements IUserService {
@Resource
private RedisTemplate<String, UserDTO> redisTemplate;
@Resource
private UserProviderCacheKeyBuilder userProviderCacheKeyBuilder;
@Resource
private IUserMapper userMapper;
@Override
public Map<Long, UserDTO> batchQueryUserInfo(List<Long> userIdList) {
// 1.检查参数是否合法
// 若为空则直接返回
if(!CollectionUtils.isEmpty(userIdList)){
return new HashMap<>();
}
// 方案1(废弃):拿到id列表直接从userIdList循环获取每一个id,分别请求redis
// 这样的话若要查1000~1100的id批量查询则需要100次网络IO请求,性能差
// 方案2(采用):利用multiGet(),它允许一次传批量参数,只需要一次网络IO就可以将这一批参数查询结果返回
List<String> keyList = new ArrayList<>();
userIdList.forEach(userId -> {
keyList.add(userProviderCacheKeyBuilder.buildUserInfoKey(userId));
});
// 通过multiGet接口直接批量查询,一次网络IO,但是multiGet有一个问题
// 若缓存中有id没有查到相应数据,那么它会给这个id对应数据置为null,因此要进行一次过滤,取值不为null的数据
List<UserDTO> userDTOList = redisTemplate.opsForValue().multiGet(keyList).stream().filter(x -> x != null).collect(Collectors.toList());
// 若查得缓存中的数据个数与要查得id个数一致,说明缓存全部命中,则直接返回
if(!CollectionUtils.isEmpty(userDTOList) && userDTOList.size() == userIdList.size()){
// 需要转成 <Long, UserDTO>
return userDTOList.stream().collect(Collectors.toMap(UserDTO::getUserId, x -> x));
}
// 取出userId对象,这样userIdInCacheList就是存在于缓存当中的userId集合
List<Long> userIdInCacheList = userDTOList.stream().map(UserDTO::getUserId).collect(Collectors.toList());
// 把没有在缓存当中的userId取出,这些Id需要从数据库来进行查询
List<Long> userNotInCacheList = userIdList.stream().filter(x -> !userIdInCacheList.contains(x)).collect(Collectors.toList());
// 对缓存未命中的数据查MySQL
// 采用多线程 + 本地内存聚合的方式去做分表的数据查询
// 将不同表的id分成不同的list交给不同的线程进行查询,最后再在本地内存做归并
// 思路:多线程查询替换了union all情况
Map<Long, List<Long>> userIdMap = userNotInCacheList.stream().collect(Collectors.groupingBy(userId -> userId % 100));
List<UserDTO> dbQueryResult = new CopyOnWriteArrayList<>();
userIdMap.values().parallelStream().forEach(queryUserIdList -> {
dbQueryResult.addAll(ConvertBeanUtils.convertList(userMapper.selectBatchIds(queryUserIdList), UserDTO.class));
});
// 写入缓存
if(!CollectionUtils.isEmpty(dbQueryResult)){
Map<String, UserDTO> saveCacheMap = dbQueryResult.stream().collect(Collectors.toMap(userDto -> userProviderCacheKeyBuilder.buildUserInfoKey(userDto.getUserId()), x -> x));
redisTemplate.opsForValue().multiSet(saveCacheMap);
userDTOList.addAll(dbQueryResult);
}
return userDTOList.stream().collect(Collectors.toMap(UserDTO::getUserId, x -> x));
}
}