前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何手写一个消息队列和延迟消息队列?

如何手写一个消息队列和延迟消息队列?

作者头像
小熊学Java
发布于 2023-12-27 02:33:30
发布于 2023-12-27 02:33:30
30400
代码可运行
举报
文章被收录于专栏:全栈学习之路全栈学习之路
运行总次数:0
代码可运行

第一次听到“消息队列”这个词时,不知你是不是和我反应一样,感觉很高阶很厉害的样子,其实当我们了解了消息队列之后,发现它与普通的技术类似,当我们熟悉之后,也能很快地上手并使用。

面试题是,消息队列的使用场景有哪些?如何手动实现一个消息队列和延迟消息队列?

典型回答

消息队列的使用场景有很多,最常见的使用场景有以下几个。

1.商品秒杀

比如,我们在做秒杀活动时,会发生短时间内出现爆发式的用户请求,如果不采取相关的措施,会导致服务器忙不过来,响应超时的问题,轻则会导致服务假死,重则会让服务器直接宕机,给用户带来的体验也非常不好。如果这个时候加上了消息队列,服务器接收到用户的所有请求后,先把这些请求全部写入到消息队列中再排队处理,这样就不会导致同时处理多个请求的情况;如果消息队列长度超过可以承载的最大数量,那么我们可以抛弃当前用户的请求,通知前台用户“页面出错啦,请重新刷新”等提示,这样就会有更好的交互体验。

2.系统解耦

使用了消息队列之后,我们可以把系统的业务功能模块化,实现系统的解耦。例如,在没有使用消息队列之前,当前台用户完善了个人信息之后,首先我们需要更新用户的资料,再添加一条用户信息修改日志。但突然有一天产品经理提了一个需求,在前台用户信息更新之后,需要给此用户的增加一定的积分奖励,然后没过几天产品经理又提了一个需求,在前台用户信息更新之后,不但要增加积分奖励,还要增加用户的经验值,但没过几天产品经理的需求又变了,他要求完善资料无需增加用户的积分了,这样反反复复、来来回回的折腾,我想研发的同学一定受不了,但这是互联网公司的常态,那我们有没有一劳永逸的办法呢?

没错,这个时候我们想到了使用消息队列来实现系统的解耦,每个功能的实现独立开,只需要一个订阅或者取消订阅的开关就可以了,当需要增加功能时,只需要打开订阅“用户信息完善”的队列就行,如果过两天不用了,再把订阅的开关关掉就行了,这样我们就不用来来回回的改业务代码了,也就轻松的实现了系统模块间的解耦。

3.日志记录

我们大部分的日志记录行为其实是和前台用户操作的主业务没有直接关系的,只是我们的运营人和经营人员需要拿到这部分用户操作的日志信息,来进行用户行为分析或行为监控。在我们没有使用消息队列之前,笼统的做法是当有用户请求时,先处理用户的请求再记录日志,这两个操作是放在一起的,而前台用户也需要等待日志添加完成之后才能拿到后台的响应信息,这样其实浪费了前台用户的部分时间。此时我们可以使用消息队列,当响应完用户请求之后,只需要把这个操作信息放入消息队列之后,就可以直接返回结果给前台用户了,无须等待日志处理和日志添加完成,从而缩短了前台用户的等待时间。

我们可以通过 JDK 提供的 Queue 来实现自定义消息队列,使用 DelayQueue 实现延迟消息队列。

考点分析

对于消息队列的考察更侧重于消息队列的核心思想,因为只有理解了什么是消息队列?以及什么情况下要用消息队列?才能解决我们日常工作中遇到的问题,而消息队列的具体实现,只需要掌握一个消息中间件的使用即可,因为消息队列中间件的核心实现思路是一致的,不但如此,消息队列中间件的使用也大致类似,只要掌握了一个就能触类旁通的用好其他消息中间件。

相关的面试题,还有以下这两个:

  • 介绍一个你熟悉的消息中间件?
  • 如何手动实现消息队列?

知识扩展

1.常用消息中间件 RabbitMQ

目前市面上比较常用的 MQ(Message Queue,消息队列)中间件有 RabbitMQ、Kafka、RocketMQ,如果是轻量级的消息队列可以使用 Redis 提供的消息队列,本课时我们先来介绍一下 RabbitMQ,其他消息中间件将会在第 15 课时中单独介绍。

RabbitMQ 是一个老牌开源的消息中间件,它实现了标准的 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)消息中间件,使用 Erlang 语言开发,支持集群部署,和多种客户端语言混合调用,它支持的主流开发语言有以下这些:

  • Java and Spring
  • .NET
  • Ruby
  • Python
  • PHP
  • JavaScript and Node
  • Objective-C and Swift
  • Rust
  • Scala
  • Go

