DeferredResult的使用场景及用法

2023-11-17

场景

假设我们现在要实现这样一个功能:浏览器要实时展示服务端计算出来的数据。
一种可能的实现是:浏览器频繁(例如定时1秒)向服务端发起请求以获得服务端数据。但定时请求并不能“实时”反应服务端的数据变化情况。
若定时周期为S,则数据延迟周期最大即为S。若想缩短数据延迟周期,则应使S尽量小,而S越小,浏览器向服务端发起请求的频率越高,又造成网络握手次数越多,影响了效率。因此,此场景应使用服务端实时推送技术。

这里说是推送,其实还是基于请求-响应机制,只不过发起的请求会在服务端挂起,直到请求超时或服务端有数据推送时才会做出响应,响应的时机完全由服务端控制。所以,整体效果看起来就像是服务端真的在“实时推送”一样。

可以利用SpringMVC的DeferredResult来实现异步长连接的服务端实时推送。

入门Demo

后端代码

@RequestMapping("/call")
@ResponseBody
public DeferredResult<Object> call() { // 泛型Object表示返回结果的类型
    DeferredResult<Object> response = new DeferredResult<Object>( 
        10000, // 请求的超时时间 
        null); // 超时后响应的结果
    response.onCompletion(new Runnable() {
        
        @Override
        public void run() {
            // 请求处理完成后所做的一些工作
        }
    });
    // 设置响应结果
    // 调用此方法时立即向浏览器发出响应;未调用时请求被挂起
    response.setResult(new Object());
    return response;
}
  • 注意:
    1. 需要开启 async
    2. 确保你使用的是servlet 3+

前端代码

var loopCall = function() {
    $.get("${yourContext}/call", function(r) {
        loopCall();
        console.log("call: ");
        console.log(r);
    });
};
loopCall(); // 循环发起异步请求

执行逻辑

  1. 浏览器发起异步请求
  2. 请求到达服务端被挂起(使用浏览器查看请求状态,此时为pending)
  3. 向浏览器进行响应,分为两种情况:
    3.1 调用DeferredResult.setResult(),请求被唤醒,返回结果
    3.2 超时,返回一个你设定的结果
  4. 浏览得到响应,再次重复1,处理此次响应结果

真实场景

示例:浏览器向A系统发起异步长连接请求,等到B系统给A推送数据时,A会立刻向浏览器响应结果。

数据格式

定义AB之间传输数据的格式

public interface DeferredData {
    String getId(); // 唯一标识
}

DeferredResult的持有者

public interface DeferredResultHolder<DeferredData> {
    DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult);
    void add(String key, DeferredResult<DeferredData> deferredResult);
    DeferredResult<DeferredData> get(String key);
    void remove(String key);
    void handleDeferredData(DeferredData deferredData);
}

DeferredResult的持有者实现

public class SimpleDeferredResultHolder implements DeferredResultHolder<DeferredData> {

    private Map<String, DeferredResult<DeferredData>> deferredResults = new ConcurrentHashMap<String, DeferredResult<DeferredData>>();
    
    public DeferredResult<DeferredData> newDeferredResult(String key) {
        return newDeferredResult(key, 30 * 1000L, null);
    }
    
    public DeferredResult<DeferredData> newDeferredResult(String key, long timeout) {
        return newDeferredResult(key, timeout, null);
    }
    
    public DeferredResult<DeferredData> newDeferredResult(String key, Object timeoutResult) {
        return newDeferredResult(key, 30 * 1000L, timeoutResult);
    }
    
    @Override
    public DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult) {
        DeferredResult<DeferredData> deferredResult = new DeferredResult<DeferredData>(timeout, timeoutResult);
        add(key, deferredResult);
        deferredResult.onCompletion(new Runnable() {
            
            @Override
            public void run() {
                remove(key);
            }
        });
        return deferredResult;
    }

    @Override
    public void add(String key, DeferredResult<DeferredData> deferredResult) {
        deferredResults.put(key, deferredResult);
    }

    @Override
    public DeferredResult<DeferredData> get(String key) {
        return deferredResults.get(key);
    }

    @Override
    public void remove(String key) {
        deferredResults.remove(key);
    }
    
    @Override
    public void handleDeferredData(DeferredData deferredData) {
        String key = deferredData.getId();
        DeferredResult<DeferredData> deferredResult = get(key);
        if (deferredResult != null) {
            deferredResult.setResult(deferredData);
        }
    }

}

