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

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

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

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

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


相关推荐

  • @helper的使用

    @helper的使用、前言最近翻到一篇Scott的旧文,觉得挺不错的,就试着翻译了一下,文章主要是说如何在Razor中使用@helper语法定义可复用的视图模板方法。如有疏漏,还请请各位看官指点一二~原文地址:http:

    2022年7月3日
    29
  • 学电脑必知的电脑配置

    学电脑必知的电脑配置电脑的配置,主要看CPU、显卡、主板、内存、硬盘、显示器等,而笔记本的话就看它的品牌就行了。国外的有HP、apple、松下、东芝等,不过顾客口碑和质量比较硬的是DELL和HP这两个品牌;国产的有:宏基、清华紫光、清华同方、神州、海尔、联想、八亿时空等。评价标准1、CPU,这个主要取决于频率和二级缓存,频越高、二级缓存越大,速度越快,未来CPU会有三级缓存、四级缓…

    2022年7月16日
    21
  • weakHashMap 用法

    weakHashMap 用法WeakHashMap,此种Map的特点是:当除了自身有对key的引用外,此key没有其他引用,那么GC之后此map会自动丢弃此值举例来说:声明了两个Map对象,一个是HashMap,一个是WeakHashMap,同时向两个map中放入a、b两个对象,当HashMapremove掉a并且将a、b都指向null时,WeakHashMap中的a将自动被回收掉。出现这个状况的原因是,对于a对象…

    2022年5月6日
    44
  • 什么是Unicode字符_Unicode格式字符是什么

    什么是Unicode字符_Unicode格式字符是什么写这篇博客的原因,从做软件开始,什么ASCII码,Unicode,UTF-8,UTF-16,UTF-32……这些鬼东西总是经常碰到,只知道这些鬼是编码格式,其他的就啥都不清楚了,既然总是遇

    2022年8月1日
    3
  • pycharm如何创建py文件_程序编写入门

    pycharm如何创建py文件_程序编写入门 1、主题  详细介绍如何使用PyCharm创建一个IPythonNotebook(基于Web技术的交互式计算文档格式)并运行。  2、准备工作  (1)已经创建一个工程,这里使用C:/SampleProjects/py/IPythonNotebookExample目录下的工程。  (2)在设置对话框的ProjectInterpreterpage页面中,    创建一个虚…

    2022年8月29日
    3
  • VsVim的vimrc

    VsVim的vimrc给VisualStudio安装VsVim后可在VS中使用vim的便捷编辑功能,很强大。同时也可通过vimrc文件来做些特殊配置,vimrc的搜索路径可通过在编辑器中输入“:set”查看。我的vimrc搜索路径如下:vimrcpaths=”C:\Users\chenbo;C:\Users\chenbo\vimfiles;C:\Users\chenbo”在C:\Users\Chenbo…

    2022年6月10日
    50

发表回复

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

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