Java Jersey@ManagedAsync并在HTTP线程和工作线程之间复制数据
我正在做一个项目,它有两种风格,有多租户和没有多租户 该项目公开了一个REST服务,我希望它是异步的。 所以我的基本服务看起来像Java Jersey@ManagedAsync并在HTTP线程和工作线程之间复制数据,java,multithreading,rest,jersey,servlet-3.0,Java,Multithreading,Rest,Jersey,Servlet 3.0,我正在做一个项目,它有两种风格,有多租户和没有多租户 该项目公开了一个REST服务,我希望它是异步的。 所以我的基本服务看起来像 @Component @Path("/resouce") @Consumes(MediaType.APPLICATION_JSON) public class ResouceEndpoint { @POST @ManagedAsync public void add(final Event event, @Suspended
@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());
}
}
没有多租户的情况下效果很好,我可以免费获得泽西岛内部执行人服务的好处。看
当我切换到多租户时,我会在请求上添加一个过滤器,解析租户id并将其放在本地线程(在我们的例子中是HTTP线程)上
当处理链点击“add()”方法时,当前线程是由Jersey executor服务提供的,因此它不包括我的租户id。
我只能考虑以下几个选项来解决这个问题
将ResourceEndpoint扩展到MutLiterationResourceEndpoint并删除@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;
}
});
}
}
公共类MutLiterationResourceEndpoint扩展了ResourceEndpoint{
@职位
public void add(最终事件,@Suspended最终异步响应){
最后一个字符串tenantId=getTeantIdFromThreadLocal();
taskExecutor.submit(新的可调用(){
@凌驾
public Void call()引发异常{
设置本地(租户ID);
browserEventsAnalyzer.insertEvent(事件);
Response=Response.status(Response.status.NO_CONTENT.build();
asyncResponse.resume(响应);
返回null;
}
});
}
}
但这样我需要管理我自己的线程执行器,感觉好像我遗漏了什么。
有没有关于不同方法的建议?以下是一些建议 作为背景,我已经使用泽西运动衫2年了,18个月前就遇到了这个问题 1.停止使用
@ManagedAsync
如果您可以控制运行Jersey的http服务器,我建议您停止使用@ManagedAsync
不要将Jersey设置为立即返回其http处理线程,并将实际请求工作卸载到托管执行器服务线程,而是将类似的内容用于http服务器,并将其配置为具有更大的工作线程池。这完成了同样的事情,但将异步责任推到了Jersey下面的一层
如果您对任何中大型项目使用@ManagedAsync
,您将在一年中遇到许多痛点。下面是我脑海中的一些想法:
- 如果任何ContainerRequestFilter命中外部服务(例如,身份验证筛选器命中您的安全模块,该模块命中数据库),您将失去您认为获得的好处
- 如果DB阻塞,并且auth filter调用需要5秒钟,Jersey尚未将工作卸载到异步线程,因此需要接收新conn的主线程被阻塞
- 如果在筛选器中设置了logback的MDC,并且希望在整个请求中都使用该上下文,则需要在托管异步线程上再次设置MDC
- 资源方法对于新手来说是神秘的,而且读起来很难看,因为:
- 它们需要一个额外的参数
- 它们返回void,隐藏其真正的响应类型
- 他们可以在任何地方“返回”,而不需要任何实际的
语句return
- Swagger或其他API文档工具无法自动记录异步资源终结点
- Guice或其他DI框架在处理异步资源端点中的某些范围绑定和/或提供程序时可能会遇到问题
@Context
和ContainerRequest
属性
这涉及到在过滤器中调用requestContext.setProperty(“tenant\u id”,tenantId)
,然后使用@Context
注入请求在资源中调用requestContext.getProperty(“tenant\u id”)
3.使用HK2 AOP而不是Jersey过滤器
这将涉及设置一个InterceptionService
的HK2绑定,该绑定具有一个MethodInterceptor
,用于检查托管异步资源方法,并手动执行所有RequestScoped
绑定的ContainerRequestFilter
。您的过滤器不是在Jersey注册的,而是在HK2注册的,由方法拦截器运行
如果您愿意,我可以在选项2/3中添加更多细节和代码示例,或者提供更多建议,但首先查看更多的筛选代码会有所帮助,如果可能,我再次建议选项1。+1对于详细答案,我最终使用了选项1建议。当我有时间时,我会尝试选项3并让你知道。选项2是不相关的,因为我必须按原样使用过滤器。我知道这有点旧,但仍然有效。关于如何解决你提到的MDC陷阱,你有什么建议吗?将自定义执行器+异步执行器设置为简单复制上下文就足够了吗?