延时消息大致设计

  延时消息,顾名思义就是发送消息后延迟多少时间接收。

  使用场景举例,例如用户买票后,出票后要给用户发一个反现金红包,但是出票一般是异步出票,所以我们可以设定一个最大时间,例如30分钟。在买票30分钟后,根据出票结果决定是否发反现金红包。此时就可以使用延时队列,在用户购票的时候发送一个30分钟的延时消息,在接收到延时消息后查出票结果决定是否发红包。

  实现方式可如下几种

  定时器

    在发送延时消息时,可以根据当前时间算出任务要执行的时间,然后将数据存入存储系统中,定时器每间隔时间去扫描,如果扫描到执行时间在当前时间之前且还未执行的,则将任务取出来执行,并标记为已执行

      这个方法延时精确度依赖于扫描频率,数据量变大后扫描效率将变低O(n) 复杂度

  堆

    可以利用小顶堆的特性,将延时任务构建成一个堆,这样只需每次查看堆顶的时间点任务是否在当时时间之后,如果是的话就取堆顶的任务执行。java中的延时队列DelayQueue就是这个设计

        该方法精准度较高,但取任务以及加任务的时候由于要重新维持堆,所以仍要logN的复杂度,在数据量较大的情况效率仍会变低

  时间轮

    该方法可以理解为定时器的优化版,主要优化了定时器的存储和扫描问题

    例如以一个小时的时间轮为例,用户投递的延时消息,我们根据投递时间加延时时间算出最终需要的投递时间,然后将这些消息按小时维度存储到文件中,例如2022021611,2022021612,2022021612...,并对这些文件建立索引,文件里面要存储的信息大概如下

   

 

   然后在内存里面用一个线程维护一个60分钟时间轮,以秒为单位可以分为3600个时间槽,系统每过一秒就将当前时间槽里面的任务取出来投递,然后可以固定在时间跑到最后一个槽的时候加载下一个小时的文件。将文件后的信息解析根据执行的time时间戳(一般用秒),放入其对应的时间槽,例如有个消息是下一个小时的第10分钟解析也就是会放入到第600个槽里面,这样当前的时间轮跑到3600之后又会重新从0开始,然后跑到600的时候将第600个槽里面刚放入的消息取出来投递。

  这个方法的好处是系统内存里面只会加载当前时间轮的消息,剩下的全部存在磁盘上,解决了海量消息投递的问题。时间精准度也很高 (公司目前用的该种实现方式)

  

  时间轮的设计不止可以用到延时队列,还有心跳检测,超时检测等都是可以的。

 

    

posted @ 2022-02-16 12:08  雨落寒沙  阅读(171)  评论(1编辑  收藏  举报