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

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

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

典型回答

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

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 的补充类,头部和尾部都支持元素插入和获取;

  • 阻塞队列指的是在元素操作时(添加或删除),如果没有成功,会阻塞等待执行,比如当添加元素时,如果队列元素已满,队列则会阻塞等待直到有空位时再插入;

  • 非阻塞队列,和阻塞队列相反,它会直接返回操作的结果,而非阻塞等待操作,双端队列也属于非阻塞队列。

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

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());
        }
    }
}

以上程序的执行结果是:

first message.
second message.
third message.

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

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

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;
        }
    }
}

以上程序的执行结果是:

开始执行时间:2020-4-2 16:17:28
消息1
消息2
结束执行时间:2020-4-2 16:17:31

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

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

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/281973.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多线程编程设计模式(单例,阻塞队列,定时器,线程池)

&#x1f495;"只有首先看到事情的可能性&#xff0c;才会有发生的机会。"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;多线程编程设计模式(单例,阻塞队列,定时器,线程池) 本文主要讲解多线程编程中常用到的设计模式,包括单例模式,阻塞队列,定时…

“C语言与人生:手把手教你玩转C语言数组,从此编程无难题“

各位少年&#xff0c;我是博主那一脸阳光&#xff0c;由我来给大家介绍C语言的数组的详解。 在C语言中&#xff0c;数组是一种极其重要的数据结构&#xff0c;它允许我们存储和管理相同类型的一系列相关数据。通过理解并熟练掌握数组的使用&#xff0c;开发者能够高效地处理大量…

激发AI时代操作系统创新活力,统信UOS持续拓宽生态护城河

操作系统作为信息技术产业之“魂”&#xff0c;在2023年迈进“真替真用阶段”&#xff0c;迎来强势崛起。 国产操作系统产业依托数字化转型浪潮&#xff0c;市场份额逐年递增&#xff0c;并向智能计算等方向加速进化。经过数年的深耕&#xff0c;统信软件交出漂亮成绩单。最新…

SpringCloud(H版alibaba)框架开发教程,使用eureka,zookeeper,consul,nacos做注册中心——附源码(1)

源码地址&#xff1a;https://gitee.com/jackXUYY/springboot-example 创建订单服务&#xff0c;支付服务&#xff0c;公共api服务&#xff08;共用的实体&#xff09;&#xff0c;eureka服务 1.cloud-consumer-order80 2.cloud-provider-payment8001 3.cloud-api-commons 4.…

kotlin快速入门1

在Google I/O 2017中&#xff0c;Google 宣布 Kotlin 成为 Android 官方开发语言。目前主流AndroidApp开发已经全部切换成此语言&#xff0c;因此对于Android开发而言&#xff0c;掌握Kotlin已经变成必要事情。 Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言&#xff…

Armpro脱壳软件搭建教程附源代码

PHP8.0版本&#xff0c;数据库8.0版本 1.配置注册机文件&#xff0c;打开将arm.zip/res目录下&#xff0c;mt管理器搜索将其全部修改为你自己的域名或者是服务器IP 2.然后建立数据库 数据库账号arm 数据库用户名arm 数据库密码EsZfXY4tD3h2NNA4 3.导入数据库 4.配置Redi…

算法基础之滑雪

滑雪 核心思想&#xff1a;记忆化搜索 状态表示&#xff1a; f[i][j] 表示所有从(i,j) 开始滑的路径的最大值 状态计算&#xff1a; 分成四个方向 f[i][j] max(f[i][j] , f[i][j1] 1) 且h[a][b] (下一个点) 必须严格小于 h[i][j] 才能滑过去 #include<iostream>#…

图像去雾/图像去雨(matlab/python)

图像去雾和图像去雨是计算机视觉领域的两个重要问题&#xff0c;旨在改善被大气条件或降雨影响而模糊或噪声化的图像质量。这两个技术在很多实际应用中具有广泛的价值&#xff0c;包括无人驾驶、安防监控、航空航天等领域。下面将分点介绍图像去雾和图像去雨的相关内容。 1. 图…

