Java servlet通过事件驱动进行高性能长轮询详解

目录
  • servlet3.0的异步原理
  • 使用servlet3.0实现长轮询
  • 长轮询实现

servlet3.0的异步原理

servlet基础就不做介绍了,这里就介绍servlet3.0的一个重要的新特性:异步。

servlet3.0原理图:

  • tomcat接收到客户端的请求后会将请求AsyncContext交给业务线程,这样tomcat工作线程就能释放出来处理其它请求的连接。
  • 业务线程池接收到AsyncContext后,就可以处理请求业务,完成业务逻辑后,根据AsyncContext获取response,返回响应结果。
  • AsyncListener会监听AsyncContext的行为,我们可以根据具体的行为做出对应的业务处理。

servlet3.0将tomcat工作线程和业务线程分隔开来,这样tomcat工作线程就能处理更多的连接请求。业务线程主要处理业务逻辑。在这种模式下,可以更好的分配业务线程的数量,也能根据不同的业务,设置不同的线程数量,更加灵活。

注意:tomcat的NIO和servlet3.0的异步没有关系。tomcat NIO模式,是对于http连接的处理使用,目的是用更少的线程处理更多的连接。servlet3.0是在tomcat工作线程的处理逻辑上实现异步处理功能。

使用servlet3.0实现长轮询

什么是长轮询:

  • 长轮询是指客户端会一直向服务端发起请求,适用与服务端向客户端推送数据使用。长轮询要满足以下几点: 客户端发起请求后,当服务端业务没有数据时,不会立即返回空值,而是hold住连接,等待数据生成后立即返回。
  • 请求在服务端有超时时间,不会一直hold住。当超时后,服务端会返回超时信息,客户端收到返回后会再次发起请求。
  • 每次请求结束后,客户端会再次发起请求。

短轮询、长轮询和长连接比较:

  • 短轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。

优点:后端程序编写比较容易,适于小型应用。。

缺点:请求中有大半是无用,浪费带宽和服务器资源。

  • 长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

优点:在无消息的情况下不会频繁的请求。

缺点:服务器hold连接会消耗资源。

  • 长连接:客户端与服务端建立长连接socket

优点:可靠性高,实时性高。

缺点:实现复杂,要维护心跳,服务器维持连接消耗资源。

长轮询实现

原理图:

  • 请求过来之后,生成事件,加入对应的事件集合。请求设置30s超时时间,并添加监听。tomcat工作线程释放。
  • 当服务端数据准备好之后,触发对应事件,从容器获取订阅事件进行执行。完成后返回response。
  • 请求超时,listener触发,返回超时信息。

下面看下具体实现:

事件定义,这里只是定义一个简单的事件:

package com.hiwe.demo.event;
import javax.servlet.AsyncContext;
public class HttpEvent {
    /**
     * 可以是业务数据主键,这里用请求名称做个简单demo
     */
    private String requestName;
    private AsyncContext asyncContext;
    public HttpEvent(String requestName,AsyncContext asyncContext){
        this.requestName = requestName;
        this.asyncContext = asyncContext;
    }
    public String getRequestName() {
        return requestName;
    }
    public AsyncContext getAsyncContext() {
        return asyncContext;
    }
}

事件管理器:

