All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.phantomthief.pool.KeyAffinityExecutor Maven / Gradle / Ivy

The newest version!
package com.github.phantomthief.pool;

import static com.github.phantomthief.pool.KeyAffinityExecutorUtils.RANDOM_THRESHOLD;
import static com.github.phantomthief.pool.KeyAffinityExecutorUtils.executor;
import static com.github.phantomthief.util.MoreReflection.logDeprecated;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.function.IntSupplier;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.github.phantomthief.pool.impl.KeyAffinityExecutorBuilder;
import com.github.phantomthief.util.ThrowableRunnable;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

/**
 * 按指定的Key亲和顺序消费的执行器
 * 

KeyAffinityExecutor是一个特殊的任务执行器{@link java.util.concurrent.Executor}, * 它可以确保投递进来的任务按Key相同的任务依照提交顺序依次执行。在既要通过并行处理来提高吞吐量、又要保证一定范围内的 * 任务按照严格的先后顺序来运行的场景下非常适用。

*

KeyAffinityExecutor的内建实现方式,是将指定的Key映射到固定的单线程执行器上,它内部会维护 * 多个(数量可配)这样的单线程执行器,来保持一定的任务并行度。

*

需要注意的是,此接口定义的KeyAffinityExecutor,并不要求Key相同的任务在相同的线程上运行, * 尽管实现类可以按照这种方式来实现,但它并非一个强制性的要求,因此在使用时也请不要依赖这样的假定。

*

很多人问,这和自己使用一个 {@link java.util.concurrent.Executor} 的数组,并通过简单取模的方式来实现有什么区别? * 事实上,大多数场景的确差异不大,但是当数据倾斜发生时,被散列到相同位置的数据可能会因为热点倾斜数据被延误,本实现在并发度较低时(阈值可设置), * 会挑选最闲置的 {@link java.util.concurrent.Executor} 投递,尽最大可能隔离倾斜数据,减少对其它数据带来的影响。

*

* 一个典型的使用方式是: *

{@code
 * class MyClass {
 *   private final KeyAffinityExecutor keyExecutor = newSerializingExecutor(10, "user-fans-count-%d");
 *   void foo(User user) {
 *     Future fansCount = keyExecutor.submit(user.getUserId(), () -> {
 *       return fansService.getByUserId(user.getUserId());
 *     });
 *   }
 * }
 * }
* * @param 该泛型如果是自定义类型,一定要实现正确的 {@link Object#hashCode()} * @author w.vela * Created on 2018-02-09. */ public interface KeyAffinityExecutor extends KeyAffinity { int DEFAULT_QUEUE_SIZE = 100; /** * 创建{@link KeyAffinityExecutorBuilder}构造器来构造KeyAffinityExecutor的实现对象 *

* 一般推荐使用其它几个 {@link #newSerializingExecutor} 重载版本,只有当需要定制具体参数时,才用本方法; *

* * @see #newSerializingExecutor */ @Nonnull static KeyAffinityExecutorBuilder newKeyAffinityExecutor() { return new KeyAffinityExecutorBuilder(); } /** * 创建一个{@link KeyAffinityExecutor}对象 * * @param parallelism 指定{@link KeyAffinityExecutor}并发度,即最多并行执行的任务数 * @param threadName 执行线程的名称,支持使用%d占位符来指定线程序号,参考{@link ThreadFactoryBuilder#setNameFormat(String)} * @return 返回{@link KeyAffinityExecutor}对象 */ @Nonnull static KeyAffinityExecutor newSerializingExecutor(int parallelism, String threadName) { return newSerializingExecutor(parallelism, DEFAULT_QUEUE_SIZE, threadName); } /** * 创建一个{@link KeyAffinityExecutor}对象 * * @param parallelism 指定{@link KeyAffinityExecutor}并发度,即最多并行执行的任务数 * @param queueBufferSize 任务队列的长度,请根据任务的吞吐量设置合适的大小,0为无上限(OOM警告) * @param threadName 执行线程的名称,支持使用%d占位符来指定线程序号,参考{@link ThreadFactoryBuilder#setNameFormat(String)} * @return 返回{@link KeyAffinityExecutor}对象 */ @Nonnull static KeyAffinityExecutor newSerializingExecutor(int parallelism, int queueBufferSize, String threadName) { return newKeyAffinityExecutor() .parallelism(parallelism) .executor(executor(threadName, queueBufferSize)) .build(); } /** * 创建一个{@link KeyAffinityExecutor}对象 * * @param parallelism 指定{@link KeyAffinityExecutor}并发度,即最多并行执行的任务数 * @param queueBufferSize 任务队列的长度,请根据任务的吞吐量设置合适的大小,0为无上限(OOM警告) * @param skipDuplicate 跳过历史的重复值 * @param threadName 执行线程的名称,支持使用%d占位符来指定线程序号,参考{@link ThreadFactoryBuilder#setNameFormat(String)} * @return 返回{@link KeyAffinityExecutor}对象 */ @Nonnull static KeyAffinityExecutor newSerializingExecutor(int parallelism, int queueBufferSize, boolean skipDuplicate, String threadName) { return newKeyAffinityExecutor() .parallelism(parallelism) .skipDuplicate(skipDuplicate) .executor(executor(threadName, queueBufferSize)) .build(); } /** * 创建一个{@link KeyAffinityExecutor}对象 *

此方法是{@link #newSerializingExecutor(int, int, String)} 的动态版本,可以动态设置并发度

* * @param parallelism 指定{@link KeyAffinityExecutor}并发度,即最多并行执行的任务数 * @param queueBufferSize 任务队列的长度,请根据任务的吞吐量设置合适的大小,0为无上限(OOM警告) * @param threadName 执行线程的名称,支持使用%d占位符来指定线程序号,参考{@link ThreadFactoryBuilder#setNameFormat(String)} * @return 返回{@link KeyAffinityExecutor}对象 */ @Nonnull static KeyAffinityExecutor newSerializingExecutor(IntSupplier parallelism, IntSupplier queueBufferSize, String threadName) { return newKeyAffinityExecutor() .parallelism(parallelism) .executor(executor(threadName, queueBufferSize)) .usingRandom(it -> it > RANDOM_THRESHOLD) .build(); } /** * 提交执行一个任务 * * @param key 任务对应的Key,此对象务必实现hashCode、equals,以确保可以起到标识作用 * @param task 任务执行对象 * @return 携带任务执行返回值的 {@link ListenableFuture},当 {@link KeyAffinityExecutorBuilder#skipDuplicate(boolean)} 配置为 {@code true} 时, * 如果提交的任务被跳过,{@link ListenableFuture#get()} 永远上抛 {@link java.util.concurrent.CancellationException} */ ListenableFuture submit(K key, @Nonnull Callable task); /** * 已废弃:提交执行一个任务 * 请使用 {@link #executeEx} 替代它 */ @Deprecated default ListenableFuture execute(K key, @Nonnull Runnable task) { checkNotNull(task); logDeprecated("Deprecated calling:KeyAffinityExecutor.execute() at ({}), use executeEx() instead."); return submit(key, () -> { task.run(); return null; }); } /** * 提交执行一个任务 * * @param key 任务对应的Key,此对象务必实现hashCode、equals,以确保可以起到标识作用 * @param task 任务执行对象 */ void executeEx(K key, @Nonnull ThrowableRunnable task); /** * 获取当前{@link KeyAffinityExecutor}的统计对象,以获取统计信息 * * @return 返回统计对象,在当前{@link KeyAffinityExecutor}初始化之前返回null * @throws IllegalStateException 获取统计对象失败时抛出此异常 */ @Nullable KeyAffinityExecutorStats stats(); /** * 获取当前所有的{@link KeyAffinityExecutor}对象集合,用于统计信息获取 */ static Collection> allExecutorsForStats() { return KeyAffinityExecutorBuilder.getAllExecutors(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy