javascript
用Spring长轮询Tomcat
就像喜劇演員弗蘭基· 豪威爾 ( Frankie Howerd)所說的“哦,小姐小姐” ,但足夠多的英國影射和雙重誘惑,因?yàn)殚L輪詢雄貓對隔壁的悶氣不是某種性偏見,這是一種技術(shù)(或更像是一種駭客)由于喬布斯(Steve Jobs)拒絕在iPhone和iPad上支持Adobe Flash Player而開發(fā)的 。 事實(shí)是,將Flash Player用作Web應(yīng)用程序的一部分是一種很好的支持“ 發(fā)布和訂閱”范例的方式,因?yàn)樗軌驖M足那些需要實(shí)時更新的情況,例如實(shí)時股票價格,新聞更新和更改。博彩賠率,而直接HTTP及其請求/回復(fù)范例是支持靜態(tài)頁面的一種好方法。 許多公司在開發(fā)使用Flash的應(yīng)用程序方面花費(fèi)了大量精力,以便為用戶提供實(shí)時數(shù)據(jù)。 當(dāng)蘋果公司宣布iOS將不支持Adobe Flash時,如果沒有iPhone解決方案,它們就會變得干and無力,并且要重新進(jìn)入移動市場,我想其中有很多經(jīng)過長時間的投票。
那么,什么是長期 投票 ? 嗯,這不是來自華沙的高個子,其想法是模仿“發(fā)布和訂閱”模式。 場景如下:
我懷疑Spring的Guy不太熱衷于“長期投票”一詞,因?yàn)樗麄兏降貙⒋思夹g(shù)稱為異步請求處理
通過查看上面的“長輪詢”或“ 異步請求處理”流程,您可能會猜到服務(wù)器將發(fā)生什么。 每次您強(qiáng)迫服務(wù)器等待數(shù)據(jù)可用時,都會占用其一些寶貴資源。 如果您的網(wǎng)站很受歡迎并且負(fù)擔(dān)很重,那么等待更新消耗的資源數(shù)量會激增,因此服務(wù)器可能會用盡并崩潰。
在2月和3月,我寫了一系列有關(guān)Producer Consumer模式的博客,這似乎是進(jìn)行長期投票的理想情況。 如果您還沒有閱讀我的Producer Consumer模式博客,請在這里查看
在那種原始情況下,我說過“電視公司會向每個游戲發(fā)送記者,以將實(shí)時更新反饋到系統(tǒng)中,然后將其發(fā)送回演播室。 到達(dá)工作室后,更新將被放入隊列中,然后由電傳打字機(jī)顯示在屏幕上?!?
隨著時代的變遷,電視公司希望用Web應(yīng)用程序替換舊的Teletype,該應(yīng)用程序可以實(shí)時顯示比賽更新。
在這種新情況下,電視公司總裁聘請了Agile Cowboys inc的朋友來整理更新。 為了使事情變得更容易,他為他們提供了Message , Match和MatchReporter類的源代碼,這些類在新項目中可以重復(fù)使用。 Agile Cowboys的首席執(zhí)行官聘請了一些新開發(fā)人員來完成這項工作: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() ,該方法帶有兩個參數(shù)。 瀏覽器使用第一個訂閱匹配更新數(shù)據(jù)供稿,而第二個是用于輪詢服務(wù)器以獲取更新的URL。 從JQuery ready(…)函數(shù)調(diào)用此代碼。
$(document).ready(function() {var startUrl = "matchupdate/subscribe";var pollUrl = "matchupdate/simple";var poll = new Poll();poll.start(startUrl,pollUrl);});調(diào)用start()方法時,它將向服務(wù)器發(fā)出Ajax請求以訂閱匹配更新。 當(dāng)服務(wù)器以簡單的“ OK”答復(fù)時, request.done(…)處理程序通過使用帶有匿名函數(shù)作為參數(shù)的setInterval(…)啟動1/2秒計時器。 此函數(shù)使用一個簡單的標(biāo)志“ allow ”,如果為true,則允許調(diào)用getUpdate()方法。 然后將allow標(biāo)志設(shè)置為false,以確保不存在重入問題。
getUpdate(…)函數(shù)使用上述第二個URL參數(shù)對服務(wù)器進(jìn)行另一個Ajax調(diào)用。 這次request.done(…)處理程序獲取匹配更新并將其轉(zhuǎn)換為HTML,并將其插入到“ first_row ” div之后以在屏幕上顯示。
回到場景, 敏捷牛仔公司的首席執(zhí)行官想打動他的新女友,于是他給她買了一輛保時捷911 。 現(xiàn)在他無法用自己的現(xiàn)金來支付,因?yàn)樗钠拮訒l(fā)現(xiàn)發(fā)生了什么事,所以他用電視公司交易中的一部分現(xiàn)金來支付。 這意味著他只能負(fù)擔(dān)一名應(yīng)屆畢業(yè)生來編寫服務(wù)器端代碼。 這位畢業(yè)生可能沒有經(jīng)驗(yàn),但是他確實(shí)重用了Message , Match和MatchReporter類以提供匹配更新。 請記住,將Queue和Match注入到MatchReporter 。 調(diào)用MatchReporter.start()方法時,它將加載匹配項并讀取更新消息,并在其中檢查其時間戳,并在適當(dāng)?shù)臅r候?qū)⑵涮砑拥疥犃兄小?如果您想查看MatchReporter , Match等的代碼,請查看原始博客 。
然后,該研究生將創(chuàng)建一個簡單的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()僅調(diào)用SimpleMatchUpdateService來訂閱比賽更新,而第二個getUpdate()向SimpleMatchUpdateService請求下一個比賽更新。 看到這一點(diǎn),您可能會猜測所有工作都是由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的實(shí)例實(shí)現(xiàn)的。 這意味著,如果瀏覽器發(fā)出請求時沒有可用的更新,則請求線程將阻塞queue.take()方法,從而占用寶貴的服務(wù)器資源。 可用更新時, queue.take()返回并將Message發(fā)送到瀏覽器。 對于沒有經(jīng)驗(yàn)的研究生培訓(xùn)生來說,一切似乎都很好,并且該代碼可以生效。 在接下來的周六,這是英超聯(lián)賽的開始(如果您在美國,則是足球比賽),這是體育日歷最繁忙的周末之一,并且大量用戶希望獲得有關(guān)大型比賽的最新信息。 當(dāng)然,服務(wù)器資源不足,無法處理負(fù)載并不斷崩潰。 電視公司的總裁對此不太滿意,因此召集了敏捷牛仔公司的首席執(zhí)行官到他的辦公室。 他明確表示,如果不解決此問題,血液將流動。 Agile Cowboys的首席執(zhí)行官意識到了自己的錯誤,并在與女友吵架后收回了保時捷。 然后,他通過電子郵件向Java / Spring顧問發(fā)送電子郵件,如果他要來修復(fù)代碼,則向他提供保時捷。 Spring顧問不能拒絕這樣的提議并接受。 這主要是因?yàn)樗繱ervlet 3規(guī)范通過允許將ServletRequest置于異步模式來解決了這個問題。 這樣可以釋放服務(wù)器資源,但可以保持ServletResponse打開,從而允許其他一些第三方線程來完成處理。 他還知道Spring的家伙們在Spring 3.2中提出了一種新技術(shù),稱為“延遲結(jié)果”,該技術(shù)專為這些情況而設(shè)計。 同時,Agile Cowboys CEO的前女友仍對失去保時捷感到不安,并通過電子郵件將其妻子的全部情況告訴了她,告知她丈夫的外遇……
當(dāng)這個博客變成達(dá)拉斯的一集時,我認(rèn)為它的時代到此結(jié)束。 那么,代碼會及時修復(fù)嗎? Java / Spring顧問會花太多時間駕駛他的新保時捷嗎? 首席執(zhí)行官會原諒他的女朋友嗎? 他的妻子會與他離婚嗎? 有關(guān)這些問題的答案,以及下次有關(guān)Spring的DeferredResult技術(shù)調(diào)整的更多信息, …
您可能已經(jīng)注意到示例代碼中還有另一個巨大的漏洞,即只有一個訂閱者的事實(shí)。 由于這僅是示例代碼,而我在談?wù)摰氖情L時間輪詢,而不是實(shí)現(xiàn)發(fā)布和訂閱,因此問題出在“主題之外”。 我稍后可能會(也可能不會)對其進(jìn)行修復(fù)。
該博客隨附的代碼可在Github上找到:https://github.com/roghughe/captaindebug/tree/master/long-poll
翻譯自: https://www.javacodegeeks.com/2013/08/long-polling-tomcat-with-spring.html
總結(jié)
以上是生活随笔為你收集整理的用Spring长轮询Tomcat的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 情侣共同存钱的方法
- 下一篇: 吸收Mockito的流利度