考虑以下代码来监听长轮询的更新:
Map<String, List<AsyncResponse>> tagMap = new ConcurrentGoodStuff();
// This endpoint listens for notifications of the tag
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@GET
@Path("listen/{tag}")
public void listenForUpdates(
@PathParam("tag") final String tag,
@Suspended final AsyncResponse response) {
tagMap.get(tag).add(response);
}
// This endpoint is for push-style notifications
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@PUT
@Path("update/{tag}/{value}")
public Response updateTag(
@PathParam("tag") final String tag,
@PathParam("value") final String value) {
for(AsyncResponse response : tagMap.get(tag)) {
// Resumes all previously suspended responses
response.resume(value);
}
return Response.ok("cool whatever").build();
}
客户端使用普通的Jersey客户端AsyncInvoker
添加侦听器,调用异步任务,然后另一个任务调用更新方法。
当我测试时,我遇到了竞争状态。在listenForUpdates()
上异步添加侦听器后,我同步地在updateTag()
端点上进行更新。但更新在添加侦听器之前运行,异步响应无法恢复。
对此的解决方案是在将<{em>}添加到侦听器之后调用上的suspend()
方法。但鉴于@Suspended
提供了一个已经暂停的AsyncResponse
对象,目前尚不清楚如何做到这一点。我应该怎么做才能在添加到侦听器之后暂停异步响应?这实际上会调用suspend方法吗?如何使用Jersey async客户端,或者我应该使用不同的长轮询客户端?
对于解决方案,我对不同的图书馆开放,比如Atmosphere或Guava。我不愿意在我的测试中添加Thread.sleep()
,因为这是一个等待发生的间歇性故障。
答案 0 :(得分:0)
我最终使用了RxJava,但在List
中使用BlockingQueue
而不是Map
提出一个同样出色的解决方案之前。它是这样的:
ConcurrentMap<String, BlockingQueue<AsyncResponse>> tagMap = new ConcurrentGoodStuff();
// This endpoint initiates a listener array for the tag.
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@GET
@Path("initListen/{tag}")
public void listenForUpdates(
@PathParam("tag") final String tag) {
tagMap.putIfAbsent(tag, new LinkedBlockingQueue<>());
}
// This endpoint listens for notifications of the tag
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@GET
@Path("listen/{tag}")
public void listenForUpdates(
@PathParam("tag") final String tag,
@Suspended final AsyncResponse response) {
BlockingQueue<AsyncResponse> responses = tagMap.get(tag);
if (responses != null) {
responses.add(response);
}
}
// This endpoint is for push-style notifications
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@PUT
@Path("update/{tag}/{value}")
public Response updateTag(
@PathParam("tag") final String tag,
@PathParam("value") final String value) {
BlockingQueue<AsyncResponse> responses = tagMap.get(tag);
if (responses == null) {
return Response.noContent().build();
}
if (responses.isEmpty()) {
// Block-wait for an async listener
try {
AsyncResponse response = tagMap.poll(15, TimeUnit.SECONDS);
if (response == null) {
return Response.noContent().build();
}
response.resume(value);
} catch (InterruptedException e) {
return Response.noContent().build();
}
} else {
for (AsyncResponse response : responses) {
// Resumes all previously suspended responses
response.resume(value);
}
}
return Response.ok("cool whatever").build();
}
我还没有测试过这个确切的代码,但我过去使用过它的某些版本。只要您首先同步调用initListen
端点,就可以调用异步listen
端点,然后调用同步update
端点,并且不会出现任何重要的竞争条件。< / p>
update
端点有一点竞争条件,但它是次要的。 responses
阻塞队列在迭代时可能变为空,或者可能由多个源以不同方式更新。为了缓解这种情况,我在每个请求实例化的数据结构上使用了drainTo(Collection)
方法。这仍然无法解决多个客户端可能尝试更新同一个监听器标签的用例,但我不需要这个用例。