【问题标题】:Jersey @ManagedAsync and copying data between HTTP thread and Worker threadJersey @ManagedAsync 和在 HTTP 线程和 Worker 线程之间复制数据
【发布时间】:2015-06-30 11:33:07
【问题描述】:

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

该项目公开了一个我希望异步的 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 executor 服务的好处。见@ManagedAsync

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

当处理链点击“add()”方法时,当前线程上面是 Jersey executor 服务提供的,所以不包含我的租户 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;
            }
        });
    }
}

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

【问题讨论】:

    标签: java multithreading rest jersey servlet-3.0


    【解决方案1】:

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

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

    1。停止使用@ManagedAsync

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

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

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

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

    2。使用@ContextContainerRequest 属性

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

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

    这将涉及设置InterceptionService 的 HK2 绑定,该绑定具有MethodInterceptor,用于检查托管异步资源方法并手动执行所有RequestScoped 绑定ContainerRequestFilters。您无需在 Jersey 中注册过滤器,而是在 HK2 中注册它们,由方法拦截器运行。

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

    【讨论】:

    • +1 我最终使用了建议的选项 1 的详细答案。当我有时间并让你知道时,我会尝试选项 3。选项 2 无关紧要,因为我必须按原样使用过滤器。
    • 我知道这有点老了,但仍然如此。您对如何解决您提到的 MDC 陷阱有任何示例建议吗?设置一个简单复制上下文的自定义 Executor + AsyncExecutor 就足够了吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-27
    • 2015-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-22
    相关资源
    最近更新 更多