謹慎使用Spring 配置標籤之任務配置標籤

1.導火線

最近都會規律的收到阿里雲預警的短信報警,於是,對線上的服務進行了健康度排查,至於排查的方法或者過程不是這裏討論的重點,需要知道的小夥子們,可以看看沈劍李彥鵬大神的一些乾貨。這裏就不多說了,經過一些列的排查得到的結論是,我們的服務器在一些業務高發的時間段不斷的創建/銷燬線程,導致內存和CUP的使用率非常的嗨!

2.定位問題

經過上面得到的信息進行分析,發現既然是一個小問題影響的,真是悔不當初呀!這個就是不好好看看操作文檔的血淋淋的教訓,哪怕是看一眼,所以寫出來自勉和跟大家共勉!如題,就是<task:annotation-driver/>配置的問題!

3.問題分析

相信大家在項目中應該有用到Spring 的異步註解@Async,這個是我們的線上的配置:


估計有些小夥伴跟我一樣也是一臉懵逼,這有什麼問題,spring 很多功能的開啓基本都是這些套路了,磨嘰,請往下看!

① 先來看一下這個標籤的 sxd

注意到紅框中的東東了嗎!這個是它的默認執行器,咋一說到這裏,可能有很多小夥伴還是不太明白,這裏我先放一下,先回過頭對是spring 異步調用的東東進行一個初級入門先。

4. spring 異步套路分析

我們都是知道spring對標籤的初始化的套路都是讀取uri的,然後進行解析然後通過反射找到相關的處理從而進行初始化,靠,好繞呀,簡單說,就是每一個標籤都是用過同名的NamespaceHandler進行處理,例如,上面的<task:annotation-driver/>家族的標籤,就是用TaskNamespaceHandler

package org.springframework.scheduling.config;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * {@code NamespaceHandler} for the 'task' namespace.
 *
 * @author Mark Fisher
 * @since 3.0
 */
public class TaskNamespaceHandler extends NamespaceHandlerSupport {

   @Override
   public void init() {
      this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
      this.registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());
      this.registerBeanDefinitionParser("scheduled-tasks", new ScheduledTasksBeanDefinitionParser());
      this.registerBeanDefinitionParser("scheduler", new SchedulerBeanDefinitionParser());
   }

}

這裏就可以看到了,對應的標籤的初始化處理器了,其他的標籤過程大同小異,怎麼感覺講的有點偏向bean的初始化了,回來了!異步任務處理的主要邏輯spring這是通過AnnotationAsyncExecutionInterceptor進行攔截處理,這裏只是列出了一些相關的方法,其他自己去看。

/**
 * Specialization of {@link AsyncExecutionInterceptor} that delegates method execution to
 * an {@code Executor} based on the {@link Async} annotation. Specifically designed to
 * support use of {@link Async#value()} executor qualification mechanism introduced in
 * Spring 3.1.2. Supports detecting qualifier metadata via {@code @Async} at the method or
 * declaring class level. See {@link #getExecutorQualifier(Method)} for details.
 *
 * @author Chris Beams
 * @author Stephane Nicoll
 * @since 3.1.2
 * @see org.springframework.scheduling.annotation.Async
 * @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor
 */
public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionInterceptor {
     /**
    * Return the qualifier or bean name of the executor to be used when executing the
    * given method, specified via {@link Async#value} at the method or declaring
    * class level. If {@code @Async} is specified at both the method and class level, the
    * method's {@code #value} takes precedence (even if empty string, indicating that
    * the default executor should be used preferentially).
    * @param method the method to inspect for executor qualifier metadata
    * @return the qualifier if specified, otherwise empty string indicating that the
    * {@linkplain #setExecutor(Executor) default executor} should be used
    * @see #determineAsyncExecutor(Method)
    */
   @Override
   protected String getExecutorQualifier(Method method) {
      // Maintainer's note: changes made here should also be made in
      // AnnotationAsyncExecutionAspect#getExecutorQualifier
      Async async = AnnotatedElementUtils.findMergedAnnotation(method, Async.class);
      if (async == null) {
         async = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Async.class);
      }
      return (async != null ? async.value() : null);
   }

}

