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。使用 @Context
和 ContainerRequest
属性
这将涉及在您的过滤器中调用 requestContext.setProperty("tenant_id", tenantId)
,然后使用 @Context
注入请求在您的资源中调用 requestContext.getProperty("tenant_id")
。
3。使用 HK2 AOP 而不是 Jersey 过滤器
这将涉及设置 InterceptionService
的 HK2 绑定,它有一个 MethodInterceptor
检查托管异步资源方法并手动执行所有 RequestScoped
绑定 ContainerRequestFilter
s .你的过滤器不是在 Jersey 注册的,而是在 HK2 注册的,通过方法拦截器 运行。
如果您愿意,我可以在选项 2/3 中添加更多详细信息和代码示例,或者提供其他建议,但首先查看更多过滤器代码会有所帮助,我再次建议选项 1,如果可能。
我正在从事一个项目,该项目有两种风格,有和没有多租户。
该项目公开了一个我希望异步的 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。使用 @Context
和 ContainerRequest
属性
这将涉及在您的过滤器中调用 requestContext.setProperty("tenant_id", tenantId)
,然后使用 @Context
注入请求在您的资源中调用 requestContext.getProperty("tenant_id")
。
3。使用 HK2 AOP 而不是 Jersey 过滤器
这将涉及设置 InterceptionService
的 HK2 绑定,它有一个 MethodInterceptor
检查托管异步资源方法并手动执行所有 RequestScoped
绑定 ContainerRequestFilter
s .你的过滤器不是在 Jersey 注册的,而是在 HK2 注册的,通过方法拦截器 运行。
如果您愿意,我可以在选项 2/3 中添加更多详细信息和代码示例,或者提供其他建议,但首先查看更多过滤器代码会有所帮助,我再次建议选项 1,如果可能。