【问题标题】:How to ignore multiple clicks from an impatient user?如何忽略不耐烦用户的多次点击?
【发布时间】:2016-12-27 16:47:09
【问题描述】:

我有一个查询来回答远程客户的标准请求。标准,因为它不需要来自服务器外部的参数。每当 任何人向 URL 提交请求,例如 http://www.example.com/query,他/她会在响应正文中获取 reply.xml 的内容, 取决于当时数据库提供的内容。 reply.xml 的内容仅在服务器上的数据库内容中发生变化,不会在任何外部内容上发生变化 就像谁进行查询,输入哪个等,因此不会从客户端获取任何参数。我什至没有检查任何身份验证——我们将所有内容都留给了防火墙。

所以我写了一个@POST 方法,比如query(),以便在发布到http://www.example.com/query 的此类请求上被调用,并传递结果。我使用了Jersey,一切都符合规格,除了

请求应该是跨时间排他的。 也就是说,一次应该服务一个请求——除非服务器没有运行由我的方法query() 调用的查询过程,否则后续用户单击应该会收到一条HTTP 状态为309 的消息。

如何做到这一点?我尝试让query() 服务@PUT 而不是@POST 响应,并得到了相同的结果。

也许是关于这个话题的一个天真的问题。但是,对 Restful 服务不是很熟悉。

我可以通过对令牌进行线程化来控制一次只运行一个查询并让同时请求接收 HTTP 309 来做到这一点。但是必须有一种更好、更简单的方法来在服务器上实现这一点。

我使用的是 Tomcat 8,泽西岛 1.19。

TIA。

注意:我已经阅读了PUT vs POST in REST 以及其他一些有用的讨论。

//=======================

编辑:

哪个用户提交查询在任何时候都没有任何区别。

假设userA 提交了一个查询。虽然该查询仍在运行,即在query() 返回对userA 的响应之前,userB 提交了一个查询。 userB 应该得到一个 309——只是因为当时正在处理一个查询。

userA = userBuserA userB 在这里无关紧要,应该返回 309,因为有一个查询请求,而查询请求已经在运行。这是用户获得 309 的唯一一次。

//=============================================== =

EDIT-2:

我知道带有并发控制的解决方案。我猜有一个使用 Restful 功能。这是一个学术问题。

【问题讨论】:

  • 您希望针对不同用户或再次运行查询的同一用户的查询为 309?
  • @RealSkeptic 是否是相同/不同的用户并不重要。请在 Q 中查看我的编辑。
  • 好吧,如果它们在同一个 JVM 中运行,我想你可以使用 ReentrantLock。
  • @RC 费率无关紧要。我们不应该限制它。我们唯一应该避免的是我在 Q 中所说的同时发生的事情。

标签: java rest tomcat jersey


【解决方案1】:
  • @Koos Gadellaa 说客户端应该阻止是正确的 第二个请求,直到响应到达。让我解释一下为什么这是最好的做法。 在架构上,它与关注点有关。服务器没有关于为什么两个请求并行到达的上下文感知。因此,它依赖于带外知识来知道并行请求是不好的。任何带外知识都会产生耦合,这意味着如果您更改系统的一个部分的工作方式,则必须更改另一部分。 RESTful 架构很受欢迎,因为它们减少了耦合。如果同一用户登录到两个客户端,则系统会中断。您永远不想使用这种类型的客户端-服务器耦合来构建系统。

  • 关于服务器的责任,良好的编码实践开始发挥作用,最好的办法是确保服务不受来自用户的多个并行请求的阻碍。缓存可能是你的朋友。根据查询参数,可以将响应写入磁盘上的缓存文件。客户端将始终使用 HTTP 303 重定向到缓存文件 URL。可以使用 304 Not Modified,这样客户端就不必下载两次答案。在这种情况下,唯一的带外知识是正确实现 HTTP 规范,该规范明确且稳健。

  • 如果服务超载,相应的响应代码似乎是 503。

    10.5.4 503 Service Unavailable

    由于服务器临时过载或维护,服务器当前无法处理请求。这意味着这是一种暂时的情况,经过一段时间的延迟会得到缓解。如果已知,延迟的长度可以在 Retry-After 标头中指示。如果没有给出 Retry-After,客户端应该像处理 500 响应一样处理响应。

         Note: The existence of the 503 status code does not imply that a
         server must use it when becoming overloaded. Some servers may wish
         to simply refuse the connection.
    
  • 既然您是从my response here 指导我到这里的,看来您想知道正确的 RESTful 方法。这将比上面的解决方案更复杂,我猜你不想走这条路,但就是这样。

    如果服务器需要向客户端传达正在执行的请求以及请求何时完成,则需要为这些概念创建资源。这感觉很奇怪,因为它们的概念存在于 HTTP 中,并且在更高级别重新实现它们很奇怪(架构气味)。

    1. 客户端会将RequestQuery 资源发布到服务器。 该资源将有一个为空的Response 属性和一个为空的Status 属性。服务器将响应一个将Status 属性设置为“处理”的正文。
    2. 然后,客户端将对该资源执行 GET(可能在此处使用长轮询)以检查更新。
    3. 创建响应后,Response 属性将链接到响应资源或嵌入响应。

    这种方法使用资源来传达正在发生的事情。因此,唯一的带外知识是资源的有效状态

【讨论】:

    【解决方案2】:

    我认为你的构想是错误的。多次点击的用户是使用前端客户端连接到您的服务器的用户。因此,由客户端来确保不会发生多次点击。 Javascript 可以完美地抵御此类请求。

    话虽如此,根据您的要求:应该返回 309 的事实。这是一个并发问题。
    如果我正确阅读了您的规格,则它与正在执行的数据库查询有关。 考虑以下代码:

    result = do_database_thing();
    out = make_up_result(result);
    return out;
    

    根据这些规范,唯一的要求是 critical section 在 do_database_thing 上。如果 make_up_results 很慢,则允许多个请求。所以自然的解决方案是对数据库使用锁定。如果它应该包含整个 query() 方法(即不是数据库查询本身),请尽早请求锁,最后释放锁。 那么代码就变成了:

    boolean locked = get_lock();
    if(!locked) return 302;
    result = do_database_thing();
    release_lock();
    out = make_up_result(result);
    return out;
    

    【讨论】:

    • 您正在对规格进行假设。不要。
    • 客户可以以任何他/她想要的方式提交查询。这取决于他们。该 url 可能只是同时点击了两个浏览器。或者一个应用程序一个接一个地发送请求只是为了它。我们关心的不是数据库结果的一致性。
    • 嗯....锁不是(直接)用于防止数据库的一致性,它们是为了防止从多个来源访问单个共享资源。当您对共享资源具有单一访问权限时,您所做的任何更新(通常)都是一致的,因此这是具有单一访问权限的结果,而这又由锁保证。这就是为什么我建议在这种情况下使用锁:以保证单次访问。不是为了更新或一致性,而只是作为一个特殊的令牌,在资源上可用,以独占方式访问,在该资源上管理,并在该资源上验证。
    • 好吧 .. 不仅仅是为您的答案提供理由。不需要为了锁而关心 db 更不用说不好的做法了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-23
    • 2010-09-14
    • 2021-05-03
    • 2016-01-12
    • 2013-03-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多