可以看到這裏面其實並沒有看到什麼,主要的邏輯在父類中

public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered {
   /**
    * Intercept the given method invocation, submit the actual calling of the method to
    * the correct task executor and return immediately to the caller.
    * @param invocation the method to intercept and make asynchronous
    * @return {@link Future} if the original method returns {@code Future}; {@code null}
    * otherwise.
    */
   @Override
   public Object invoke(final MethodInvocation invocation) throws Throwable {
      Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
      Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
      final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

      AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
      if (executor == null) {
         throw new IllegalStateException(
               "No executor specified and no default executor set on AsyncExecutionInterceptor either");
      }

      Callable<Object> task = new Callable<Object>() {
         @Override
         public Object call() throws Exception {
            try {
               Object result = invocation.proceed();
               if (result instanceof Future) {
                  return ((Future<?>) result).get();
               }
            }
            catch (ExecutionException ex) {
               handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
            }
            catch (Throwable ex) {
               handleError(ex, userDeclaredMethod, invocation.getArguments());
            }
            return null;
         }
      };

      return doSubmit(task, executor, invocation.getMethod().getReturnType());
   }

   /**
    * This implementation searches for a unique {@link org.springframework.core.task.TaskExecutor}
    * bean in the context, or for an {@link Executor} bean named "taskExecutor" otherwise.
    * If neither of the two is resolvable (e.g. if no {@code BeanFactory} was configured at all),
    * this implementation falls back to a newly created {@link SimpleAsyncTaskExecutor} instance
    * for local use if no default could be found.
    * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME
    */
   @Override
   protected Executor getDefaultExecutor(BeanFactory beanFactory) {
      Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
      return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
   }
}

這個方法還是在父類中,哎,好深的套路

/**
 * Base class for asynchronous method execution aspects, such as
 * {@code org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor}
 * or {@code org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect}.
 *
 * <p>Provides support for <i>executor qualification</i> on a method-by-method basis.
 * {@code AsyncExecutionAspectSupport} objects must be constructed with a default {@code
 * Executor}, but each individual method may further qualify a specific {@code Executor}
 * bean to be used when executing it, e.g. through an annotation attribute.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @since 3.1.2
 */
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
   /**
    * The default name of the {@link TaskExecutor} bean to pick up: "taskExecutor".
    * <p>Note that the initial lookup happens by type; this is just the fallback
    * in case of multiple executor beans found in the context.
    * @since 4.2.6
    */
   public static final String DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor";
   // Java 8's CompletableFuture type present?
   private static final boolean completableFuturePresent = ClassUtils.isPresent(
         "java.util.concurrent.CompletableFuture", AsyncExecutionInterceptor.class.getClassLoader());
   private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<Method, AsyncTaskExecutor>(16);
   private volatile Executor defaultExecutor;
   private AsyncUncaughtExceptionHandler exceptionHandler;
   private BeanFactory beanFactory;

   /**
    * Determine the specific executor to use when executing the given method.
    * Should preferably return an {@link AsyncListenableTaskExecutor} implementation.
    * @return the executor to use (or {@code null}, but just if no default executor is available)
    */
   protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
      AsyncTaskExecutor executor = this.executors.get(method);
      if (executor == null) {
         Executor targetExecutor;
         String qualifier = getExecutorQualifier(method);
         if (StringUtils.hasLength(qualifier)) {
            targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
         }
         else {
            targetExecutor = this.defaultExecutor;
            if (targetExecutor == null) {
               synchronized (this.executors) {
                  if (this.defaultExecutor == null) { // 這裏獲取默認的執行器是重點
                     this.defaultExecutor = getDefaultExecutor(this.beanFactory);
                  }
                  targetExecutor = this.defaultExecutor;
               }
            }
         }
         if (targetExecutor == null) {
            return null;
         }
         executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
               (AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
         this.executors.put(method, executor);
      }
      return executor;
   }

   /**
    * Retrieve or build a default executor for this advice instance.
    * An executor returned from here will be cached for further use.
    * <p>The default implementation searches for a unique {@link TaskExecutor} bean
    * in the context, or for an {@link Executor} bean named "taskExecutor" otherwise.
    * If neither of the two is resolvable, this implementation will return {@code null}.
    * @param beanFactory the BeanFactory to use for a default executor lookup
    * @return the default executor, or {@code null} if none available
    * @since 4.2.6
    * @see #findQualifiedExecutor(BeanFactory, String)
    * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME
    */
   protected Executor getDefaultExecutor(BeanFactory beanFactory) {
      if (beanFactory != null) {
         try {
            // Search for TaskExecutor bean... not plain Executor since that would
            // match with ScheduledExecutorService as well, which is unusable for
            // our purposes here. TaskExecutor is more clearly designed for it.
            return beanFactory.getBean(TaskExecutor.class);
         }
         catch (NoUniqueBeanDefinitionException ex) {
            logger.debug("Could not find unique TaskExecutor bean", ex);
            try {
               return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
            }
            catch (NoSuchBeanDefinitionException ex2) {
               if (logger.isInfoEnabled()) {
                  logger.info("More than one TaskExecutor bean found within the context, and none is named " +
                        "'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " +
                        "as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());
               }
            }
         }
         catch (NoSuchBeanDefinitionException ex) {
            logger.debug("Could not find default TaskExecutor bean", ex);
            try {
               return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
            }
            catch (NoSuchBeanDefinitionException ex2) {
               logger.info("No task executor bean found for async processing: " +
                     "no bean of type TaskExecutor and no bean named 'taskExecutor' either");
            }
            // Giving up -> either using local default executor or none at all...
         }
      }
      return null;
   }
}

