记录一次分段数据多线程优化过程

针对MongoDB大SQL查询效率低下问题,采用预存数据到Redis并分段存储的方法。通过多线程异步读取Redis中的分段数据,利用CountDownLatch进行同步,提高数据获取速度。同时设置超时避免长时间等待,确保系统稳定性。
摘要由CSDN通过智能技术生成

背景描述

在项目中开发一个接口,这个接口是一个大sql查询,数据库使用的是mongodb,一次性查询5千条以上的数据,这就带来了一个问题,就是查询效率的问题,会很慢,要等5秒以上才能等到数据,尽管对关键字段都做了索引,还是很慢。也许你会说不要一次性查这么多数据,搞个分页不就行了吗?外卖这种情况还真不行,得把所有数据拿到才可以进行计算。
为了提高查询效率,就想到要么把数据预先存到redis里面去,到时候直接读取redis不就快了吗?我们真这样做了,结果发现redis读取这种大key的时候发现依然比较慢,那么怎么办?

终极解决办法:

分段数据保存,多线程读取

思想:预先将大sql查询出来的数据拆分成多个小集合,缓存在redis里面,在查询的时候使用多线程异步去读取这几个缓存在redis里面的集合数据,再将读取到的数据组装成一个新的大集合,如此大大提高了数据读取效率,那么问题来了,在使用多线程多去redis里面的数据的时候,如何判断所有线程已经将数据都已经取完了呢?我这边做了一个自旋判断,只有将读取到数据数量等于之前缓存到redis中的数量,那么就认为数据已经取完了,否则就一直等知道所有线程都读取完,当然,在遇到网络不好的情况下,可能遇到redis读取很慢,那我们也不能一直自旋等待下去,就需要设置一个等待超时时间。不多说了,上代码:

1、把查询出来的数据缓存到redis
            Criteria criteria = Criteria.where("code").is(code).and("time").lte(endDate);
            Query stockQuery = new Query(criteria);
            stockQuery.with(Sort.by(Sort.Order.asc("time")));
            stockQuery.fields().exclude("id", "turnoverRatio", "amount", "change", "changeRatio");//id
            myStockPools = mongoTemplate.find(stockQuery, MyStockPool.class, "myStockPool");
            int groupNum = (int) Math.ceil((double) myStockPools.size() / StockConstant.PAGE_SIZE_D_800);
            redisTemplate.opsForValue().set(StockConstant.CODE_GROUP_NUM + code, String.valueOf(groupNum));
            if (groupNum > StockConstant.ONE) {
                //拆成多个组,使用多线程去去读取数据
                for (int i = 1; i <= groupNum; i++) {
                    List<MyStockPool> reslist = pageBySubList(myStockPools, StockConstant.PAGE_SIZE_800, i);
                    redisTemplate.opsForValue().set(StockConstant.CODE + code + i, JSON.toJSONString(reslist));
                    log.info("reslist size:{}", reslist.size());
                }
                log.info("mappedResults end:{}", LocalDateTime.now());
            }
2、使用异步多线程读取redis分段数据
public List<MyStockPool> mongoQuotaData(String endDate, String code) {
        //保证集合线程的安全性
        List<MyStockPool> myStockPools = Collections.synchronizedList(new ArrayList<>());
        String codeGroupNum = redisTemplate.opsForValue().get(StockConstant.CODE_GROUP_NUM + code);
        if (StringUtils.hasText(codeGroupNum)) {
            int goupNumRedis = Integer.parseInt(codeGroupNum);
            CompletableFuture<Void>[] jasc = new CompletableFuture[goupNumRedis];
            log.info("redisList start:{}", LocalDateTime.now());
            CountDownLatch countDownLatch = new CountDownLatch(goupNumRedis);
            for (int i = 0; i < goupNumRedis; i++) {
                String key = StockConstant.CODE + code + (i + 1);
                //异步调用
                List<MyStockPool> finalMyStockPools = myStockPools;
                jasc[i] = CompletableFuture.runAsync(() -> {
                    try {
                        String str = redisTemplate.opsForValue().get(key);
                        if (StringUtils.hasText(str)) {
                            List<MyStockPool> redisMyStockPools = JSON.parseArray(str, MyStockPool.class);
                            log.info("redismyStockPools size:{}", redisMyStockPools.size());
                            if (CollectionUtil.isNotEmpty(redisMyStockPools)) {
                                finalMyStockPools.addAll(redisMyStockPools);
                            }
                        }
                    } finally {
                        //当前线程执行完成之后减一
                        countDownLatch.countDown();
                    }
                }, executor);
            }

            log.info("redisList start:{}", LocalDateTime.now());
            try {
                //阻塞等待所有的线程都执行完成才会进行下一步操作,如果超过5秒还没有完成就不等了
                countDownLatch.await(5, TimeUnit.SECONDS);
                log.info("countDownLatch完成了:{}", countDownLatch.getCount());
                myStockPools = myStockPools.stream().sorted(Comparator.comparing(MyStockPool::getTime)).collect(Collectors.toList());
                AtomicInteger index = new AtomicInteger(0);
                myStockPools.stream().filter(s -> {
                    //每比对一个元素,数值加1
                    index.getAndIncrement();
                    return s.getTime().equals(endDate);
                }).findFirst();
                int end = index.get();
                //截取该日期之前的数据
                myStockPools = myStockPools.subList(0, end);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                executor.shutdown();
            }
        }
        return myStockPools;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

量化接口stockapi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值