更多支持语言,请点击这里访问官网查看。

RabbitMQ 中有 3 个重要的概念:生产者、消费者和代理。

  • 生产者:消息的创建者,负责创建和推送数据到消息服务器。
  • 消费者:消息的接收方,用于处理数据和确认消息。
  • 代理:也就是 RabbitMQ 服务本身,它用于扮演“快递”的角色,因为它本身并不生产消息,只是扮演了“快递”的角色,把消息进行暂存和传递。

它们的运行流程,如下图所示:

RabbitMQ 具备以下几个优点:

  • 支持持久化,RabbitMQ 支持磁盘持久化功能,保证了消息不会丢失;
  • 高并发,RabbitMQ 使用了 Erlang 开发语言,Erlang 是为电话交换机开发的语言,天生自带高并发光环和高可用特性;
  • 支持分布式集群,正是因为 Erlang 语言实现的,因此 RabbitMQ 集群部署也非常简单,只需要启动每个节点并使用 --link 把节点加入到集群中即可,并且 RabbitMQ 支持自动选主和自动容灾
  • 支持多种语言,比如 Java、.NET、PHP、Python、JavaScript、Ruby、Go 等;
  • 支持消息确认,支持消息消费确认(ack)保证了每条消息可以被正常消费;
  • 它支持很多插件,比如网页控制台消息管理插件、消息延迟插件等,RabbitMQ 的插件很多并且使用都很方便。

RabbitMQ 的消息类型,分为以下四种:

  • direct(默认类型)模式,此模式为一对一的发送方式,也就是一条消息只会发送给一个消费者;
  • headers 模式,允许你匹配消息的 header 而非路由键(RoutingKey),除此之外 headers 和 direct 的使用完全一致,但因为 headers 匹配的性能很差,几乎不会被用到;
  • fanout 模式,为多播的方式,会把一个消息分发给所有的订阅者;
  • topic 模式,为主题订阅模式,允许使用通配符(#、*)匹配一个或者多个消息,我可以使用“cn.mq.#”匹配到多个前缀是“cn.mq.xxx”的消息,比如可以匹配到“cn.mq.rabbit”、“cn.mq.kafka”等消息。

2.自定义消息队列

我们可使用 Queue 来实现消息队列,Queue 大体可分为以下三类:

  • **双端队列(Deque)**是 Queue 的子类也是 Queue 的补充类,头部和尾部都支持元素插入和获取;
  • 阻塞队列指的是在元素操作时(添加或删除),如果没有成功,会阻塞等待执行,比如当添加元素时,如果队列元素已满,队列则会阻塞等待直到有空位时再插入;
  • 非阻塞队列,和阻塞队列相反,它会直接返回操作的结果,而非阻塞等待操作,双端队列也属于非阻塞队列。

自定义消息队列的实现代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.util.LinkedList;
import java.util.Queue;

public class CustomQueue {
    // 定义消息队列
    private static Queue<String> queue = new LinkedList<>();

    public static void main(String[] args) {
        producer(); // 调用生产者
        consumer(); // 调用消费者
    }

    // 生产者
    public static void producer() {
        // 添加消息
        queue.add("first message.");
        queue.add("second message.");
        queue.add("third message.");
    }

    // 消费者
    public static void consumer() {
        while (!queue.isEmpty()) {
            // 消费消息
            System.out.println(queue.poll());
        }
    }
}

以上程序的执行结果是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
first message.
second message.
third message.

可以看出消息是以先进先出顺序进行消费的。

实现自定义延迟队列需要实现 Delayed 接口,重写 getDelay() 方法,延迟队列完整实现代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import lombok.Getter;
import lombok.Setter;

import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * 自定义延迟队列
 */
public class CustomDelayQueue {
    // 延迟消息队列
    private static DelayQueue delayQueue = new DelayQueue();

    public static void main(String[] args) throws InterruptedException {
        producer(); // 调用生产者
        consumer(); // 调用消费者
    }

    // 生产者
    public static void producer() {
        // 添加消息
        delayQueue.put(new MyDelay(1000, "消息1"));
        delayQueue.put(new MyDelay(3000, "消息2"));
    }

    // 消费者
    public static void consumer() throws InterruptedException {
        System.out.println("开始执行时间:" +
                DateFormat.getDateTimeInstance().format(new Date()));
        while (!delayQueue.isEmpty()) {
            System.out.println(delayQueue.take());
        }
        System.out.println("结束执行时间:" +
                DateFormat.getDateTimeInstance().format(new Date()));
    }

    /**
     * 自定义延迟队列
     */
    static class MyDelay implements Delayed {
        // 延迟截止时间(单位:毫秒)
        long delayTime = System.currentTimeMillis();

        // 借助 lombok 实现
        @Getter
        @Setter
        private String msg;

        /**
         * 初始化
         * @param delayTime 设置延迟执行时间
         * @param msg       执行的消息
         */
        public MyDelay(long delayTime, String msg) {
            this.delayTime = (this.delayTime + delayTime);
            this.msg = msg;
        }

        // 获取剩余时间
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        // 队列里元素的排序依据
        @Override
        public int compareTo(Delayed o) {
            if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
                return 1;
            } else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {
                return -1;
            } else {
                return 0;
            }
        }

        @Override
        public String toString() {
            return this.msg;
        }
    }
}