紫光展锐5G扬帆出海 | 东南亚成为5G新热土

东南亚是一块充满活力和潜力的市场&#xff0c;这里人口基数大、年轻消费群体占比高&#xff0c;电子市场在过去几年显著增长。 增速“狂飙”的东南亚手游 近年来&#xff0c;东南亚手游下载量逐年增长&#xff0c;2023 年第一季度下载量突破 21 亿次&#xff0c;贡献了全球近…

结构体:枚举

#include<iostream> using namespace std; int main() {enum weekday { mon, tus, wed, thu, fri, sat,sun }; //声明枚举类型 enum weekday day; //定义枚举变量 int a, b, c, d, e, f, g, loop; //定义整型变量 char ch A; //定义字符变量 f thu; //按照题意&a…

轻量封装WebGPU渲染系统示例<55>- 顶点数据更新

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/VertexUpdateTest.ts 当前示例运行效果: ​​​​​​​ 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下: export class VertexUpdateTest {pr…

2023 年四川省职业院校技能大赛(高职组)应用软件系统开发赛项样题

2023 年四川省职业院校技能大赛 &#xff08;高职组&#xff09;应用软件系统开发赛项样题 目录 竞赛说明 二、竞赛内容 三、竞赛成果物提交​编辑 四、文档及组件 五、竞赛注意事项 模块一&#xff1a;系统需求分析 二、任务要求 三、竞赛任务 任务 1&#xff1a;智造双碳—能…

从2023看2024前端发展趋势

前言 流光溯影&#xff0c;纵观2023全年&#xff0c;整个前端业界呈现出百业凋零之状&#xff0c;更不乏有“前端已死”等论调甚嚣尘上。从全局视角看IT行业&#xff0c;除了AI领域的大语言模型爆发外&#xff0c;整体都鲜有特别亮眼及突出的技术展现。故而&#xff0c;作为IT…

啊?这也算事务?!

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

学Python的正确顺序千万别弄反了,到时候后悔就来不及了

学Python的正确顺序&#xff1a;从基础到高级&#xff0c;步步为营 在当今数字化时代&#xff0c;Python已成为最受欢迎的编程语言之一。它不仅广泛应用于数据分析、人工智能和Web开发等领域&#xff0c;还为初学者提供了一个友好且功能强大的平台。然而&#xff0c;学习Python…

2023 全球 AI 大事件盘点

本文来自微信公众号硅星人

python观察图像的幅度谱和相位谱——冈萨雷斯数字图像处理

原理 在图像处理中&#xff0c;当我们对图像进行傅里叶变换&#xff0c;可以得到两个重要的成分&#xff1a;幅度谱和相位谱。这些成分在图像分析和处理中扮演着关键的角色。 傅里叶变换 傅里叶变换是一种将信号从时域&#xff08;或空间域&#xff09;转换到频率域的工具。对…

EOS运行启动 keosd 和 nodeos

EOS运行启动 keosd 和 nodeos 启动keosd:启动nodeos 安装完eosio后keosd和nodeos是一起带着的&#xff0c;我把EOS的运行keosd 和 nodeos官网教程放上来&#xff1a; https://developers.eos.io/welcome/latest/getting-started-guide/local-development-environment/start-nod…

智能制造 - 你需要了解的 10 个基本要素

智能制造 - 您需要了解的 10 个基本要素 在第四次工业革命 (IoT4) 的动态格局中&#xff0c;智能制造成为生产方式的关键变革。先进技术和创新工艺的融合正在塑造工业领域的新时代。探索智能制造的基本要素以及它们如何共同重新定义制造领域的效率、生产力和敏捷性。 一、数字…

Vue+elementUI引入MessageUI展示问题

VueelementUI引入MessageUI展示问题 1.出现问题的界面 2.解决问题 import "element-plus/theme-chalk/el-message.css";