基于springboot 长轮询的实现操作

springboot 长轮询实现

基于 @EnableAsync , @Sync

@SpringBootApplication
@EnableAsync
public class DemoApplication {
 public static void main(String[] args) {
 SpringApplication.run(DemoApplication.class, args);
 }
}
@RequestMapping("/async")
@RestController
public class AsyncRequestDemo {
 @Autowired
 private AsyncRequestService asyncRequestService;
 @GetMapping("/value")
 public String getValue() {
 String msg = null;
 Future<String> result = null;
 try{
  result = asyncRequestService.getValue();
  msg = result.get(10, TimeUnit.SECONDS);
 }catch (Exception e){
  e.printStackTrace();
 }finally {
  if (result != null){
  result.cancel(true);
  }
 }
 return msg;
 }
 @PostMapping("/value")
 public void postValue(String msg) {
 asyncRequestService.postValue(msg);
 }
}
@Service
public class AsyncRequestService {
 private String msg = null;
 @Async
 public Future<String> getValue() throws InterruptedException {
 while (true){
  synchronized (this){
  if (msg != null){
   String resultMsg = msg;
   msg = null;
   return new AsyncResult(resultMsg);
  }
  }
  Thread.sleep(100);
 }
 }
 public synchronized void postValue(String msg) {
 this.msg = msg;
 }
}

备注

@EnableAsync 开启异步

@Sync 标记异步方法

Future 用于接收异步返回值

result.get(10, TimeUnit.SECONDS); 阻塞,超时获取结果

Future.cancel() 中断线程

补充:通过spring提供的DeferredResult实现长轮询服务端推送消息

DeferredResult字面意思就是推迟结果,是在servlet3.0以后引入了异步请求之后,spring封装了一下提供了相应的支持,也是一个很老的特性了。DeferredResult可以允许容器线程快速释放以便可以接受更多的请求提升吞吐量,让真正的业务逻辑在其他的工作线程中去完成。

最近再看apollo配置中心的实现原理,apollo的发布配置推送变更消息就是用DeferredResult实现的,apollo客户端会像服务端发送长轮训http请求,超时时间60秒,当超时后返回客户端一个304 httpstatus,表明配置没有变更,客户端继续这个步骤重复发起请求,当有发布配置的时候,服务端会调用DeferredResult.setResult返回200状态码,然后轮训请求会立即返回(不会超时),客户端收到响应结果后,会发起请求获取变更后的配置信息。

下面我们自己写一个简单的demo来演示这个过程

springboot启动类:

@SpringBootApplication
public class DemoApplication implements WebMvcConfigurer {
 public static void main(String[] args) {
 SpringApplication.run(DemoApplication.class, args);
 } 

 @Bean
 public ThreadPoolTaskExecutor mvcTaskExecutor() {
 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 executor.setCorePoolSize(10);
 executor.setQueueCapacity(100);
 executor.setMaxPoolSize(25);
 return executor;

 }

 //配置异步支持,设置了一个用来异步执行业务逻辑的工作线程池,设置了默认的超时时间是60秒
 @Override
 public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
 configurer.setTaskExecutor(mvcTaskExecutor());
 configurer.setDefaultTimeout(60000L);
 }
}
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.Collection;

@RestController
public class ApolloController {
 private final Logger logger = LoggerFactory.getLogger(this.getClass());

 //guava中的Multimap,多值map,对map的增强,一个key可以保持多个value
 private Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create());

 //模拟长轮询
 @RequestMapping(value = "/watch/{namespace}", method = RequestMethod.GET, produces = "text/html")
 public DeferredResult<String> watch(@PathVariable("namespace") String namespace) {
 logger.info("Request received");
 DeferredResult<String> deferredResult = new DeferredResult<>();
 //当deferredResult完成时(不论是超时还是异常还是正常完成),移除watchRequests中相应的watch key
 deferredResult.onCompletion(new Runnable() {
  @Override
  public void run() {
  System.out.println("remove key:" + namespace);
  watchRequests.remove(namespace, deferredResult);
  }
 });
 watchRequests.put(namespace, deferredResult);
 logger.info("Servlet thread released");
 return deferredResult;

 }

 //模拟发布namespace配置
 @RequestMapping(value = "/publish/{namespace}", method = RequestMethod.GET, produces = "text/html")
 public Object publishConfig(@PathVariable("namespace") String namespace) {
 if (watchRequests.containsKey(namespace)) {
  Collection<DeferredResult<String>> deferredResults = watchRequests.get(namespace);
  Long time = System.currentTimeMillis();
  //通知所有watch这个namespace变更的长轮训配置变更结果
  for (DeferredResult<String> deferredResult : deferredResults) {
  deferredResult.setResult(namespace + " changed:" + time);
  }
 }
 return "success";

 }
}

