导入利用布隆BloomFilter

在这里插入图片描述

package com.youlai.boot.system.utils;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import com.youlai.boot.common.util.ExcelUtilsA;
import com.youlai.boot.system.mapper.PhoneMapper;
import com.youlai.boot.system.model.dto.PhoneImportDTO;
import com.youlai.boot.system.model.entity.Phone;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Component
public class UploadExcel {
    @Resource
    private PhoneMapper phoneMapper;
    @Resource
    private JdbcTemplate jdbcTemplate;

    // 配置参数
    private static final int BATCH_SIZE = 10000;
    private static final int THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;
    // 使用线程安全的计数器
    private static final AtomicInteger totalProcessed = new AtomicInteger(0);
    private static final AtomicInteger duplicateCount = new AtomicInteger(0);

    public List<String> upLoad(MultipartFile file, String name) {
        List<String> listCf = new ArrayList<>();
        long startTime = System.currentTimeMillis();
        try {
            System.out.println("(1) :");
            listCf.addAll(importPhoneNumbers(file,name)) ;
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("导入完成,耗时: " + (endTime - startTime) / 1000 + "秒");
        System.out.println("处理总数: " + totalProcessed.get() + ", 重复数: " + duplicateCount.get());
        return listCf;
    }

    public List<String> importPhoneNumbers(MultipartFile file,String name) throws Exception {
        List<String> listCf =new ArrayList<>();
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        // 使用CompletionService管理任务结果
        CompletionService<List<String>> completionService =
                new ExecutorCompletionService<>(executor);
        List<PhoneImportDTO> phoneNumbers =  ExcelUtilsA.read(file,PhoneImportDTO.class);
        if(!phoneNumbers.isEmpty()){
            try {
                BloomFilter<String> yuan =  yuan();
                List<String> list =phoneNumbers.stream().map(PhoneImportDTO::getId).toList();
                int totalRows = list.size();
                // 分片处理Excel,每个任务处理一部分行
                for (int start = 1; start < totalRows; start += BATCH_SIZE) {
                    int end = Math.min(start + BATCH_SIZE, totalRows);
                    completionService.submit(new ExcelProcessor(list, start, end));
                }
                // 创建布隆过滤器 (实际生产环境应使用分布式布隆过滤器如RedisBloom)
                BloomFilter<String> bloomFilter = BloomFilter.create(
                        Funnels.stringFunnel(Charset.defaultCharset()),
                        6_000_000,
                        0.001);
                int ci = 0;
                for (int i = 0; i < (totalRows + BATCH_SIZE - 1) / BATCH_SIZE; i++) {
                    // 格式化当前cell对应数据为String
                    List<Phone> uniqueNumbers = new ArrayList<>();
                    List<String> batch = completionService.take().get();
                    long startTime = System.currentTimeMillis();
                    listCf.addAll(processBatch(batch, bloomFilter,name,yuan,uniqueNumbers));
                    long entTime = System.currentTimeMillis();
                    System.out.println("数据筛选+分段封装耗时: " + (entTime - startTime) + " 毫秒");
                    // 批量插入数据库
                    if (!uniqueNumbers.isEmpty()) {
                        long s = System.currentTimeMillis();
                        ci++;
                        asynchronous(uniqueNumbers);
                        long e = System.currentTimeMillis();
                        System.out.println("数据插入表数据 第"+ci+"次  耗时: " + ( e - s) + " 毫秒");
//                        batchInsertToDatabase(uniqueNumbers);
                    }
                }
            } finally {
                executor.shutdown();
                executor.awaitTermination(1, TimeUnit.HOURS);
            }
        }
        // 关闭线程池
        executor.shutdown();
        return listCf;
    }
//    cash/applyOutOfflineCash
    private List<String> processBatch(List<String> phoneNumbers, BloomFilter<String> bloomFilter,String name,BloomFilter<String> yuan,List<Phone> uniqueNumbers) {
        List<String> listCf = new ArrayList<>();
        LocalDateTime time = LocalDateTime.now();
        for (String phone : phoneNumbers) {
            if (!bloomFilter.mightContain(phone)) {
                if(!yuan.mightContain(phone)){
                    Phone s = new Phone();
                    bloomFilter.put(phone);
                    s.setCreateTime(time);
                    s.setId(phone);
                    s.setName(name);
                    uniqueNumbers.add(s);
                    yuan.put(phone);
                }else {
                    // 重复手机号
                    listCf.add(phone);
                }
            } else {
                duplicateCount.incrementAndGet();
            }
            totalProcessed.incrementAndGet();
        }
        return listCf;
    }

    // 异步插入
    private void asynchronous(List<Phone> uniqueNumbers){
        CompletableFuture.runAsync(() -> {
            batchInsertToDatabase(uniqueNumbers);
            System.out.println("Task running asynchronously");
        });
    }
    /**
     * JDBC 批量插入或JPA批量处理
     * @param phoneNumbers
     */
    private void batchInsertToDatabase(List<Phone> phoneNumbers) {
        // 使用JDBC批量插入或JPA批量处理
        // 这里简化为伪代码
        if(!phoneNumbers.isEmpty()){
            phoneMapper.saveAll(phoneNumbers);
        }
    }

    /**
     * 获取表中全部数据 大数据表分段查询
     * @return
     */
    private BloomFilter<String> yuan(){
        BloomFilter<String> yuanBloomFilter = BloomFilter.create(
                Funnels.stringFunnel(Charset.defaultCharset()),
                6_000_000_0,
                0.001);
        // 获取表信息策略
        List<String> listAll = geng(yuanBloomFilter.approximateElementCount());
        if(!listAll.isEmpty()){
            // 处理每个数据项的异步任务
            List<CompletableFuture<Void>> futures = listAll.stream()
                    .map(data -> CompletableFuture.runAsync(() -> {
                        if(!yuanBloomFilter.mightContain(data)){
                            yuanBloomFilter.put(data);
                        }
                    }))
                    .collect(Collectors.toList());
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); // 等待所有任务完成
        }
        return yuanBloomFilter;
    }