消息发送和消费

用mq或dubbo等技术发送都可以,这里用rabbitmq做示例。
如果消费的消费者做了集群部署,则只能使用mq的topic机制分发,推送消息到A的所有部署节点,若使用dubbo则只能调用其中一个节点。因此,这里最好还是使用mq

消费的发送者

public interface DeferredDataProducer {
    void sendDeferredData(DeferredData deferredData);
}

消息的消费者

public interface DeferredDataConsumer {
   void consume(DeferredData deferredData) throws Exception;
}

消费的发送者的实现

public class DeferredDataMqProducer implements DeferredDataProducer {

    @Autowired
    private AmqpTemplate amqpTemplate;

    private String exchange;
    private String routingKey = "";

    public void setExchange(String exchange) {
        this.exchange = exchange;
    }

    public void setRoutingKey(String routingKey) {
        this.routingKey = routingKey;
    }

    @Override
    public void sendDeferredData(DeferredData deferredData) {
        amqpTemplate.convertAndSend(exchange, routingKey, deferredData);
    }

}

消费的消费者的实现

public class DeferredDataMqConsumer implements DeferredDataConsumer {

    private DeferredResultHolder<DeferredNotification> deferredResultHolder;
    
    public void setDeferredResultHolder(DeferredResultHolder<DeferredNotification> deferredResultHolder) {
        this.deferredResultHolder = deferredResultHolder;
    }
    
    @Override
    public void consume(DeferredData deferredData) throws Exception;
        deferredResultHolder.handleDeferredData(deferredData);
    }
}

具体的数据格式

通知对象

public class Notification {
    private String to; // 接收者
    private String content; // 内容
    
    // 省略getter和setter方法
}

Notification的DeferredData适配器

public DeferredNotification extends Notification implements DeferredResponse {
    
    @Override
    public String getId() {
        return getTo();
    }
    
}

B的逻辑

消息的发送者的实现

public class NotificationProducer implements DeferredResponseProducer<DeferredNotification> {

    @Autowired
    private AmqpTemplate amqpTemplate;
    
    @Override
    public void sendMessage(DeferredNotification notification, String exchange) {
        amqpTemplate.convertAndSend(exchange, "", notification);
    }
}

B的Service

@Autowired
private DeferredDataProducer<DeferredNotification> deferredDataProducer;

public void test() {
    DeferredNotification n = new DeferredNotification();
    n.setTo("abc"); // 会员
    n.setContent("哈哈,我是从admin推送过来的");
    deferredDataProducer.sendDeferredData(n);
}

A的逻辑

js请求

var loopCall = function() {
    $.get("${yourContext}/call", function(r) {
        loopCall();
        console.log("call: ");
        console.log(r);
    });
};
loopCall(); // 循环发起异步请求

Controller

@RequestMapping
@Controller
public class CallController {
    
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    
    @RequestMapping("/call")
    @ResponseBody
    public DeferredResult<DeferredData> call() {
        String id = "abc";
        return deferredResultHolder.newDeferredResult(id, 10 * 1000L, null);
    }

}

代码就是这些了,好好理一下思路,实现你自己的功能吧!



摘自: https://my.oschina.net/ojeta/blog/806087



作者:jackcooper
链接:https://www.jianshu.com/p/0e3654d14cf6
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

DeferredResult的使用场景及用法 的相关文章

