Apache Flink源码系列(1)__线程

   线程

 

创建线程方法

继承线程类 java.lang.Thread
实现接口 java.lang.Runnable、java.lang.Callable、java.lang.FutureTask
线程池

java.util.concurrent.ThreadPoolExecutor

  • Java线程池是预先创建并管理一组工作线程的机制,通过复用线程来避免频繁创建和销毁线程的开销
  • 线程池核心组件
    • corePoolSize:线程池中的常驻核心线程数
    • maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
    • keepAliveTime:余的空闲线程的存活时间。 当线程池(中线程)数量超过corePoolSize时,且空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到剩下coolPoolSize个线程为止
    • unit:keepAliveTime的单位
    • workQueue:任务队列,被提交尚未被执行的任务
    • threadFactory:生成线程池中线程的线程工厂,用于创建线程(一般用默认的即可)
    • handler:拒绝策略,当任务队列满了且工作线程数大于等于线程池的最大线程数时,如何来拒绝请求执行的runnable的策略:
      • AbortPolicy(默认): 直接抛出java.util.concurrent.RejectedExecutionException,组织系统正常运行
      • CallerRunsPolicy:   调用者运行,不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量    
      • DiscardOldestPolicy: 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务;
      • DiscardPolicy: 直接丢弃任务,不予任何处理也不抛出异常(如果允许任务丢失,该方案适用)。
  • 线程池的工作原理:当提交新任务时,如果当前线程数少于核心线程数,直接创建新线程执行任务;如果核心线程都在忙碌,任务会被放入工作队列等待;当队列满了且线程数未达到最大值时,创建额外线程处理任务;如果所有线程都在工作且队列已满,则执行拒绝策略。

  • 线程池分类
    • FixedThreadPool:固定大小线程池,核心线程数等于最大线程数,使用无界
    • LinkedBlockingQueue:适合负载比较重的服务器环境,但可能因为队列无界导致内存溢出
    • CachedThreadPool:可缓存线程池,核心线程数为0,最大线程数为Integer.MAX_VALUE,使用SynchronousQueue。适合执行大量短期异步任务,但在高并发情况下可能创建过多线程
    • SingleThreadExecutor:单线程池,确保所有任务按照提交顺序串行执行。适合需要保证任务执行顺序的场景
    • ScheduledThreadPool:支持定时和周期性任务执行的线程池,基于DelayedWorkQueue实现
  •  

线程池实际应用

实时风控系统中的多级线程池设计
1. 业务背景
   电商平台在订单处理环节可以使用线程池来提升系统性能。订单系统需要处理用户下单、库存检查、支付处理、物流安排等多个环节,每个环节都涉及不同的IO操作和计算任务。

2. 技术实现
   为不同类型的任务设计了专门的线程池。
   订单验证使用核心线程数为16、最大线程数为32的线程池,因为验证逻辑相对简单但需要快速响应;
   库存扣减操作使用核心线程数为8、最大线程数为16的线程池,配合数据库连接池来控制并发度;
   支付回调处理使用核心线程数为4、最大线程数为8的线程池,通常来讲,支付处理对数据一致性要求很高。
   
3. 队列选择
   订单验证使用容量为1000的ArrayBlockingQueue,确保在高峰期能够缓存足够的任务;
   库存操作使用容量为500的ArrayBlockingQueue,避免过度积压导致库存数据不准确;
   支付回调使用容量为200的ArrayBlockingQueue,配合CallerRunsPolicy拒绝策略,确保重要的支付信息不会丢失。
   
4. 好处
   经生产验证,高峰期能够处理每秒数万笔订单,系统响应时间控制在200毫秒以内,显著提升了用户体验。
   

 

金融交易系统风控处理
1. 业务背景
   证券公司的实时风控系统采用线程池来处理大量的交易风险评估任务。
   系统需要在毫秒级时间内完成风险规则检查、历史交易分析、账户状态验证等操作。

2. 技术实现
   分层的线程池架构:
   第一层是快速筛选池,使用核心线程数为32、最大线程数为64的配置,配合SynchronousQueue来处理基础风险规则检查,这类任务执行时间短但频率高;
   第二层是深度分析池,使用核心线程数为16、最大线程数为24的配置,配合容量为100的ArrayBlockingQueue来处理复杂的历史数据分析任务;
   第三层是异常处理池,使用核心线程数为4、最大线程数为8的配置,专门处理需要人工介入的异常交易。
   
3. 拒绝策略
   为了确保交易的实时性,系统可使用自定义的拒绝策略,当快速筛选池满载时,会将任务降级到备用线程池处理,同时发送告警通知运维人员。
   深度分析池使用DiscardOldestPolicy策略,确保最新的分析任务能够得到及时处理。
   
4. 好处
   经生产验证,该风控系统能够在毫秒级完成风险评估,日处理交易量超过千万笔,风险识别准确率达到99.8%。

 

互联网广告投放系统
1. 业务背景
   互联网公司广告投放系统使用线程池来处理实时竞价、广告素材渲染、效果统计等任务。系统需要在100毫秒内完成广告竞价和投放决策。

