我正在开发一个向 Musicbrainz 网络服务发出请求的应用程序。我在 musicbrainz 手册中阅读到每秒向 Web 服务发出的请求不超过一个,否则客户端 IP 将被阻止。
您建议使用哪种架构来使此限制对服务客户端透明。
- 我想调用一个方法(例如 getAlbuns),它应该只在最后一个请求后 1 秒发出请求。
- 我还想一次调用 10 个请求,服务应该处理排队,在可用时返回结果(非阻塞)。
谢谢!
我正在开发一个向 Musicbrainz 网络服务发出请求的应用程序。我在 musicbrainz 手册中阅读到每秒向 Web 服务发出的请求不超过一个,否则客户端 IP 将被阻止。
您建议使用哪种架构来使此限制对服务客户端透明。
谢谢!
您需要定义本地客户将调用的本地“代理服务”。
本地代理将接收请求并将其传递给实际服务。但仅以每秒一条消息的速度。
您如何做到这一点在很大程度上取决于您可以使用的技术。
最简单的是多线程 java 服务,它具有静态和同步的 LastRequestTime long;" 时间戳变量。(尽管您需要一些代码杂技来保持您的请求按顺序排列)。
更复杂的服务可以让工作线程接收请求并将它们放置在队列中,其中单个线程拾取请求并将它们传递给真正的服务。
由于调用之间需要延迟,我建议使用java.util.Timer
or java.util.concurrent.ScheduledThreadPoolExecutor
。Timer
非常简单,非常适合这个用例。但是,如果稍后确定了额外的调度要求,那么一个人Executor
就可以处理所有这些要求。在任何一种情况下,都使用固定延迟方法,而不是固定速率方法。
循环任务轮询请求对象的并发队列。如果有挂起的请求,任务会执行它,并通过回调返回结果。服务的查询和要调用的回调是请求对象的成员。
应用程序保留对共享队列的引用。要安排请求,只需将其添加到队列中。
只是为了澄清一下,如果执行计划任务时队列为空,则不会发出请求。简单的方法是结束任务,调度程序将在一秒钟后调用任务再次检查。
但是,这意味着启动任务最多可能需要一秒钟,即使最近没有处理任何请求。如果这种不必要的延迟是无法忍受的,那么编写自己的线程可能比使用Timer
or更可取ScheduledThreadPoolExecutor
。在您自己的计时循环中,如果您选择阻塞空队列直到请求可用,您可以更好地控制调度。内置计时器不能保证在上一次执行完成后等待整整一秒;他们通常相对于任务的开始时间进行调度。
如果您想到的是第二种情况,那么您的run()
方法将包含一个循环。每次迭代都从阻塞队列开始,直到收到请求,然后记录时间。处理请求后,再次检查时间。如果时间差小于一秒,则休眠剩余的时间。此设置假定在一个请求的开始和下一个请求之间需要一秒钟的延迟。如果在一个请求结束和下一个请求之间需要延迟,则不需要检查时间;只睡一秒钟。
还有一点需要注意的是,该服务可能能够在一个请求中接受多个查询,这将减少开销。如果是这样,则通过阻塞take()
第一个元素来利用这一点,然后使用poll()
,可能会以非常短的阻塞时间(5 毫秒左右)来查看应用程序是否正在发出更多请求。如果是这样,这些可以捆绑在对服务的单个请求中。如果queue
是 a BlockingQueue<? extends Request>
,它可能看起来像这样:
Collection<Request> bundle = new ArrayList<Request>();
bundle.add(queue.take());
while (bundle.size() < BUNDLE_MAX) {
Request req = queue.poll(EXTRA, TimeUnit.MILLISECONDS);
if (req == null)
break;
bundle.add(req);
}
/* Now make one service request with contents of "bundle". */