一文详解 springboot 项目启动时异步执行初始化逻辑

你知道的越多,你不知道的越多
点赞再看,养成习惯
如果您有疑问或者见解,欢迎指教:
企鹅:869192208

文章目录

        • 前言
        • 代码实现
          • 定义异步处理工具类
          • 实现 java 线程池
          • 新建 AppInit 实现 ApplicationRunner 接口完成启动项目时异步数据初始化

前言

前面的工作中,为了提高地区数据的响应时间,需要加载全国区划数据到 redis 中缓存起来,这个过程希望在项目时启动。
由于初始化全国区划到 redis 中这个过程是比较耗时的,所以我们可以考虑使用异步执行的方式去实现。

代码实现
定义异步处理工具类
import com.xymy.common.config.ThreadPoolExecutorUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

/**
 * 

异步处理工具类

* * @author xymy * @create 2023-02-07 16:45 */ @Slf4j public enum RunMerger { SM; public void asyncHandle(String threadName, Runnable ...runs) { this.asyncHandle(null, threadName,runs); } public void asyncHandle(ExecutorService seedPool, String threadName, Runnable ... runs) { if (seedPool == null) { seedPool = ThreadPoolExecutorUtil.getSeedPool(threadName, runs.length); } try { for (Runnable run : runs) { CompletableFuture.runAsync(() -> { run.run(); },seedPool).exceptionally(ex -> { log.error("处理异常", ex); return null; }); } } finally { ThreadPoolExecutorUtil.releaseSeedPool(seedPool); } } public void syncHandle(Runnable ...runs) { Arrays.stream(runs).forEach(Runnable::run); } public void asyncHandle(Runnable ...runs) { this.asyncHandle(null, runs); } }

实现 java 线程池
  • 新建 ThreadPoolExecutorUtil 类
import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.xymy.common.utils.DistrKeyGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;

/**
 * 

线程池

* * @author xymy * @create 2021-11-22 14:24 */ @Slf4j public class ThreadPoolExecutorUtil { private static Map<String, ExecutorService> executors = new ConcurrentHashMap<>(); public static ExecutorService getSeedPool(int poolSize) { DistrKeyGenerator keyGenerator = new DistrKeyGenerator(); return getSeedPool(keyGenerator.generateKey() +"-", poolSize); } public static ExecutorService getSeedPool(String poolName, int poolSize) { // 需手动释放 if (StringUtils.isBlank(poolName)) { DistrKeyGenerator keyGenerator = new DistrKeyGenerator(); poolName = keyGenerator.generateKey() +"-"; } ExecutorService executorService = executors.get(poolName); if (null == executorService) { synchronized (ThreadPoolExecutorUtil.class) { executorService = executors.get(poolName); if (null == executorService) { executorService = init(poolName,poolSize); executors.put(poolName, executorService); } } } return executorService; } public static void releaseSeedPool(ExecutorService executorService) { Set<Map.Entry<String, ExecutorService>> entries = executors.entrySet(); for (Map.Entry<String, ExecutorService> entry : entries) { ExecutorService value = entry.getValue(); if (value == executorService) { executors.remove(entry.getKey()); } } if (executorService != null) { executorService.shutdown(); } } public static void releaseSeedPool(String poolName) { // 释放 ExecutorService executorService = executors.remove(poolName); if (executorService != null) { executorService.shutdown(); } } private ThreadPoolExecutorUtil(){} private static ExecutorService init(String poolName, int poolSize) { ThreadFactoryBuilder threadFactoryBuilder = new ThreadFactoryBuilder(); threadFactoryBuilder.setUncaughtExceptionHandler((t, e) -> log.error( "线程[{}]异常:", t.getName(), e)); threadFactoryBuilder.setNamePrefix("apply-pool-" + poolName); threadFactoryBuilder.setDaemon(false); return new ThreadPoolExecutor(poolSize, poolSize,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(poolSize*10), threadFactoryBuilder.build(), new ThreadPoolExecutor.CallerRunsPolicy()); } }

  • 新建 DistrKeyGenerator 类,用来生成主键
<dependency>
    <groupId>com.google.guavagroupId>
    <artifactId>guavaartifactId>
    <version>32.1.1-jreversion>
dependency>
import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * 主键生成
 * sharding-jdbc核心源码
 * @author xymy
 * @create 2019-07-24 09:37
 */
@Slf4j
@Component
public class DistrKeyGenerator {

    public static final long EPOCH;
    // 自增序列长度
    private static final long SEQUENCE_BITS = 12L;
    // workId的长度(单位是位时长度)
    private static final long WORKER_ID_BITS = 10L;

    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;
    // 位运算计算workerId的最大值(workerId占10位,那么1向左移10位就是workerId的最大值)
    private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;


    private long workerId = 0;
    // EPOCH就是起始时间,从2016-11-01 00:00:00开始的毫秒数
    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }

    private long sequence;

    private long lastTime;

    public DistrKeyGenerator() {

    }

    public DistrKeyGenerator(long workerId) {
        Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE);
        this.workerId = workerId;
    }

    /**
     * Generate key.
     * 生成id
     * @return key type is @{@link Long}.
     */
    public synchronized long generateKey() {
        long currentMillis = getCurrentMillis();
        // 每次取分布式唯一ID的时间不能少于上一次取时的时间
        Preconditions.checkState(lastTime <= currentMillis, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastTime, currentMillis);
        // 如果同一毫秒范围内,那么自增,否则从0开始
        if (lastTime == currentMillis) {
            // 如果自增后的sequence值超过4096,那么等待直到下一个毫秒
            if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } else {
            sequence = 0;
        }
        // 更新lastTime的值,即最后一次获取分布式唯一ID的时间
        lastTime = currentMillis;
        if (log.isDebugEnabled()) {
            log.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTime)), workerId, sequence);
        }
        // 从这里可知分布式唯一ID的组成部分;
        return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }
    // 获取下一毫秒的方法:死循环获取当前毫秒与lastTime比较,直到大于lastTime的值;
    private long waitUntilNextTime(final long lastTime) {
        long time = getCurrentMillis();
        while (time <= lastTime) {
            time = getCurrentMillis();
        }
        return time;
    }

    /**
     * Get current millis.
     *
     * @return current millis
     */
    private long getCurrentMillis() {
        return System.currentTimeMillis();
    }

}

新建 AppInit 实现 ApplicationRunner 接口完成启动项目时异步数据初始化
@Component
@Slf4j
public class AppInit implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) {

        RunMerger.SM.asyncHandle(() -> {
            log.info("项目启动完成,开始初始化全国区划数据...");
            //省略数据初始化的代码,此处为耗时的操作
            String countryCode = "xxxxx";
            xxService.initAreaInfoList(countryCode);
            log.info("项目启动完成,完成初始化全国区划数据...");
        });
    }
}

至此,就可以实现 springboot 项目启动时异步执行初始化逻辑。

你可能感兴趣的:(工作需求解决方案,spring,boot,后端,java,项目启动初始化,异步操作)