以上程序的执行结果是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
开始执行时间:2020-4-2 16:17:28
消息1
消息2
结束执行时间:2020-4-2 16:17:31

可以看出,消息 1 和消息 2 都实现了延迟执行的功能。

更多RabbitMQ的知识点可看着:RabbitMQ知识点

总结

今天讲了消息队列的使用场景:商品秒杀、系统解耦和日志记录,我们还介绍了 RabbitMQ 以及它的消息类型和它的特点等内容,同时还使用 Queue 的子类 LinkedList 实现了自定义消息队列,使用 DelayQueue 实现了自定义延迟消息队列。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小熊学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
工作三年,小胖问我怎么实现一个消息队列?真的菜!
哈喽,新年好呀。我是狗哥,鸽了两周,今天回归。这是 Java 面试及源码剖析的第四篇,其他篇章的链接在这里有兴趣的小伙伴可以看看:
JavaFish
2021/03/04
4470
工作三年,小胖问我怎么实现一个消息队列?真的菜!
延迟任务多种实现姿势--下
基于mq来实现延迟任务方案,相信各位很容易就可以想到mq中的延迟队列,延时队列就是用来存放需要在指定时间被处理的元素的队列。
大忽悠爱学习
2022/09/29
3030
延迟任务多种实现姿势--下
延迟队列使用指南-Java快速进阶教程
在本文中,我们将研究java.util.concurrent包中的DelayQueue构造。这是一个阻塞队列,可用于生产者-消费者程序。
jack.yang
2025/04/05
540
【消息队列之rabbitmq】Rabbitmq之消息可靠性投递和ACK机制实战
上篇文章介绍了rabbitmq的基本知识、交换机类型实战《【消息队列之rabbitmq】学习RabbitMQ必备品之一》 这篇文章主要围绕着消息确认机制为中心,展开实战;接触过消息中间件的伙伴都知道,消息会存在以下问题: 1、消息丢失问题和可靠性投递问题; 2、消息如何保证顺序消费; 3、消息如何保证幂等性问题,即重复消费问题等等… 本文主要以Rabbitmq消息中间件解决问题一的实践,其他问题小编会重新写文章总结;
沁溪源
2022/01/13
1.3K0
【消息队列之rabbitmq】Rabbitmq之消息可靠性投递和ACK机制实战
我攻克的技术难题:自定义延时消息队列
消息队列是一种异步的服务间通信方式,适用于无服务器和微服务架构。消息在被处理和删除之前一直存储在队列上。每条消息仅可被一位用户处理一次。消息队列可被用于分离重量级处理、缓冲或批。
不惑
2024/01/29
3040
我攻克的技术难题:自定义延时消息队列
Redis实现消息队列及延迟队列
在选择消息中间件的问题上,我们有很多解决方案,具体选择哪一种还是要根据实际的情况来进行确认。
半月无霜
2023/03/03
2.1K0
Redis实现消息队列及延迟队列
10分钟搞懂!消息队列选型全方位对比
导语 | 消息队列是分布式系统中重要的中间件,在高性能、高可用、低耦合等系统架构中扮演着重要作用。本文对Kafka、Pulsar、RocketMQ、RabbitMQ、NSQ这几个消息队列组件进行了一些调研,并整理了相关资料,为业务对MQ中间件选型提供参考。 一、概述 消息队列是分布式系统中重要的中间件,在高性能、高可用、低耦合等系统架构中扮演着重要作用。分布式系统可以借助消息队列的能力,轻松实现以下功能: 解耦,将一个流程的上游和下游拆开,上游专注生产消息,下游专注处理消息。 广播,一个上游生产的消息轻松被
腾讯云开发者
2022/02/17
14.1K0
秒懂消息队列MQ,看这篇就够了!
前面介绍了分布式锁以及如何使用Redis实现分布式锁,接下来介绍分布式系统中另外一个非常重要的组件:消息队列。
架构师精进
2023/10/06
15.6K0
秒懂消息队列MQ,看这篇就够了!
1.6万字+28张图盘点11种延迟任务的实现方式
延迟任务在我们日常生活中比较常见,比如订单支付超时取消订单功能,又比如自动确定收货的功能等等。
三友的java日记
2024/07/02
2440
1.6万字+28张图盘点11种延迟任务的实现方式
延迟任务多种实现姿势--上
例如:pdd下单,但是没有付款,那么24小时候,订单会自动取消。收货后,如果一直不进行确认,那么默认七天后自动确认收货等等。
大忽悠爱学习
2022/09/29
5500
延迟任务多种实现姿势--上
田哥 手把手教 你用 Redis 做延迟消息队列
看到消息队列,我们肯定会想到各种MQ,比如:RabbitMQ,acivityMQ、RocketMQ、Kafka等。
田维常
2021/04/02
3.8K0
田哥 手把手教 你用 Redis 做延迟消息队列
消息队列经典十连问
大家好呀,我是捡田螺的小男孩。金三银四即将来临,整理了十道十分经典的消息队列面试题,看完肯定对面试有帮助的,大家一起加油哈~
捡田螺的小男孩
2022/04/06
8120
消息队列经典十连问
史上最全的延迟任务实现方式汇总!附代码(强烈推荐)
这篇文章的诞生要感谢一位读者,是他让这篇优秀的文章有了和大家见面的机会,重点是优秀文章,哈哈。
磊哥
2020/04/16
3.7K0
Redis 学习笔记(六)Redis 如何实现消息队列
消息队列(Messeage Queue,MQ)是在分布式系统架构中常用的一种中间件技术,从字面表述看,是一个存储消息的队列,所以它一般用于给 MQ 中间的两个组件提供通信服务。
归思君
2023/10/16
4.4K0
Redis 学习笔记(六)Redis 如何实现消息队列
Java 并发编程:解析多种队列类型的用途 Queue Nice !!!
Java 中的队列有很多,例如:ArrayBlockingQueue、LinkedBlockingQueue、PriorityQueue、DelayQueue、SynchronousQueue 等,那它们的作用是什么?又是如何分类的呢?
杨不易呀
2023/10/05
5250
Java 并发编程:解析多种队列类型的用途 Queue Nice !!!
Java之BlockingQueue
public interface BlockingQueue<E> extends Queue<E> {
用户7886150
2021/04/08
4170
为什么使用消息队列
以rcoketMQ为例,他的集群就有多master 模式、多master多slave异步复制模式、多 master多slave同步双写模式
Autooooooo
2020/11/09
4050
为什么使用消息队列
延迟消息处理
之前有这样一个需求,运营在后端配置一条系统消息或者营销活动等类型的消息等到了需要推送的时间以后会自动的将消息推送给用户APP端显示,一开始是采用的任务调度的方式(定时器),通过轮询扫表去做,因为具体什么时候推送消息没有固定的频率,固定的时间,因此需要每分钟扫表以避免消息在指定时间内未及时推送给APP端内.所以每次都是1分钟扫描一次,太过于频繁。所以不太适合(定时器适合那种固定频率或时间段处理)。
用户1215919
2021/12/28
8760
Java中的5大队列,你知道几个?
通过前面文章的学习《一文详解「队列」,手撸队列的3种方法!》我们知道了队列(Queue)是先进先出(FIFO)的,并且我们可以用数组、链表还有 List 的方式来实现自定义队列,那么本文我们来系统的学习一下官方是如何实现队列的。
磊哥
2020/10/29
1.6K0
Java中的5大队列,你知道几个?
消息队列RabbitMQ的常见面试题目
👨‍💻个人主页: 才疏学浅的木子 🙇‍♂️ 本人也在学习阶段如若发现问题,请告知非常感谢 🙇‍♂️ 📒 本文来自专栏: 消息队列 RabbitMQ面试题 什么是RabbitMQ 什么是AMQP 为什么要使用RabbitMQ(优点) RabbitMQ的缺点 RabbitMQ的构造 生产者生产消息的过程 消费者接受消息过程 如何保证消息不丢失,进行可靠传输 如何保证消息不被重复消费 如何保证消息的有序性 如何处理消息堆积情况 什么是RabbitMQ 使用AMQP高级队列协议的一种消息队列技术,最大的
才疏学浅的木子
2022/11/20
3890
消息队列RabbitMQ的常见面试题目
推荐阅读
相关推荐
工作三年,小胖问我怎么实现一个消息队列?真的菜!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验