就像喜剧演员弗兰基· 豪威尔 ( Frankie Howerd)所说的“哦,小姐小姐” ,但足够多的英国影射和双重诱惑,因为长轮询雄猫对隔壁的闷气不是某种性偏见,这是一种技术(或更像是一种骇客)由于乔布斯(Steve Jobs)拒绝在iPhone和iPad上支持Adobe Flash Player而开发的 。 事实是,将Flash Player用作Web应用程序的一部分是一种很好的支持“ 发布和订阅”范例的方式,因为它能够满足那些需要实时更新的情况,例如实时股票价格,新闻更新和更改。博彩赔率,而直接HTTP及其请求/回复范例是支持静态页面的一种好方法。 许多公司在开发使用Flash的应用程序方面花费了大量精力,以便为用户提供实时数据。 当苹果公司宣布iOS将不支持Adobe Flash时,如果没有iPhone解决方案,它们就会变得干and无力,并且要重新进入移动市场,我想其中有很多经过长时间的投票。
那么,什么是长期 投票 ? 嗯,这不是来自华沙的高个子,其想法是模仿“发布和订阅”模式。 场景如下:
- 浏览器从服务器请求一些数据。
- 服务器没有可用的数据,并允许请求挂起。
- 稍后,响应数据可用,服务器完成请求。
- 浏览器一接收到数据,便立即显示该数据,然后立即请求更新。
- 现在,流程循环回到点2。
我怀疑Spring的Guy不太热衷于“长期投票”一词,因为他们更正式地将此技术称为异步请求处理
通过查看上面的“长轮询”或“ 异步请求处理”流程,您可能会猜到服务器将发生什么。 每次您强迫服务器等待数据可用时,都会占用其一些宝贵资源。 如果您的网站很受欢迎并且负担很重,那么等待更新消耗的资源数量会激增,因此服务器可能会用尽并崩溃。
在2月和3月,我写了一系列有关Producer Consumer模式的博客,这似乎是进行长期投票的理想情况。 如果您还没有阅读我的Producer Consumer模式博客,请在这里查看
在那种原始情况下,我说过“电视公司会向每个游戏发送记者,以将实时更新反馈到系统中,然后将其发送回演播室。 到达工作室后,更新将被放入队列中,然后由电传打字机显示在屏幕上。”
随着时代的变迁,电视公司希望用Web应用程序替换旧的Teletype,该应用程序可以实时显示比赛更新。
在这种新情况下,电视公司总裁聘请了Agile Cowboys inc的朋友来整理更新。 为了使事情变得更容易,他为他们提供了Message
, Match
和MatchReporter
类的源代码,这些类在新项目中可以重复使用。 Agile Cowboys的首席执行官聘请了一些新开发人员来完成这项工作:JavaScript,JQuery,CSS和HTML的专家来做前端,Spring MVC Webapp的人来做Java。
前端专家提供了以下JavaScript轮询代码:
var allow = true;
var startUrl;
var pollUrl; function Poll() { this.start = function start(start, poll) { startUrl = start; pollUrl = poll; if (request) { request.abort(); // abort any pending request } // fire off the request to MatchUpdateController var request = $.ajax({ url : startUrl, type : "get", }); // This is jQuery 1.8+ // callback handler that will be called on success request.done(function(reply) { console.log("Game on..." + reply); setInterval(function() { if (allow === true) { allow = false; getUpdate(); } }, 500); }); // callback handler that will be called on failure request.fail(function(jqXHR, textStatus, errorThrown) { // log the error to the console console.log("Start - the following error occured: " + textStatus, errorThrown); }); }; function getUpdate() { console.log("Okay let's go..."); if (request) { request.abort(); // abort any pending request } // fire off the request to MatchUpdateController var request = $.ajax({ url : pollUrl, type : "get", }); // This is jQuery 1.8+ // callback handler that will be called on success request.done(function(message) { console.log("Received a message"); var update = getUpdate(message); $(update).insertAfter('#first_row'); }); function getUpdate(message) { var update = "<div class='span-4 prepend-2'>" + "<p class='update'>Time:</p>" + "</div>" + "<div class='span-3 border'>" + "<p id='time' class='update'>" + message.matchTime + "</p>" + "</div>" + "<div class='span-13 append-2 last' id='update-div'>" + "<p id='message' class='update'>" + message.messageText + "</p>" + "</div>"; return update; }; // callback handler that will be called on failure request.fail(function(jqXHR, textStatus, errorThrown) { // log the error to the console console.log("Polling - the following error occured: " + textStatus, errorThrown); }); // callback handler that will be called regardless if the request failed or succeeded request.always(function() { allow = true; }); };
};
名为Poll
的类具有一个方法start()
,该方法带有两个参数。 浏览器使用第一个订阅匹配更新数据供稿,而第二个是用于轮询服务器以获取更新的URL。 从JQuery ready(…)
函数调用此代码。
$(document).ready(function() {var startUrl = "matchupdate/subscribe";var pollUrl = "matchupdate/simple";var poll = new Poll();poll.start(startUrl,pollUrl);});
调用start()
方法时,它将向服务器发出Ajax请求以订阅匹配更新。 当服务器以简单的“ OK”答复时, request.done(…)
处理程序通过使用带有匿名函数作为参数的setInterval(…)
启动1/2秒计时器。 此函数使用一个简单的标志“ allow
”,如果为true,则允许调用getUpdate()
方法。 然后将allow
标志设置为false,以确保不存在重入问题。
getUpdate(…)
函数使用上述第二个URL参数对服务器进行另一个Ajax调用。 这次request.done(…)
处理程序获取匹配更新并将其转换为HTML,并将其插入到“ first_row
” div之后以在屏幕上显示。
回到场景, 敏捷牛仔公司的首席执行官想打动他的新女友,于是他给她买了一辆保时捷911 。 现在他无法用自己的现金来支付,因为他的妻子会发现发生了什么事,所以他用电视公司交易中的一部分现金来支付。 这意味着他只能负担一名应届毕业生来编写服务器端代码。 这位毕业生可能没有经验,但是他确实重用了Message
, Match
和MatchReporter
类以提供匹配更新。 请记住,将Queue
和Match
注入到MatchReporter
。 调用MatchReporter.start()
方法时,它将加载匹配项并读取更新消息,并在其中检查其时间戳,并在适当的时候将其添加到队列中。 如果您想查看MatchReporter
, Match
等的代码,请查看原始博客 。
然后,该研究生将创建一个简单的Spring比赛更新控制器
@Controller()
public class SimpleMatchUpdateController { private static final Logger logger = LoggerFactory.getLogger(SimpleMatchUpdateController.class); @Autowired private SimpleMatchUpdateService updateService; @RequestMapping(value = "/matchupdate/subscribe" + "", method = RequestMethod.GET) @ResponseBody public String start() { updateService.subscribe(); return "OK"; } /** * Get hold of the latest match report - when it arrives But in the process * hold on to server resources */ @RequestMapping(value = "/matchupdate/simple", method = RequestMethod.GET) @ResponseBody public Message getUpdate() { Message message = updateService.getUpdate(); logger.info("Got the next update in a really bad way: {}", message.getMessageText()); return message; }
}
SimpleMatchUpdateController
包含两个非常简单的方法。 第一个start()
仅调用SimpleMatchUpdateService
来订阅比赛更新,而第二个getUpdate()
向SimpleMatchUpdateService
请求下一个比赛更新。 看到这一点,您可能会猜测所有工作都是由SimpleMatchUpdateService
完成的。
@Service("SimpleService")
public class SimpleMatchUpdateService { @Autowired @Qualifier("theQueue") private LinkedBlockingQueue<Message> queue; @Autowired @Qualifier("BillSkyes") private MatchReporter matchReporter; /** * Subscribe to a match */ public void subscribe() { matchReporter.start(); } /** * * Get hold of the latest match update */ public Message getUpdate() { try { Message message = queue.take(); return message; } catch (InterruptedException e) { throw new UpdateException("Cannot get latest update. " + e.getMessage(), e); } } }
SimpleMatchUpdateService
也包含两个方法。 第一个, MatchReporter
subscribe()
,告诉MatchReporter
开始将更新放入队列中。 第二个getUpdate()
从Queue
删除下一个更新,并将其作为JSON返回给浏览器以显示。
到目前为止,一切都很好; 但是,在这种情况下,队列是由LinkedBlockingQueue
的实例实现的。 这意味着,如果浏览器发出请求时没有可用的更新,则请求线程将阻塞queue.take()
方法,从而占用宝贵的服务器资源。 可用更新时, queue.take()
返回并将Message
发送到浏览器。 对于没有经验的研究生培训生来说,一切似乎都很好,并且该代码可以生效。 在接下来的周六,这是英超联赛的开始(如果您在美国,则是足球比赛),这是体育日历最繁忙的周末之一,并且大量用户希望获得有关大型比赛的最新信息。 当然,服务器资源不足,无法处理负载并不断崩溃。 电视公司的总裁对此不太满意,因此召集了敏捷牛仔公司的首席执行官到他的办公室。 他明确表示,如果不解决此问题,血液将流动。 Agile Cowboys的首席执行官意识到了自己的错误,并在与女友吵架后收回了保时捷。 然后,他通过电子邮件向Java / Spring顾问发送电子邮件,如果他要来修复代码,则向他提供保时捷。 Spring顾问不能拒绝这样的提议并接受。 这主要是因为他知道Servlet 3规范通过允许将ServletRequest
置于异步模式来解决了这个问题。 这样可以释放服务器资源,但可以保持ServletResponse
打开,从而允许其他一些第三方线程来完成处理。 他还知道Spring的家伙们在Spring 3.2中提出了一种新技术,称为“延迟结果”,该技术专为这些情况而设计。 同时,Agile Cowboys CEO的前女友仍对失去保时捷感到不安,并通过电子邮件将其妻子的全部情况告诉了她,告知她丈夫的外遇……
当这个博客变成达拉斯的一集时,我认为它的时代到此结束。 那么,代码会及时修复吗? Java / Spring顾问会花太多时间驾驶他的新保时捷吗? 首席执行官会原谅他的女朋友吗? 他的妻子会与他离婚吗? 有关这些问题的答案,以及下次有关Spring的DeferredResult
技术调整的更多信息, …
您可能已经注意到示例代码中还有另一个巨大的漏洞,即只有一个订阅者的事实。 由于这仅是示例代码,而我在谈论的是长时间轮询,而不是实现发布和订阅,因此问题出在“主题之外”。 我稍后可能会(也可能不会)对其进行修复。
该博客随附的代码可在Github上找到:https://github.com/roghughe/captaindebug/tree/master/long-poll
翻译自: https://www.javacodegeeks.com/2013/08/long-polling-tomcat-with-spring.html