java 长轮询_基于springboot 长轮询的实现操作

java 长轮询_基于springboot 长轮询的实现操作springboot长轮询实现基于@EnableAsync,@Sync@SpringBootApplication@EnableAsyncpublicclassDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);}}@Req…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

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 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 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> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create());

//模拟长轮询

@RequestMapping(value = “/watch/{namespace}”, method = RequestMethod.GET, produces = “text/html”)

public DeferredResult watch(@PathVariable(“namespace”) String namespace) {

logger.info(“Request received”);

DeferredResult 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> deferredResults = watchRequests.get(namespace);

Long time = System.currentTimeMillis();

//通知所有watch这个namespace变更的长轮训配置变更结果

for (DeferredResult 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长轮询的方式会在服务器变更的时候主动推送给客户端,其他时间客户端是挂起请求的,这样同时满足了性能和实时性。

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/182077.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Socket 编程原理

    Socket 编程原理目录socket编程基本概念协议TCPUDPDNSICMPHTTPHTTPS编程流程socket函数socket编程基本概念socket编程即计算机网络编程,目的是使两台主机能够进行远程连接,既然要使两者产生联系,那么就要有至少一个信息发送端和一个信息接收端,因此形成了现在绝大多数socket编程都会用到的C/S架构(Client[客户端]/Server[服务端]),最典型的应用就是web服务器/客户端。在Unix/Linux中执行任何形式的I/O操作(比如网络连接)时,都是在读取

    2022年10月18日
  • 【SpringBoot】20、SpringBoot中打war包需要注意「建议收藏」

    【SpringBoot】20、SpringBoot中打war包需要注意「建议收藏」最近在做一个项目,遇到了项目打成war包的一个问题,项目创建时选择的时jar包方式,后因项目部署要求,需要打成war包部署,遇到很多坑,在此做一下记录一、修改打包方式原:<version>0.0.1-SNAPSHOT</version><packaging>jar</packaging>改后:<version>0.0.1-SNAPSHOT</version><packaging>war</p

  • 点击scrollview释放键盘触发touchesBegan方法

    点击scrollview释放键盘触发touchesBegan方法scrollView 本身继承了touch的响应事件,要从新自定义scrollView 的响应事件。所以添加一个手势事件:-(void)addGestureRecognizer{  UITapGestureRecognizer*sigleTap=[[UITapGestureRecognizeralloc]initWithTarget

  • eclipse中改变默认的workspace的方法及说明

    eclipse中改变默然的workspace的方法可以有:1.在创建project的时候,手动选择使用新的workspace,如创建一个webproject,在向导中的Location选项,取消使

    2021年12月22日
  • STM32的NVIC和中断的总结「建议收藏」

    STM32的NVIC和中断的总结「建议收藏」前言:1.要想学习STM32中断,要先掌握STM32对优先级的定义;2.有51单片机开发经验会比较容易理解中断优先级;3.本篇博文基于STM32F103ZET6芯片和3.5.0标准库编写;4.本篇博文从寄存器入手,最终实现编程的步骤;如有不足之处,还请前辈多多指教;一基础知识1.cortex-m3支持256个中断,其中包含了16个内核中断,240个外部中断。(本博文只介绍

  • db4o官方停止支持及面向对象数据库的一些感想「建议收藏」

    db4o官方停止支持及面向对象数据库的一些感想

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号