2017-02-15 11 views
0

Мы используем Spring Boot для разработки наших сервисов. Мы решили сделать это в асинхронном пути, и мы столкнулись со следующей проблемой: у нас есть следующий аспект поверх все наши асинхронных остальных ресурсов:Весна Boot DeferredResult Aspect делает CPU очень высоким.

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.reflect.MethodSignature; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.http.HttpStatus; 
import org.springframework.http.ResponseEntity; 
import org.springframework.stereotype.Component; 
import org.springframework.web.context.request.async.AsyncRequestTimeoutException; 
import org.springframework.web.context.request.async.DeferredResult; 


@Aspect 
@Component 
public class TerminatedUsersAspect { 

    private static final Logger LOGGER = LoggerFactory.getLogger("[TERMINATED_USERS]"); 
    public static final ThreadLocal<String> ID_LOCAL = new ThreadLocal<>(); 

    @Autowired 
    private UserRepository userRepo; 

    @Autowired 
    private UserService userService; 

    @Autowired 
    private ExecutorService executorService; 

    @SuppressWarnings("unchecked") 
    @Around("within(com.test..*) && @annotation(authorization)") 
    public Object checkForId(ProceedingJoinPoint joinPoint, Authorization authorization) throws Throwable { 

     final MethodInvocationProceedingJoinPoint mJoinPoint = (MethodInvocationProceedingJoinPoint) joinPoint; 
     final MethodSignature signature = (MethodSignature) mJoinPoint.getSignature(); 
     final DeferredResult<Object> ret = new DeferredResult<>(60000L); 
     final String id = ID_LOCAL.get(); 

     if (signature.getReturnType().isAssignableFrom(DeferredResult.class) && (id != null)) { 

      userRepo.getAccountStatus(id).thenAcceptAsync(status -> { 
       boolean accountValid = userService.isAccountValid(status, true); 

       if (!accountValid) { 
        LOGGER.debug("AccountId: {} is not valid. Rejecting with 403", id); 
        ret.setErrorResult(new ResponseEntity<String>("Invalid account.", HttpStatus.FORBIDDEN)); 
        return; 
       } 

       try { 

        final DeferredResult<Object> funcRet = (DeferredResult<Object>) joinPoint.proceed(); 

        funcRet.setResultHandler(r -> { 
         ret.setResult(r); 
        }); 

        funcRet.onTimeout(() -> { 
         ret.setResult(new AsyncRequestTimeoutException()); 
        }); 

       } catch (Throwable e) { 
        ret.setErrorResult(ret); 
       } 

      }, executorService).exceptionally(ex -> { 
       ret.setErrorResult(ex); 
       return null; 
      }); 

      return ret; 
     } 

     return joinPoint.proceed(); 
    } 

} 

Нашего встроенный сервер в приложении прибойный. Проблема возникает во времени. Похоже, что после почти одного дня из-за этого аспекта процессоры постепенно заканчиваются на 100% красным. Я отлаживал код и, похоже, с моей точки зрения, но, может быть, я чего-то не хватает? Любые идеи приветствуются. Спасибо, C.

ответ

1

Первое, что меня поразило, это то, что вы дважды вызываете joinPoint.proceed() в свой код. Первый, когда вы отсылаете его в DeferredResult, а второй - при возврате из метода. Вы должны переписать его, чтобы он выполнялся только один раз. Вы можете использовать @Around аннотацию с «выполнением» (https://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-pointcuts-examples) вместо «внутри», поскольку она позволяет указать тип возврата. В этом случае вам не понадобится, если на основе типа возвращаемого метода.

Учитывая это, я не уверен, что это решит вашу проблему. Мне не хватает контекста, и я не знаю, чего вы пытаетесь достичь. Возможно, есть более простой способ сделать это, поскольку использование отложенного результата в этом контексте для меня немного странно. Кроме того, что конкретно вызвало 100% -ное использование ресурсов? Были ли какие-то потоки бесконечно или какие-то HTTP-соединения висели?

+0

Я согласен с тем, что здесь сказано. Вы, кажется, слишком широко применяете аспект, большой 'if' и все дорогостоящие вещи отражения не нужны. Тогда 'continue()' после 'if' вначале не понадобится, но если вы его используете, по крайней мере, поместите его в ветку' else'. Я подготовлю небольшой образец для вас. – kriegaex

0

Как насчет этого небольшого рефакторинга? Он типичен, не нуждается в отражении и только вызывает proceed() один раз.

Кроме того, я думал, что в вашем коде ret.setErrorResult(ret) не имеет смысла, и я заменил его на setErrorResult(e), установив фактическое выполнение в качестве результата ошибки.

Я также надеюсь, что вы простите меня за переименование нескольких переменных. Это помогло мне понять, что происходит. Я все еще не уверен на 100%, потому что я ничего не знаю о Spring (но многое о AspectJ).

package de.scrum_master.aspect; 

import java.util.concurrent.ExecutorService; 

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.http.HttpStatus; 
import org.springframework.http.ResponseEntity; 
import org.springframework.stereotype.Component; 
import org.springframework.web.context.request.async.AsyncRequestTimeoutException; 
import org.springframework.web.context.request.async.DeferredResult; 

@Aspect 
@Component 
public class TerminatedUsersAspect { 

    private static final Logger LOGGER = LoggerFactory.getLogger("[TERMINATED_USERS]"); 
    public static final ThreadLocal<String> ID_LOCAL = new ThreadLocal<>(); 

    @Autowired private UserRepository userRepo; 
    @Autowired private UserService userService; 
    @Autowired private ExecutorService executorService; 

    @Around(
    "within(com.test..*) && @annotation(authorization) && " + 
    "execution(org.springframework.web.context.request.async.DeferredResult *(..))" 
) 
    public DeferredResult<Object> checkForId(ProceedingJoinPoint joinPoint, Authorization authorization) throws Throwable { 

    final DeferredResult<Object> aspectResult = new DeferredResult<>(60000L); 
    final String id = ID_LOCAL.get(); 

    userRepo 
     .getAccountStatus(id) 
     .thenAcceptAsync(status -> { 
      boolean accountValid = userService.isAccountValid(status, true); 
      if (!accountValid) { 
      LOGGER.debug("AccountId: {} is not valid. Rejecting with 403", id); 
      aspectResult.setErrorResult(new ResponseEntity<String>("Invalid account.", HttpStatus.FORBIDDEN)); 
      return; 
      } 
      try { 
      @SuppressWarnings("unchecked") 
      final DeferredResult<Object> originalResult = (DeferredResult<Object>) joinPoint.proceed(); 
      originalResult.setResultHandler(r -> { aspectResult.setResult(r); }); 
      originalResult.onTimeout(() -> { aspectResult.setResult(new AsyncRequestTimeoutException()); }); 
      } catch (Throwable e) { 
      aspectResult.setErrorResult(e); 
      } 
     }, 
     executorService 
    ) 
     .exceptionally(ex -> { 
      aspectResult.setErrorResult(ex); 
      return null; 
     } 
    ); 

    return aspectResult; 
    } 
} 
Смежные вопросы