Jersey @ManagedAsync 并在 HTTP 线程和工作线程之间复制数据

Jersey @ManagedAsync and copying data between HTTP thread and Worker thread

我正在从事一个项目,该项目有两种风格,有和没有多租户。

该项目公开了一个我希望异步的 REST 服务。 所以我的基本服务看起来像

@Component
@Path("/resouce")
@Consumes(MediaType.APPLICATION_JSON)
public class ResouceEndpoint {
    @POST
    @ManagedAsync
    public void add(final Event event, @Suspended final AsyncResponse asyncResponse) {
        resouce.insert (event);
        asyncResponse.resume( Response.status(Response.Status.NO_CONTENT).build());     
    }
}

在没有多租户的情况下工作正常,我免费获得内部 Jersey 执行程序服务的好处。参见 @ManagedAsync

当我切换到多租户时,我在解析租户 ID 的请求上添加了一个过滤器,并将其放在本地线程(在我们的例子中是 HTTP 线程)上。

当处理链命中上面的“add()”方法时,当前线程是Jersey 执行器服务提供的线程,因此它不包括我的租户ID。 我只能考虑以下选项来解决此问题。

将 ResouceEndpoint 扩展为 MutliTenantResouceEndpoint 并删除 @ManagedAsync 使用我自己的线程执行器

public class MutliTenantResouceEndpoint extends ResouceEndpoint {
    @POST
    public void add(final Event event, @Suspended final AsyncResponse asyncResponse) {
        final String tenantId = getTeantIdFromThreadLocal();
        taskExecutor.submit(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                setTeantIdToThreadLocal(tenantId);
                browserEventsAnalyzer.insertEvent(event);
                Response response = Response.status(Response.Status.NO_CONTENT).build();
                asyncResponse.resume(response);
                return null;
            }
        });
    }
}

但是这样我需要管理我自己的线程执行器,感觉好像我在这里遗漏了一些东西。 对不同的方法有什么建议吗?

这里有一些建议,按顺序排列。

就上下文而言,我已经使用 Jersey 2 年了,并且在 18 个月前就遇到了这个确切的问题。

1。停止使用 @ManagedAsync

如果您可以控制 Jersey 运行 正在使用的 http 服务器,我建议您停止使用 @ManagedAsync

与其将 Jersey 设置为 return,不如立即将其设置为 http 处理线程并将实际请求工作卸载到托管执行程序服务线程,为您的 http 服务器使用类似 Grizzly 的东西,并将其配置为有一个更大的工作线程池。这完成了同样的事情,但是将异步责任推到了 Jersey 下面的一层。

如果您将 @ManagedAsync 用于任何大中型项目,一年中您将 运行 遇到许多痛点。以下是我脑海中浮现的一些内容:

  • 如果任何 ContainerRequestFilter 命中外部服务(例如,auth 过滤器命中您的安全模块,后者命中数据库),您将失去您认为获得的好处
    • 如果您的数据库阻塞并且 auth 过滤器调用需要 5 秒,则 Jersey 尚未将工作卸载到异步线程,因此您需要接收新连接的主线程被阻塞
  • 如果您在过滤器中设置 logback 的 MDC,并且希望在整个请求中使用该上下文,则需要在托管异步线程上再次设置 MDC
  • 资源方法对于新手来说是晦涩难懂的,因为:
    • 他们需要一个额外的参数
    • 他们return无效,隐藏了他们真正的反应类型
    • 他们可以 "return" 任何地方,没有任何实际的 return 陈述
  • Swagger 或其他 API 文档工具无法自动记录异步资源端点
  • Guice 或其他 DI 框架可能无法处理某些范围绑定and/or 异步资源端点中的提供程序

2。使用 @ContextContainerRequest 属性

这将涉及在您的过滤器中调用 requestContext.setProperty("tenant_id", tenantId),然后使用 @Context 注入请求在您的资源中调用 requestContext.getProperty("tenant_id")

3。使用 HK2 AOP 而不是 Jersey 过滤器

这将涉及设置 InterceptionService 的 HK2 绑定,它有一个 MethodInterceptor 检查托管异步资源方法并手动执行所有 RequestScoped 绑定 ContainerRequestFilters .你的过滤器不是在 Jersey 注册的,而是在 HK2 注册的,通过方法拦截器 运行。

如果您愿意,我可以在选项 2/3 中添加更多详细信息和代码示例,或者提供其他建议,但首先查看更多过滤器代码会有所帮助,我再次建议选项 1,如果可能。