這裏有點繞,其實在第二層的父類中已經重寫了getDefaultExecutor了,相關的代碼,上面已經貼出來了,可以看到的是,默認什麼都沒有配合的時候,它的默認執行器是SimpleAsyncTaskExecutor,其實在第三小節的我貼出來的sxd限制文檔中也有提到的,那到底這個鬼到底幹了什麼呢?上代碼!

/**
 * Template method for the actual execution of a task.
 * <p>The default implementation creates a new Thread and starts it.
 * @param task the Runnable to execute
 * @see #setThreadFactory
 * @see #createThread
 * @see java.lang.Thread#start()
 */
protected void doExecute(Runnable task) {
   Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
   thread.start();
}

這個是的主要執行代碼,看到的是,當threadFactory 爲空的時候,他回去調用createThread這個創建線程的方法去創建一個新的線程去執行目標代碼的

/**
 * Template method for the creation of a new {@link Thread}.
 * <p>The default implementation creates a new Thread for the given
 * {@link Runnable}, applying an appropriate thread name.
 * @param runnable the Runnable to execute
 * @see #nextThreadName()
 */
public Thread createThread(Runnable runnable) {
   Thread thread = new Thread(getThreadGroup(), runnable, nextThreadName());
   thread.setPriority(getThreadPriority());
   thread.setDaemon(isDaemon());
   return thread;
}

可以看到的,當我們業務很是頻繁的時候,這裏就會不斷的創建線程,我的天呀,服務能挺到現在也不容易!異步執行的套路分析到這裏!

5. 總結

  • 其實任務執行框架是有給我們提供相應的線程池的執行器和配置方法,就是沒有看呀,這個是一知半解的危害呀!
  • 解決方法也很簡單,直接通過文檔配置ThreadPoolTaskExecutor 這個自帶的線程池就可以滿足大部分的業務場景了,但是我們這邊用到的線程變量進行登陸人信息,要求轉到異步的時候也要能拿到這些線程變量,所以用了其他方式,遲點在跟大家分享了!

水平有限,GGYY亂說一通,有疑問大家可以探討一下,莫噴!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章