2. 技术实现
   分层的线程池架构:
   竞价处理池使用核心线程数为64、最大线程数为128的配置,因为竞价请求量大且对延迟敏感;
   素材渲染池使用核心线程数为32、最大线程数为48的配置,处理广告图片、视频的实时渲染和压缩;
   数据统计池使用核心线程数为16、最大线程数为24的配置,处理点击、展示等效果数据的收集和分析。
   
3. 队列设计
   竞价处理使用SynchronousQueue配合AbortPolicy策略,确保竞价请求要么立即处理要么立即拒绝,避免超时导致的广告投放失败;
   素材渲染使用容量为2000的LinkedBlockingQueue,允许一定程度的任务积压;
   数据统计使用容量为5000的LinkedBlockingQueue配合DiscardOldestPolicy策略,确保最新的统计数据能够得到处理。
   
4. 好处
   经生产验证,日处理广告请求超过10亿次,平均响应时间控制在50毫秒以内,广告投放成功率达到99.9%。

最佳实践

在实际应用中,线程池的配置需要根据具体业务场景进行调优。建议如下:

1. 为线程池设置有意义的名称便于监控和调试,同时配置JVM参数来优化线程栈大小;

2. 定期分析线程池的运行状况,根据业务负载变化调整配置参数,确保系统在各种情况下都能稳定高效运行。

3. 通过压力测试来确定最优的线程数配置,同时监控线程池的关键指标如队列长度、活跃线程数、任务完成数等。
   合理设置队列大小很重要,队列过小可能导致任务被拒绝,队列过大可能导致内存溢出或响应时间过长。
   选择合适的拒绝策略,对于重要任务建议使用CallerRunsPolicy,对于可以丢弃的任务可以使用DiscardOldestPolicy。
   
4. 避免使用Executors提供的默认线程池,而是根据业务需求自定义ThreadPoolExecutor参数。

flink中的线程池

flink源码模块线程池
/**
* Flink版本:release-2.0.0
* 
**/
// flink-core模块,ExecutorThreadFactory.java
public class ExecutorThreadFactory implements ThreadFactory {

    /** The thread pool name used when no explicit pool name has been specified. */
    private static final String DEFAULT_POOL_NAME = "flink-executor-pool";

    private final AtomicInteger threadNumber = new AtomicInteger(1);

    private final ThreadGroup group;

    private final String namePrefix;

    private final int threadPriority;

    @Nullable private final UncaughtExceptionHandler exceptionHandler;

    // ------------------------------------------------------------------------

    /**
     * Creates a new thread factory using the default thread pool name ('flink-executor-pool') and
     * the default uncaught exception handler (log exception and kill process).
     */
    public ExecutorThreadFactory() {
        this(DEFAULT_POOL_NAME);
    }

    /**
     * Creates a new thread factory using the given thread pool name and the default uncaught
     * exception handler (log exception and kill process).
     *
     * @param poolName The pool name, used as the threads' name prefix
     */
    public ExecutorThreadFactory(String poolName) {
        this(poolName, FatalExitExceptionHandler.INSTANCE);
    }

    /**
     * Creates a new thread factory using the given thread pool name and the given uncaught
     * exception handler.
     *
     * @param poolName The pool name, used as the threads' name prefix
     * @param exceptionHandler The uncaught exception handler for the threads
     */
    public ExecutorThreadFactory(String poolName, UncaughtExceptionHandler exceptionHandler) {
        this(poolName, Thread.NORM_PRIORITY, exceptionHandler);
    }

    ExecutorThreadFactory(
            final String poolName,
            final int threadPriority,
            @Nullable final UncaughtExceptionHandler exceptionHandler) {
        this.namePrefix = checkNotNull(poolName, "poolName") + "-thread-";
        this.threadPriority = threadPriority;
        this.exceptionHandler = exceptionHandler;

        SecurityManager securityManager = System.getSecurityManager();
        this.group =
                (securityManager != null)
                        ? securityManager.getThreadGroup()
                        : Thread.currentThread().getThreadGroup();
    }

    // ------------------------------------------------------------------------

    @Override
    public Thread newThread(Runnable runnable) {
        Thread t = new Thread(group, runnable, namePrefix + threadNumber.getAndIncrement());
        t.setDaemon(true);

        t.setPriority(threadPriority);

        // optional handler for uncaught exceptions
        if (exceptionHandler != null) {
            t.setUncaughtExceptionHandler(exceptionHandler);
        }

        return t;
    }

    // --------------------------------------------------------------------------------------------

    /** Builder for {@link ExecutorThreadFactory}. */
    public static final class Builder {
        private String poolName;
        private int priority = Thread.NORM_PRIORITY;
        private UncaughtExceptionHandler exceptionHandler = FatalExitExceptionHandler.INSTANCE;

        public Builder setPoolName(final String poolName) {
            this.poolName = poolName;
            return this;
        }

