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

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


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

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

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

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

赞 (0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • max31865C语言程序,max31865温度传感器通信驱动代码「建议收藏」

    max31865C语言程序,max31865温度传感器通信驱动代码「建议收藏」最近研究maximmax31865温度传感器,贴出通信驱动代码和大牛门一起学习,指点我一二max31865是专门用来测量电阻类温度传感器的,ad分辨率为15位,可以做PT100高精度测量,通信接口为SPI。通过评估板CN7的PC10,PC11,PC12(SPI3)连接芯片模块,PD2做片选。下面贴上驱动代码:1.///////////////////////////////////////…

    2022年6月14日
  • clion 激活码 2021【中文破解版】

    (clion 激活码 2021)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html…

    2022年3月27日
  • 12个最好的JavaScript动画框架

    12个最好的JavaScript动画框架1.Scripty2scripty2是一个用于基于HTML的高级用户界面的开源JavaScript框架。或者简单地说,scripty2帮助你构建一个更美味的网络:视觉效果,UI组件和CSS实用程序。scripty2是script.aculo.us的完全重写和重新实现,具有模块化结构,旨在简化高度自定义的用户界面效果和行为的开发。2.JSAnimjsAnim

    2022年10月9日
  • mysql删除表语句_navicat不小心把表删了

    mysql删除表语句_navicat不小心把表删了MySQL删除表#1、droptable表名:直接删除表,删除较干净彻底.删除表全部数据和表结构,立刻释放磁盘空间,不管是Innodb和MyISAM;删除学生表:droptablestudent;#2、truncatetable表名:truncatetable:是对表进行先删除,再创建(与原表一致)删除表全部数据,保留表结构,立刻释放磁盘空间,不管是Innodb和MyISAM;删除学生表:truncatetablestudent;#3、dele

    2022年10月24日
  • windows关闭端口方法「建议收藏」

    windows关闭端口方法「建议收藏」windows关闭端口方法在介绍各种端口的作用前,这里先介绍一下在Windows中如何关闭/打开端口,因为默认的情况下,有很多不安全的或没有什么用的端口是开启的,比如Telnet服务的23端口、FT

    2022年7月4日
  • 开始激活成功教程so文件_so文件格式怎么打开

    开始激活成功教程so文件_so文件格式怎么打开第一、利用IDA静态分析native函数1.isEquals函数分析函数指令代码:简单分析指令代码:1>、PUSH{r3-r7,lr}是保存r3,r4,r5,r6,r7,lr的值到内存的栈中;与之对应的是POP{r3-r7,pc}pc:程序寄存器,保留下一条CPU即将执行的指令lr:连接返回寄存器,保留函数返回后,下一条应执行的指令2>、调用strlen,malloc,st

    2022年9月19日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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