基于ShardingJDBC+Redis的分布式高并发批量查询提速思路

思路:用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));
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值