簡體   English   中英

生成的未知線程忽略過濾器鏈並在異步裝飾器上失敗

[英]Unknown thread spawns which ignores the filter chain and fails on async decorator

我目前面臨一個奇怪的問題,我無法在本地重現,但經常在 AWS ECS 中發生,讓應用程序崩潰或運行緩慢。

我們有一個 spring 啟動應用程序,它從傳入的 GraphQL 請求中提取租戶並將租戶設置為ThreadLocal實例。

為了支持 GraphQL Java kickstart 中的DataLoader ,我們將租戶填充到 graphql 數據加載器將使用的每個子線程中。 租戶必須指定數據庫架構。

執行人

@Bean
    @Override
    public Executor getAsyncExecutor() {

        log.info("Configuring async executor for multi tenancy...");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(15);
        executor.setThreadNamePrefix("tenant-child-executor-");
        // Important part: Set the MultiTenancyTaskDecorator to populate current tenant to child thread
        executor.setTaskDecorator(new MultiTenancyAsyncTaskDecorator());
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        log.info("Executor configured successfully!");
        executor.initialize();
        return executor;
    }

任務裝飾器

@NonNull
    @Override
    public Runnable decorate(@NonNull Runnable runnable) {        
        if (Objects.isNull(CurrentTenantContext.getTenant())) {
            log.warn("Current tenant is null while decorating a new thread!");
        }

        final TenantIdentifier parentThreadTenantIdentifier = Objects.isNull(CurrentTenantContext.getTenant()) ? TenantIdentifier.asSystem() : CurrentTenantContext.getTenant();
        // Also need to get the MDC context map as it is bound to the current local thread
        final Map<String, String> parentContextMap = MDC.getCopyOfContextMap();
        final var requestAttributes = RequestContextHolder.getRequestAttributes();

        return () -> {
            try {
            
                CurrentTenantContext.setTenant(TenantIdentifier.of(parentThreadTenantIdentifier.getTenantName()));
                if (Objects.isNull(requestAttributes)) {
                    log.warn("RequestAttributes are not available!");
                    log.warn("Running on tenant: {}", parentThreadTenantIdentifier.getTenantName());
                } else {
                    RequestContextHolder.setRequestAttributes(requestAttributes, true);
                }

                if (Objects.isNull(parentContextMap)) {
                    log.warn("Parent context map not available!");
                    log.warn("Running on tenant: {}", parentThreadTenantIdentifier.getTenantName());
                } else {
                    MDC.setContextMap(parentContextMap);
                }


                runnable.run();
            } finally {
          
                // Will be executed after thread finished or on exception
                RequestContextHolder.resetRequestAttributes();
                CurrentTenantContext.clear();
                MDC.clear();
            }
        };
    }

租戶上下文

public class CurrentTenantContext {
    private static final ThreadLocal<TenantIdentifier> currentTenant = new ThreadLocal<>();

    private CurrentTenantContext() {
        // Hide constructor to only provide static functionality
    }

    public static TenantIdentifier getTenant() {
        return currentTenant.get();
    }

    public static String getTenantName() {
        return getTenant().getTenantName();
    }

    public static void setTenant(TenantIdentifier tenant) {
        currentTenant.set(tenant);
    }


    public static void clear() {
        currentTenant.remove();
    }

    public static boolean isTenantSet() {
        return Objects.nonNull(currentTenant.get());
    }
}

在本地,這就像一個魅力。 即使在 docker 組合環境中,資源(CPU 和內存)有限,如 AWS。 即使是 100.000 個請求(JMETER),一切都按預期工作。

在 AWS 上,我們可以輕松地讓應用程序崩潰。 在一個或兩個請求之后,包含一些要由 GraphQL 解析的子對象,我們看到一個線程產生,它似乎通過鏈忽略或不忽略 go

Thread-110 | [sys ] | WARN | MultiTenancyAsyncTaskDecorator | Current tenant is null while decorating a new thread!

這一行中一個有趣的事情是線程的名稱。 每個傳入的請求都有模式http-nio-9100-exec-[N]和每個子線程模式tenant-child-executor-[I]但這個有模式Thread-[Y]

現在我想知道這個線程是從哪里來的,為什么它不能在本地重現。

我能夠找到問題的解決方案。

我需要改變

private static final ThreadLocal<TenantIdentifier> currentTenant = new ThreadLocal<>();

private static final InheritableThreadLocal<TenantIdentifier> currentTenant = new InheritableThreadLocal<>();

但我不知道為什么它適用於InheritableThreadLocal但不適用於 AWS 環境中的ThreadLocal

此外,我想知道為什么對於兩種方式都適用的本地測試不需要這種更改。

也許有人可以提供一些想法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM