加入收藏 | 设为首页 | 会员中心 | 我要投稿 开发网_开封站长网 (http://www.0378zz.com/)- 科技、AI行业应用、媒体智能、低代码、办公协同!
当前位置: 首页 > 云计算 > 正文

我有七种实现Web实时消息推送的措施

发布时间:2022-07-30 13:01:46 所属栏目:云计算 来源:互联网
导读:我有一个朋友~ 做了一个小破站,现在要实现一个站内信web消息推送的功能,对,就是下图这个小红点,一个很常用的功能。 不过他还没想好用什么方式做,这里我帮他整理了一下几种方案,并简单做了实现。 什么是消息推送(push) 推送的场景比较多,比如有人关
  我有一个朋友~
 
  做了一个小破站,现在要实现一个站内信web消息推送的功能,对,就是下图这个小红点,一个很常用的功能。
 
 
  不过他还没想好用什么方式做,这里我帮他整理了一下几种方案,并简单做了实现。
 
 
  什么是消息推送(push)
  推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。
 
  消息推送(push)通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备APP进行的主动消息推送。
 
  消息推送一般又分为web端消息推送​和移动端消息推送。
 
 
  上边的这种属于移动端消息推送,web端消息推送常见的诸如站内信、未读邮件数量、监控报警数量等,应用的也非常广泛。
 
 
  在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息),web页面的通知小红点就会实时的+1就可以了。
 
  通常在服务端会有若干张消息推送表,用来记录用户触发不同事件所推送不同类型的消息,前端主动查询(拉)或者被动接收(推)用户所有未读的消息数。
 
 
  消息推送无非是推(push​)和拉(pull)两种形式,下边我们逐个了解下。
 
  短轮询
  轮询(polling​)应该是实现消息推送方案中最简单的一种,这里我们暂且将轮询分为短轮询​和长轮询。
 
  短轮询很好理解,指定的时间间隔,由浏览器向服务器发出HTTP请求,服务器实时返回未读消息数据给客户端,浏览器再做渲染显示。
 
  一个简单的JS定时器就可以搞定,每秒钟请求一次未读消息数接口,返回的数据展示即可。
 
  复制
  setInterval(() => {
    // 方法请求
    messageCount().then((res) => {
        if (res.code === 200) {
            this.messageCount = res.data
        }
    })
  }, 1000);
  1.
  2.
  3.
  4.
  5.
  6.
  7.
  8.
  效果还是可以的,短轮询实现固然简单,缺点也是显而易见,由于推送数据并不会频繁变更,无论后端此时是否有新的消息产生,客户端都会进行请求,势必会对服务端造成很大压力,浪费带宽和服务器资源。
 
 
  长轮询
  长轮询是对上边短轮询的一种改进版本,在尽可能减少对服务器资源浪费的同时,保证消息的相对实时性。长轮询在中间件中应用的很广泛,比如Nacos​和apollo​配置中心,消息队列kafka​、RocketMQ中都有用到长轮询。
 
  Nacos配置中心交互模型是push还是pull?​一文中我详细介绍过Nacos长轮询的实现原理,感兴趣的小伙伴可以瞅瞅。
 
  这次我使用apollo​配置中心实现长轮询的方式,应用了一个类DeferredResult​,它是在servelet3.0后经过Spring封装提供的一种异步请求机制,直意就是延迟结果。
 
 
  DeferredResult​可以允许容器线程快速释放占用的资源,不阻塞请求线程,以此接受更多的请求提升系统的吞吐量,然后启动异步工作线程处理真正的业务逻辑,处理完成调用DeferredResult.setResult(200)提交响应结果。
 
  下边我们用长轮询来实现消息推送。
 
  因为一个ID可能会被多个长轮询请求监听,所以我采用了guava​包提供的Multimap结构存放长轮询,一个key可以对应多个value。一旦监听到key发生变化,对应的所有长轮询都会响应。前端得到非请求超时的状态码,知晓数据变更,主动查询未读消息数接口,更新页面数据。
 
  复制
  @Controller
  @RequestMapping("/polling")
  public class PollingController {
 
      // 存放监听某个Id的长轮询集合
      // 线程同步结构
      public static Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedMultimap(HashMultimap.create());
 
      /**
       * 公众号:程序员小富
       * 设置监听
       */
      @GetMapping(path = "watch/{id}")
      @ResponseBody
      public DeferredResult<String> watch(@PathVariable String id) {
          // 延迟对象设置超时时间
          DeferredResult<String> deferredResult = new DeferredResult<>(TIME_OUT);
          // 异步请求完成时移除 key,防止内存溢出
          deferredResult.onCompletion(() -> {
              watchRequests.remove(id, deferredResult);
          });
          // 注册长轮询请求
          watchRequests.put(id, deferredResult);
          return deferredResult;
      }
 
      /**
       * 公众号:程序员小富
       * 变更数据
       */
      @GetMapping(path = "publish/{id}")
      @ResponseBody
      public String publish(@PathVariable String id) {
          // 数据变更 取出监听ID的所有长轮询请求,并一一响应处理
          if (watchRequests.containsKey(id)) {
              Collection<DeferredResult<String>> deferredResults = watchRequests.get(id);
              for (DeferredResult<String> deferredResult : deferredResults) {
                  deferredResult.setResult("我更新了" + new Date());
              }
          }
          return "success";
      }
  
  当请求超过设置的超时时间,会抛出AsyncRequestTimeoutException​异常,这里直接用@ControllerAdvice全局捕获统一返回即可,前端获取约定好的状态码后再次发起长轮询请求,如此往复调用。
 
  复制
  @ControllerAdvice
  public class AsyncRequestTimeoutHandler {
 
      @ResponseStatus(HttpStatus.NOT_MODIFIED)
      @ResponseBody
      @ExceptionHandler(AsyncRequestTimeoutException.class)
      public String asyncRequestTimeoutHandler(AsyncRequestTimeoutException e) {
          System.out.println("异步请求超时");
          return "304";
      }
  }
   我们来测试一下,首先页面发起长轮询请求/polling/watch/10086​监听消息更变,请求被挂起,不变更数据直至超时,再次发起了长轮询请求;紧接着手动变更数据/polling/publish/10086,长轮询得到响应,前端处理业务逻辑完成后再次发起请求,如此循环往复。
  

(编辑:开发网_开封站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读