        public Builder setThreadPriority(final int priority) {
            this.priority = priority;
            return this;
        }

        public Builder setExceptionHandler(final UncaughtExceptionHandler exceptionHandler) {
            this.exceptionHandler = exceptionHandler;
            return this;
        }

        public ExecutorThreadFactory build() {
            return new ExecutorThreadFactory(poolName, priority, exceptionHandler);
        }
    }
}


// flink-runtime模块,TaskExecutor.java
public TaskExecutor(
            RpcService rpcService,
            TaskManagerConfiguration taskManagerConfiguration,
            HighAvailabilityServices haServices,
            TaskManagerServices taskExecutorServices,
            ExternalResourceInfoProvider externalResourceInfoProvider,
            HeartbeatServices heartbeatServices,
            TaskManagerMetricGroup taskManagerMetricGroup,
            @Nullable String metricQueryServiceAddress,
            TaskExecutorBlobService taskExecutorBlobService,
            FatalErrorHandler fatalErrorHandler,
            TaskExecutorPartitionTracker partitionTracker,
            DelegationTokenReceiverRepository delegationTokenReceiverRepository) {

        super(rpcService, RpcServiceUtils.createRandomName(TASK_MANAGER_NAME));

        checkArgument(
                taskManagerConfiguration.getNumberSlots() > 0,
                "The number of slots has to be larger than 0.");

        this.taskManagerConfiguration = checkNotNull(taskManagerConfiguration);
        this.taskExecutorServices = checkNotNull(taskExecutorServices);
        this.haServices = checkNotNull(haServices);
        this.fatalErrorHandler = checkNotNull(fatalErrorHandler);
        this.partitionTracker = partitionTracker;
        this.delegationTokenReceiverRepository = checkNotNull(delegationTokenReceiverRepository);
        this.taskManagerMetricGroup = checkNotNull(taskManagerMetricGroup);
        this.taskExecutorBlobService = checkNotNull(taskExecutorBlobService);
        this.metricQueryServiceAddress = metricQueryServiceAddress;
        this.externalResourceInfoProvider = checkNotNull(externalResourceInfoProvider);

        this.libraryCacheManager = taskExecutorServices.getLibraryCacheManager();
        this.taskSlotTable = taskExecutorServices.getTaskSlotTable();
        this.jobTable = taskExecutorServices.getJobTable();
        this.jobLeaderService = taskExecutorServices.getJobLeaderService();
        this.unresolvedTaskManagerLocation =
                taskExecutorServices.getUnresolvedTaskManagerLocation();
        this.localStateStoresManager = taskExecutorServices.getTaskManagerStateStore();
        this.fileMergingManager = taskExecutorServices.getTaskManagerFileMergingManager();
        this.changelogStoragesManager = taskExecutorServices.getTaskManagerChangelogManager();
        this.channelStateExecutorFactoryManager =
                taskExecutorServices.getTaskManagerChannelStateManager();
        this.shuffleEnvironment = taskExecutorServices.getShuffleEnvironment();
        this.kvStateService = taskExecutorServices.getKvStateService();
        this.ioExecutor = taskExecutorServices.getIOExecutor();
        this.resourceManagerLeaderRetriever = haServices.getResourceManagerLeaderRetriever();

        this.hardwareDescription =
                HardwareDescription.extractFromSystem(taskExecutorServices.getManagedMemorySize());
        this.memoryConfiguration =
                TaskExecutorMemoryConfiguration.create(taskManagerConfiguration.getConfiguration());

        this.resourceManagerAddress = null;
        this.resourceManagerConnection = null;
        this.currentRegistrationTimeoutId = null;

        final ResourceID resourceId =
                taskExecutorServices.getUnresolvedTaskManagerLocation().getResourceID();
        this.jobManagerHeartbeatManager =
                createJobManagerHeartbeatManager(heartbeatServices, resourceId);
        this.resourceManagerHeartbeatManager =
                createResourceManagerHeartbeatManager(heartbeatServices, resourceId);
        // 线程池
        ExecutorThreadFactory sampleThreadFactory =
                new ExecutorThreadFactory.Builder()
                        .setPoolName("flink-thread-info-sampler")
                        .build();
        ScheduledExecutorService sampleExecutor =
                Executors.newSingleThreadScheduledExecutor(sampleThreadFactory);
        this.threadInfoSampleService = new ThreadInfoSampleService(sampleExecutor);
        this.profilingService =
                ProfilingService.getInstance(taskManagerConfiguration.getConfiguration());

        this.slotAllocationSnapshotPersistenceService =
                taskExecutorServices.getSlotAllocationSnapshotPersistenceService();

        this.sharedResources = taskExecutorServices.getSharedResources();
        this.jobInformationCache = taskExecutorServices.getJobInformationCache();
        this.taskInformationCache = taskExecutorServices.getTaskInformationCache();
        this.shuffleDescriptorsCache = taskExecutorServices.getShuffleDescriptorCache();
    }

 

posted @ 2025-09-05 14:55  lvlin241  阅读(12)  评论(0)    收藏  举报