Dubbo源码:AbortPolicyWithReport

AbortPolicyWithReport

dubbo搞了拒绝策略AbortPolicyWithReport、线程池EagerThreadPoolExecutor、线程工厂NameThreadFactory、任务队列TaskQueue、线程服务ExecuteService-ThreadlessExecutor,需要学会这些自定义的用法。


AbortPolicyWithReport在原来支持的AbortPolicy(该拒绝策略是默认的,策略是抛异常)基础上加了WithReport功能(打日志+dump线程堆栈+事件通知)。补充下jdk的四种拒绝策略:

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

类、属性、构造方法如下。继承ThreadPoolExecutor.AbortPolicy类,重写了 rejectedExecution方法。我们后面的SPI ThreadPool的那几中,都是用这个拒绝策略,会使用其构造方法,传入的name给传给NameThreadFactory的一致。从lastPrintTime到guard的属性都是给后面dumpJStack方法使用的。lastPrintTime和TEN_MINUTES_MILLS控制dump点的间隔,OS_WIN_PREFIX到DEFAULT_DATETIME_FORMAT是为了针对不同的系统设置不同的日期格式,guard信号量是控制并发的,多线程并发dumpJStack的时候只允许当前时刻一个线程进行dump。

public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {

    protected static final Logger logger = LoggerFactory.getLogger(AbortPolicyWithReport.class);

    private final String threadName;

    private final URL url;

    private static volatile long lastPrintTime = 0;

    private static final long TEN_MINUTES_MILLS = 10 * 60 * 1000;

    private static final String OS_WIN_PREFIX = "win";

    private static final String OS_NAME_KEY = "os.name";

    private static final String WIN_DATETIME_FORMAT = "yyyy-MM-dd_HH-mm-ss";

    private static final String DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd_HH:mm:ss";

    private static Semaphore guard = new Semaphore(1);

    // 发现主要是在new ThreadPoolExecutor的时候传入自定义的拒绝策略
    public AbortPolicyWithReport(String threadName, URL url) {
        this.threadName = threadName;
        this.url = url;
    }

rejectedExecution,当线程池发生拒绝操作的时候,会调用如下方法,这里是重写了父类AbortPolicy的rejectedExecution方法。直接看注释。

@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    // 没有用到参数r
    String msg = String.format("Thread pool is EXHAUSTED!" +  // Thread pool is EXHAUSTED! 线程池EXHAUSTED疲惫不堪,存放太多任务触发拒绝操作
            " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: "
            + "%d)," +
            " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
        threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(),
        e.getLargestPoolSize(),
        e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
        url.getProtocol(), url.getIp(), url.getPort());
    // 1.打日志 以AbortPolicyWithReportTest为例:Thread pool is EXHAUSTED! Thread Name: Test, Pool Size: 0 (active: 0, core: 1, max: 1, largest: 0), Task: 0 (completed: 0), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://10.20.130.230:20880!
    logger.warn(msg);

    // 2.dump系统当前线程的堆栈信息,进去
    dumpJStack();

    // 3.处理事件,事件监听模型的惯用法:发生事件后让对应的监听器进行处理,进去
    dispatchThreadPoolExhaustedEvent(msg); // dispatch:派遣,发送;迅速处理,

    // 4.最后还是抛异常,和AbortPolicy的rejectedExecution方法的内容一样,只是上面做了一些其他操作
    throw new RejectedExecutionException(msg);
}

通知ThreadPoolExhaustedEvent事件 给 相关监听器。

public void dispatchThreadPoolExhaustedEvent(String msg) {
    // dispatch进去
    EventDispatcher.getDefaultExtension().dispatch(new ThreadPoolExhaustedEvent(this, msg));
}

dumpJStack直接看注释,dump的过程是现new线程池进行的。里面调用了JVMUtil.jstack(jStackStream)方法,该方法主要是jdk api 写文件的过程,这里不细说了。默认使在用户目录下生成文件,当然也可以在url配置dump.directory参数值。文件名比如Dubbo_JStack.log.2021-03-15_11/23/18

private void dumpJStack() {
    long now = System.currentTimeMillis();

    // dump every 10 minutes
    if (now - lastPrintTime < TEN_MINUTES_MILLS) {
        return;
    }

    // 信号量限制并发数为1
    if (!guard.tryAcquire()) {
        return;
    }

    ExecutorService pool = Executors.newSingleThreadExecutor();
    pool.execute(() -> {
        String dumpPath = url.getParameter(DUMP_DIRECTORY, System.getProperty("user.home"));

        SimpleDateFormat sdf;

        String os = System.getProperty(OS_NAME_KEY).toLowerCase();

        // window system don't support ":" in file name
        if (os.contains(OS_WIN_PREFIX)) {
            sdf = new SimpleDateFormat(WIN_DATETIME_FORMAT);
        } else {
            sdf = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT);
        }

        String dateStr = sdf.format(new Date());
        // try-with-resources
        try (FileOutputStream jStackStream = new FileOutputStream(
            new File(dumpPath, "Dubbo_JStack.log" + "." + dateStr))) { // new File第二个参数是child及具体的文件名称
            // 进去
            JVMUtil.jstack(jStackStream);
        } catch (Throwable t) {
            logger.error("dump jStack error", t);
        } finally {
            // 释放信号量
            guard.release();
        }
        lastPrintTime = System.currentTimeMillis();
    });

    // must shutdown thread pool ,if not will lead to OOM
    pool.shutdown();

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值