今日指数项目集成多线程采集数据注意事项

今日指数项目集成多线程采集数据注意事项

一. 配置线程池

配置参数

# 定时任务线程池基础参数
task:
  pool:
    corePoolSize: 5 # 核心线程数
    maxPoolSize: 20 # 设置最大线程数
    keepAliveSeconds: 300 # 设置线程活跃时间,单位秒
    queueCapacity: 100 # 设置队列容量

配置实体类提取参数

@ConfigurationProperties(prefix = "task.pool")
@Data
public class TaskThreadPoolInfo {
    /**
     *  核心线程数(获取硬件):线程池创建时候初始化的线程数
     */

    private Integer corePoolSize;
    private Integer maxPoolSize;
    private Integer keepAliveSeconds;
    private Integer queueCapacity;
}

核心配置类

@Configuration
public class TaskExecutePoolConfig {

    @Autowired
    private TaskThreadPoolInfo poolSource;

    @Bean(name = "threadPoolTaskExecutor" , destroyMethod = "shutdown")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 配置核心线程数
        executor.setCorePoolSize(poolSource.getCorePoolSize());
        // 配置最大线程数
        executor.setMaxPoolSize(poolSource.getMaxPoolSize());
        // 配置队列数
        executor.setQueueCapacity(poolSource.getQueueCapacity());
        // 配置活跃时间
        executor.setKeepAliveSeconds(poolSource.getKeepAliveSeconds());
        // 初始化
        executor.initialize();
        return executor;
    }
}

二. 思路分析

为什么要集成多线程在项目中已有说明 , 这里就不多赘述 , 讲一下集成多线程有哪些注意事项

首先在原方案所采取的策略

在这里插入图片描述

在这里循环获取每个步长的数据后直接执行插入数据库的操作

可能的问题

  1. 数据库连接竞争

    • 如果多个线程同时尝试访问数据库(尤其是进行写操作),可能会导致数据库连接池中的连接被迅速耗尽,进而影响应用的性能甚至导致错误。
  2. 数据一致性问题

    • 如果多个线程同时向数据库写入数据,且这些操作之间存在依赖关系或需要维护某种顺序,可能会导致数据不一致。虽然您这里看起来是批量插入独立的数据集,但如果有其他事务性操作依赖这些插入的结果,则可能出现问题。
  3. 事务隔离级别

    • 并发写入时,不同的数据库事务隔离级别(如读未提交、读已提交、可重复读、序列化)会影响数据的可见性和一致性。
  4. 死锁

    • 如果数据库表有复杂的索引或外键约束,多个线程同时尝试写入数据可能会导致死锁。
  5. 无法获取数据是否插入成功的信息

这里我提供一种解决方案 , 就是通过爬虫获取所有数据 , 将这些数据封装进一个集合中 ,再集中存处

三. 代码实现

在这里也会有另外一些问题

  1. 用什么类型的集合存储数据 ?
    • 这里因为采用的是多线程 , 集合必然要使用线程安全性的 , 当然也可以用不安全的通过使用锁进行保护也可以 , 但是这会降低效率
  2. 如何判断所有线程全部执行完毕 , 再进行下一步处理
    • 使用CountDownLatch 对代码进行同步
public void getStockRtIndex() {
        // 将股票编码信息保存到Redis
        List<List<String>> partition = (List<List<String>>) redisTemplate.opsForValue().get("Stock_Codes");
        if (partition == null) {
            List<String> allCodes = stockBusinessMapper.getStockCodes();
            allCodes = allCodes.stream().map(item -> item.startsWith("6") ? StockConstant.SHSTOCK + item : StockConstant.SZSTOCK + item).collect(Collectors.toList());
            partition = Lists.partition(allCodes, 10);
            redisTemplate.opsForValue().set("Stock_Codes", partition, 1, TimeUnit.DAYS);
        }
        ConcurrentLinkedQueue<StockRtInfo> dataSource = new ConcurrentLinkedQueue<>();
        CountDownLatch latch = new CountDownLatch(partition.size());


        partition.forEach(item -> {
            threadPoolTaskExecutor.execute(() -> {
                try {
                    String url = stockInfoConfig.getMarketUrl() + String.join(",", item);

                    //2.3 resetTemplate发起请求
                    ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
                    int statusCodeValue = exchange.getStatusCodeValue();
                    if (statusCodeValue != 200) {
                        log.error("当前时间点{} , 数据采集失败 , 状态码{}", DateTime.now().toString("yyyy-MM-dd HH-mm-ss"), statusCodeValue);
                        latch.countDown(); // 即使失败也要计数减一
                        return;
                    }
                    String jsData = exchange.getBody();

                    List<StockRtInfo> list = parserStockInfoUtil.parser4StockOrMarketInfo(jsData, ParseType.ASHARE);
                    log.info("采集个股数据: {}", list);
                    list.forEach(dataSource::add);
                }finally {
                    latch.countDown();
                }

            });

        });

        try {
            latch.await(); // 等待所有线程完成
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 处理中断异常
            log.error("数据采集有误 , 线程等待被中断{}", e);

        }

        List<StockRtInfo> finalDataSource  = dataSource.stream().collect(Collectors.toList());

        int num = stockRtInfoMapper.insertSelfStock(finalDataSource );

        if (num > 0) {
            log.info("当前时间点{} , 个股数据插入成功", DateTime.now().toString("yyyy-MM-dd HH-mm-ss"));
            rabbitTemplate.convertAndSend("innerMarketExchange" , "stock.inner" , new Date());
        } else {
            log.error("当前时间点{} , 个股数据插入失败", DateTime.now().toString("yyyy-MM-dd HH-mm-ss"));


        }


    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

攒了一袋星辰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值