当请求超时的时候会产生AsyncRequestTimeoutException,我们定义一个全局异常捕获类:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@ControllerAdvice
class GlobalControllerExceptionHandler {

 protected static final Logger logger = LoggerFactory.getLogger(GlobalControllerExceptionHandler.class);

 @ResponseStatus(HttpStatus.NOT_MODIFIED)//返回304状态码
 @ResponseBody
 @ExceptionHandler(AsyncRequestTimeoutException.class) //捕获特定异常
 public void handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e, HttpServletRequest request) {
 System.out.println("handleAsyncRequestTimeoutException");
 }
}

然后我们通过postman工具发送请求http://localhost:8080/watch/mynamespace,请求会挂起,60秒后,DeferredResult超时,客户端正常收到了304状态码,表明在这个期间配置没有变更过。

然后我们在模拟配置变更的情况,再次发起请求http://localhost:8080/watch/mynamespace,等待个10秒钟(不要超过60秒),然后调用http://localhost:8080/publish/mynamespace,发布配置变更。这时postman会立刻收到response响应结果:

mynamespace changed:1538880050147

表明在轮训期间有配置变更过。

这里我们用了一个MultiMap来存放所有轮训的请求,Key对应的是namespace,value对应的是所有watch这个namespace变更的异步请求DeferredResult,需要注意的是:在DeferredResult完成的时候记得移除MultiMap中相应的key,避免内存溢出请求。

采用这种长轮询的好处是,相比一直循环请求服务器,实例一多的话会对服务器产生很大的压力,http长轮询的方式会在服务器变更的时候主动推送给客户端,其他时间客户端是挂起请求的,这样同时满足了性能和实时性。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • springboot2.x整合tkmapper的示例代码

    springboot整合tkmapper 1.导入pom依赖 1.1 导入springboot的parent依赖 <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.1.9.RELEASE</version> </parent> 1.

  • SpringBoot项目如何把接口参数中的空白值替换为null值(推荐)

    问题发生 我们公司代码生成的时候,查询列表统一都是使用了setEntity() ,查询写法如下: public List<BasReservoirArea> selectList(BasReservoirArea basReservoirArea) { QueryWrapper<BasReservoirArea> where = new QueryWrapper<>(); where.setEntity(basReservoirArea); return baseMap

  • SpringBoot整合JDBC的实现

    简介 JDBC是最原基本的连接数据源的方式,在springboot中所有和数据源有关系的都在Spring Data家族中,所以我们看看springboot中如何使用JDBC来实现对数据库的增删改查操作. 简单使用 引入依赖 这里我们只引入基本的依赖就好,创建一个springboot项目(这里版本是2.1.6),然后添加以下依赖: <dependencies> <!--jdbc--> <dependency> <groupId>org.springframew

  • SpringBoot集成SpringMVC的方法示例

    Spring MVC是一款优秀的.基于MVC思想的应用框架,它是Spring的一个子框架.是当前最优秀的MVC框架. Spring Boot整合Spring MVC只需在pom.xml中引入 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.3.7.RE

  • SpringBoot中使用Servlet三大组件的方法(Servlet、Filter、Listener)

    本篇主要讲解SpringBoot当中使用Servlet三大组件,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧! 三大组件作用 1.Servlet Servlet是用来处理客户端请求的动态资源,也就是当我们在浏览器中键入一个地址回车跳转后,请求就会被发送到对应的Servlet上进行处理. Servlet的任务有: 1.接收请求数据:我们都知道客户端请求会被封装成HttpServletRequest对象,里面包含了请求头.参数等各种信息. 2.处理请求:通常我

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

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

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

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

  • javascript和jQuery实现网页实时聊天的ajax长轮询

    介绍 大家都知道,HTTP协议是一个属于应用层的面向对象的协议,HTTP 协议一共有五大特点: 1.支持客户/服务器模式; 2.简单快速; 3.灵活; 4.无连接; 5.无状态. 所以一次的请求都是一个单独的事件,和前后都没有联系.所以我们在解决网页实时聊天时就遇到一个问题,如何保证与服务器的长时间联系,从而源源不段地获取信息. 一直以来的方式无非有这么几种: 1.长连接,即服务器端不断开联系,PHP服务器端用ob系列函数来不停的读取输出,但是相当耗费服务器资源. 2.Flash socket,

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

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

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

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

  • .Net MVC实现长轮询

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

  • 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

  • PHP实现长轮询消息实时推送功能代码实例讲解

    本文实例讲述了PHP实现的消息实时推送功能.分享给大家供大家参考,具体如下: 入口文件index.html <!DOCTYPE HTML> <html> <head> <title>反ajax推送</title> <style> .send{color:#555;text-align: left;} .require{color:blue;text-align: right;} .content_box{text-align: cen

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

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

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

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

随机推荐