    // 更新策略
    private List<String> geng(long yuanBloomFilterCount){
        long startTime = System.currentTimeMillis();

        List<String> listAll = new ArrayList<>();
        long countAll = phoneMapper.countAll();
        if(yuanBloomFilterCount<countAll){
            int countLimit = 1000;
            if(checkOddEven((int)countAll)){
               countLimit = (int)countAll/20+20;
            }else {
              countLimit =(int)countAll/20+20;
            }

            if (countAll<500000){
                listAll.addAll(phoneMapper.phoneIdList());
            }else {
                int offset = 0;
                int limit = countLimit;
                while (true) {
                    List<String> records =new ArrayList<>();
//                    List<String> records = jdbcTemplate.queryForList("SELECT id FROM phone LIMIT " + limit + " OFFSET " + offset, String.class);
                    yibuList(offset,limit,records);
                    if (records.isEmpty()) break;
                    listAll.addAll(records);
                    // 处理records
                    offset += limit;
                }
            }
        }
        long entTime = System.currentTimeMillis();
        System.out.println("读取原数据耗时: " + (entTime - startTime) + " 毫秒");
        return listAll;
    }
    // 异步读取多线程读取 非阻
    private void yibuList(int offset,int limit,List<String> listAll){
        CompletableFuture.runAsync(() -> {
            listAll.addAll(jdbcTemplate.queryForList("SELECT id FROM phone LIMIT " + limit + " OFFSET " + offset, String.class));
            System.out.println("Task running asynchronously");
        });
    }

    // 取模5数判断
    public boolean checkOddEven(int number) {
        if (number % 2 == 0) {
            return true;
        }
        return false;
    }

    // 造数据
    public void zao(){
        LocalDateTime l =  LocalDateTime.now();
        Long yua =  18645550314L;
        for(int i=0; i<100; i++){
            List<Phone> phoneNumbers = new ArrayList<>();
            for(int j = 0; j<50000; j++){
                yua++;
                Phone p = new Phone();
                p.setId(yua.toString());
                p.setName("测试");
                p.setCreateTime(l);
                phoneNumbers.add(p);
            }
//            batchInsertToDatabase(phoneNumbers);
        }
    }


    static class ExcelProcessor implements Callable<List<String>> {
        private final List<String> phoneNumbers;
        private final int startRow;
        private final int endRow;
        public ExcelProcessor(List<String> phoneNumbers, int startRow, int endRow) {
            this.phoneNumbers = phoneNumbers;
            this.startRow = startRow;
            this.endRow = endRow;
        }
        @Override
        public List<String> call() {
            List<String> numbers = new ArrayList<>();
            for (int i = startRow; i < endRow; i++) {
                String p = phoneNumbers.get(i);
                if(Objects.nonNull(p)){
                    numbers.add(p);
                }
            }
            return numbers;
        }
    }
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