package com.hiwe.demo.event;
import javax.servlet.AsyncContext;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
public class EventManager {
    private final static Map<String,HttpEvent> subHttpEvents = new HashMap<>();
    /**
     * 新增事件订阅
     * @param event
     */
    public static void addHttpEvent(HttpEvent event){
        subHttpEvents.put(event.getRequestName(),event);
    }
    /**
     * 触发事件
     * @param requestName
     */
    public static void onEvent(String requestName){
        HttpEvent httpEvent = subHttpEvents.get(requestName);
        if(httpEvent==null){
            return;
        }
        AsyncContext asyncContext = httpEvent.getAsyncContext();
        try {
            PrintWriter writer = asyncContext.getResponse().getWriter();
            writer.print(requestName+" request success!");
            writer.flush();
            asyncContext.complete();
            subHttpEvents.remove(requestName);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

异步请求监听器:

package com.hiwe.demo.listener;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.PrintWriter;
@WebListener
public class AppAsyncListener implements AsyncListener {
    @Override
    public void onComplete(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onComplete");
        // we can do resource cleanup activity here
    }
    @Override
    public void onError(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onError");
        //we can return error response to client
    }
    @Override
    public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onStartAsync");
        //we can log the event here
    }
    /**
     * 超时触发
     * @param asyncEvent
     * @throws IOException
     */
    @Override
    public void onTimeout(AsyncEvent asyncEvent) throws IOException {
        AsyncContext asyncContext = asyncEvent.getAsyncContext();
        ServletResponse response = asyncEvent.getAsyncContext().getResponse();
        PrintWriter out = response.getWriter();
        //返回code码,以便前端识别,并重建请求
        out.write(201+" longPolling timeout");
        out.flush();
        asyncContext.complete();
    }
}

长轮询接口:

package com.hiwe.demo.controller;
import com.hiwe.demo.listener.AppAsyncListener;
import com.hiwe.demo.event.EventManager;
import com.hiwe.demo.event.HttpEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/app")
public class AsyncController {
    /**
     * 长轮询接口
     * @param requestName
     * @param request
     * @param response
     */
    @GetMapping("/asyncGet")
    public void getDemo(@RequestParam(value = "requestName") String requestName, HttpServletRequest request, HttpServletResponse response){
        //开启异步支持
        request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
        AsyncContext asyncContext = request.startAsync();
        //添加监听器
        asyncContext.addListener(new AppAsyncListener());
        //设置超时时间
        asyncContext.setTimeout(30000);
        //添加到事件集合中去
        HttpEvent httpEvent = new HttpEvent(requestName, asyncContext);
        EventManager.addHttpEvent(httpEvent);
    }
    /**
     * 触发事件使用
     * @param requestName
     */
    @GetMapping("/trigger")
    public void triggerDemo(@RequestParam(value = "requestName") String requestName){
        EventManager.onEvent(requestName);
    }
}

以上一个简单的长轮询就实现了,我们可以进行一下测试:

启动应用后访问:http://localhost:8080/app/asyncGet?requestName=123

服务端因为数据未准备就绪,所以会hold住请求。当等待30s后会返回超时信息:

我们在30s内触发event:http://localhost:8080/app/trigger?requestName=123

返回:

以上整个长轮询实现完成了,如果有错误,欢迎指正!

到此这篇关于Java servlet通过事件驱动进行高性能长轮询详解的文章就介绍到这了,更多相关Java 高性能长轮询内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java如何使用ReentrantLock实现长轮询

    Java代码 1. ReentrantLock 加锁阻塞,一个condition对应一个线程,以便于唤醒时使用该condition一定会唤醒该线程 /** * 获取探测点数据,长轮询实现 * @param messageId * @return */ public JSONObject getToutData(String messageId) { Message message = toutMessageCache.get(messageId); if (message == null) {

  • 基于springboot 长轮询的实现操作

    springboot 长轮询实现 基于 @EnableAsync , @Sync @SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } @RequestMapping("/async") @RestControlle

  • Java servlet通过事件驱动进行高性能长轮询详解

    目录 servlet3.0的异步原理 使用servlet3.0实现长轮询 长轮询实现 servlet3.0的异步原理 servlet基础就不做介绍了,这里就介绍servlet3.0的一个重要的新特性:异步. servlet3.0原理图: tomcat接收到客户端的请求后会将请求AsyncContext交给业务线程,这样tomcat工作线程就能释放出来处理其它请求的连接. 业务线程池接收到AsyncContext后,就可以处理请求业务,完成业务逻辑后,根据AsyncContext获取respons

  • Java实现一个简单的长轮询的示例代码

    目录 分析一下长轮询的实现方式 长轮询与短轮询 配置中心长轮询设计 配置中心长轮询实现 客户端实现 服务端实现 分析一下长轮询的实现方式 现在各大中间件都使用了长轮询的数据交互方式,目前比较流行的例如Nacos的配置中心,RocketMQ Pull(拉模式)消息等,它们都是采用了长轮询方的式实现.就例如Nacos的配置中心,如何做到服务端感知配置变化实时推送给客户端的呢? 长轮询与短轮询 说到长轮询,肯定存在和它相对立的,我们暂且叫它短轮询吧,我们简单介绍一下短轮询: 短轮询也是拉模式.是指不管

  • java 常规轮询长轮询Long polling实现示例详解

    目录 正文 常规轮询 长轮询 正文 长轮询是与服务器保持持久连接的最简单的方式,它不使用任何特定的协议,例如 WebSocket 或者 Server Sent Event. 它很容易实现,在很多场景下也很好用. 常规轮询 从服务器获取新信息的最简单的方式是定期轮询.也就是说,定期向服务器发出请求:“你好,我在这儿,你有关于我的任何信息吗?”例如,每 10 秒一次. 作为响应,服务器首先通知自己,客户端处于在线状态,然后 —— 发送目前为止的消息包. 这可行,但是也有些缺点: 消息传递的延迟最多为

  • JS实现websocket长轮询实时消息提示的效果

    效果图如下: 参考代码如下: jsp代码: <%@ page contentType="text/html;charset=UTF-8" language="java"%> <div class="page-header navbar navbar-fixed-top"> <div class="page-header-inner"> <div class="page-log

  • Java中Spring Boot+Socket实现与html页面的长连接实例详解

    Spring Boot+Socket实现与html页面的长连接,客户端给服务器端发消息,服务器给客户端轮询发送消息,附案例源码 功能介绍 客户端给所有在线用户发送消息客户端给指定在线用户发送消息服务器给客户端发送消息(轮询方式) 注意:socket只是实现一些简单的功能,具体的还需根据自身情况,代码稍微改造下 项目搭建 项目结构图 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xml

  • jquery与php结合实现AJAX长轮询(LongPoll)

    HTTP是无状态.单向的协议,用户只能够通过客服端向服务器发送请求并由服务器处理发回一个响应.若要实现聊天室.WEBQQ.在线客服.邮箱等这些即时通讯的应用,就要用到" 服务器推送技术(Comet)". 传统的AJAX轮询方式,客服端以用户定义的时间间隔去服务器上查询最新的数据.种这种拉取数据的方式需要很短的时间间隔才能保证数据的精确度,但太短的时间间隔客服端会对服务器在短时间内发送出多个请求. 反转AJAX,就是所谓的长轮询或者COMET.服务器与客服端需要保持一条长时间的请求,它使

  • .Net MVC实现长轮询

    什么是长轮询? 长轮询是"服务器推"技术实现方式的一种,可以将服务端发生的变化实时传送到客户端而无须客户端频繁的地刷新.发送请求. 长轮询原理? 客户端向服务器发送Ajax请求,服务器接收到请求后,保持连接不返回消息,直到进行相关处理完毕后才返回响应信息并关闭连接,客户端接收到响应信息后,进行相关处理,处理完毕后再想服务器发送新的请求. 长轮询的应用场景? 长轮询常应用于Web及时通讯.监控.即时报价系统等需要实时将服务端的变化发送到客户端的场景. 长轮询的优缺点? 优点:无消息时不会

  • Thinkphp结合AJAX长轮询实现PC与APP推送详解

    前言 本文主要给大家介绍的关于Thinkphp结合AJAX长轮询实现PC与APP推送的相关内容,分享出来供大家参考学习,话不多说,来一起看看详细的介绍. 实现逻辑 某个操作(比如新建一条公告)后,触发同时推送消息给APP或是移动WEB的所有用户或指定用户. 不论性能,总还是有人会用到吧,实现如下(基于Thinkphp5消息推送): PHP长轮询 /* * long轮询 API查询接口 */ public function id_log() { if (request()->isPost()) {

随机推荐