随机推荐

  • 每天进步一点点——Linux中的线程局部存储(二)

    转载 http blog csdn net cywosp article details 26876231 在Linux中还有一种更为高效的线程局部存储方法 就是使用关键字 thread来定义变量 thread是GCC内置的线程局部存储设施
  • TreeMap 的特点

    TreeMap基于红黑树实现 增删改查的平均和最差时间复杂度均为O 最大特点时Key有序 key必须实现Comparable接口或者提供Comparator比较器 所以key不允许为null HashMap 依靠hashCode和equal
  • web移动端适配方案以及不同单位之间的区别

    web移动端适配方案 第一种 rem实现原理 rem是一个倍数单位 它是基于html的font size的倍数 只要我们在不同的设备上设置一个合适的初始值 当设备发生变化font size就会自动等比适配大小 从而在不同的设备上表现统一 如
  • 注意力模型CBAM

    论文 CBAM Convolutional Block Attention Module Convolutional Block Attention Module CBAM 表示卷积模块的注意力机制模块 是一种结合了空间 spatial 和
  • 【Vue2】之简单自定义插件开发,含demo

    一个vue2 x的简单插件开发实例 首先创建文件夹和文件 个人习惯把插件都放在src plugins文件夹里 创建本次demo插件目录src plugins demo 以及目录文件 demo index js demo src main j
  • python,求解字符串的所有子串

    网上的一种解法 def cut s str results num 0 x 1 表示子字符串长度 for x in range len s i 表示偏移量 for i in range len s x results append s i
  • 部署 - 前后端发布策略

    前端发布策略 前端发布的本质是静态资源的发布 主要关心缓存和资源同步问题 HTTP缓存 合理的使用缓存让未修改的文件复用可以有效的减轻服务器负担和提高前端页面渲染效率 1 协商缓存 2 本地缓存 本地缓存无需跟服务器再次确认 直接根据文件名
  • CORS policy: header is present on the requested

    CORS policy header is present on the requested 欢迎使用Markdown编辑器 解决方法 请求结果 欢迎使用Markdown编辑器 Access to XMLHttpRequest at htt
  • spring boot读取pom.xml变量

    1 application yml配置 version project version 项目版本号 为什么不使用 project version 呢 避免与避免与Spring语法冲突 项目pom继承了spring boot starter
  • DCDC相关

    1 同步和异步 同步 MOSFET管 效率高 价格贵 零件数多 电路复杂 异步 二极管 效率低 价格低 电路简单 同步整流上管S1和下管S2需要一相同频率信号以互补方式进行驱动 保证S1导通时S2截止 S1截止S2导通 异步二极管损耗 在电
  • plsql 修改sql窗口字体

    工具 首选项 用户界面 字体 编辑器 选择
  • Android自定义一个广播接收器BroadcastReceiver监听系统wifi连接

    概述 注册一个广播用来接收系统发送的广播 比如 发送或接收到一个短信 用Toast或Notification通知提醒 或者是打开或者断开网络连接 用Toast做出提示 注册文件
  • uniapp-计算属性、watch 侦听器、props验证

    一 计算属性 计算属性本质上就是一个 function 函数 它可以实时监听 data 中数据的变化 并 return 一个计算后的新值 1 声明与使用计算属性 计算属性需要以 function 函数的形式声明到组件的 computed 选
  • win11不兼容很多游戏?win11不兼容哪些游戏

    很多用户升级win11系统之后 最担心的就是win11兼容性不强 很多游戏都玩不了 那到底win11不兼容哪些游戏 下面小编就来给大家讲讲 win11很多游戏不兼容 1 其实win11系统并没有那么多无法兼容的游戏 基本上win10可以兼容
  • java插入gif_Java swing(纯代码和含部分个人解析)插入png或gif图片的方法,切换界面功能的实现...

    package swing public class mains public static void main String args new swing package swing import java awt Color impor
  • 听说,你想做大模型时代的应用层创业!

    亲爱的科技探险家们和代码魔法师们 未来的钟声已经敲响 预示着一场极度炫酷的虚拟现实游戏即将展开 从初期简单的智能识别 到设计师级别的图纸设计 生成式AI技术 Generative AI 以其独特理念和创新模式重塑了传统内容生产效率和交互模式
  • Unity手机端3档震动

    using System Collections using System Collections Generic using UnityEngine public class VibrateHelper MonoBehaviour sta
  • elementui dialog组件固定高度

    弹窗高度过大 想设置个自适应的高度 固定头尾 deep el dialog margin 5vh auto important deep el dialog body height 70vh overflow auto margin hei
  • ChatGPT救命!4岁男孩3年求医17位专家无果,大模型精准揪出病因

    克雷西 萧箫 发自 凹非寺量子位 公众号 QbitAI 怪病 缠身3年求医无果 最终竟然被ChatGPT成功诊断 这是发生在一名4岁男孩身上的真实经历 某次运动后 他身体开始剧痛 母亲前后带她看了17名医生 从儿科 骨科到各种专家 先后进行
  • DeferredResult的使用场景及用法

    场景 假设我们现在要实现这样一个功能 浏览器要实时展示服务端计算出来的数据 一种可能的实现是 浏览器频繁 例如定时1秒 向服务端发起请求以获得服务端数据 但定时请求并不能 实时 反应服务端的数据变化情况 若定时周期为S 则数据延迟周期最大即