用Spring长轮询Tomcat

就像喜剧演员弗兰基· 豪威尔Frankie Howerd)所说的“哦,小姐小姐” ,但足够多的英国影射和双重诱惑,因为长轮询雄猫对隔壁的闷气不是某种性偏见,这是一种技术(或更像是一种骇客)由于乔布斯(Steve Jobs)拒绝在iPhone和iPad上支持Adobe Flash Player而开发的 。 事实是,将Flash Player用作Web应用程序的一部分是一种很好的支持“ 发布和订阅”范例的方式,因为它能够满足那些需要实时更新的情况,例如实时股票价格,新闻更新和更改。博彩赔率,而直接HTTP及其请求/回复范例是支持静态页面的一种好方法。 许多公司在开发使用Flash的应用程序方面花费了大量精力,以便为用户提供实时数据。 当苹果公司宣布iOS将不支持Adobe Flash时,如果没有iPhone解决方案,它们就会变得干and无力,并且要重新进入移动市场,我想其中有很多经过长时间的投票。

那么,什么是长期 投票 ? 嗯,这不是来自华沙的高个子,其想法是模仿“发布和订阅”模式。 场景如下:

  1. 浏览器从服务器请求一些数据。
  2. 服务器没有可用的数据,并允许请求挂起。
  3. 稍后,响应数据可用,服务器完成请求。
  4. 浏览器一接收到数据,便立即显示该数据,然后立即请求更新。
  5. 现在,流程循环回到点2。

我怀疑Spring的Guy不太热衷于“长期投票”一词,因为他们更正式地将此技术称为异步请求处理

通过查看上面的“长轮询”或“ 异步请求处理”流程,您可能会猜到服务器将发生什么。 每次您强迫服务器等待数据可用时,都会占用其一些宝贵资源。 如果您的网站很受欢迎并且负担很重,那么等待更新消耗的资源数量会激增,因此服务器可能会用尽并崩溃。

在2月和3月,我写了一系列有关Producer Consumer模式的博客,这似乎是进行长期投票的理想情况。 如果您还没有阅读我的Producer Consumer模式博客,请在这里查看

在那种原始情况下,我说过“电视公司会向每个游戏发送记者,以将实时更新反馈到系统中,然后将其发送回演播室。 到达工作室后,更新将被放入队列中,然后由电传打字机显示在屏幕上。”

随着时代的变迁,电视公司希望用Web应用程序替换旧的Teletype,该应用程序可以实时显示比赛更新。

屏幕截图2013年8月19日在18.04.01

在这种新情况下,电视公司总裁聘请了Agile Cowboys inc的朋友来整理更新。 为了使事情变得更容易,他为他们提供了MessageMatchMatchReporter类的源代码,这些类在新项目中可以重复使用。 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 。 现在他无法用自己的现金来支付,因为他的妻子会发现发生了什么事,所以他用电视公司交易中的一部分现金来支付。 这意味着他只能负担一名应届毕业生来编写服务器端代码。 这位毕业生可能没有经验,但是他确实重用了MessageMatchMatchReporter类以提供匹配更新。 请记住,将QueueMatch注入到MatchReporter 。 调用MatchReporter.start()方法时,它将加载匹配项并读取更新消息,并在其中检查其时间戳,并在适当的时候将其添加到队列中。 如果您想查看MatchReporterMatch等的代码,请查看原始博客

然后,该研究生将创建一个简单的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值