如何手动实现一个消息队列和消息队列延迟,常见的MQ中间件有哪些?

消息队列的使用场景

1, 如秒杀活动时 短时间内爆发大量用户请求. 如果不处理相关请求,轻则导致系统响应超时 重则导致系统宕机.
如果使用消息队列 ,将请求全部写入到队列中 . 在队列中排队处理.如果超过了队列的最大请求长度.可以直接将后续的请求抛弃,返回通知给用户提示 请求出错了,请刷新.
2. 系统解藕
如果现在需求频繁变更的情况,如何在不修改原代码的基础上添加或者修改功能.

消息队列使用场景
1. 当用户进行某个操作后 需要进行日志记录时 .可以将记录日志的步骤 发送到消息队列里面
在这里插入图片描述

常用的消息队列 RabbitMQ
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
自定义实现的消息队列


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

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

    /**
     * 消费者
     */
    public static void consumer() {
        while (!queue.isEmpty()) {
            System.out.println("message --> : "+queue.poll());
        }
    }

    /**
     * 生产者
     */
    public static void producer() {
        queue.add("first");
        queue.add("second");
        queue.add("three");
    }
}

自定义实现延迟消息队列

public class MyCustomDelayQueue {
    // 自定义延迟时间队列
    public static DelayQueue queue=new DelayQueue();

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

    /**
     * 消费者
     */
    private static void consumer() throws InterruptedException {
        System.out.println(DateUtil.now());
        while (!queue.isEmpty()) {
            // 强制转换成自定义的实现了Delay接口的类
            MyDelay take = (MyDelay) queue.take();
            System.out.println("----> : "+  take.getMsg());
        }
        System.out.println(DateUtil.now());
    }

    /**
     * 生产者
     */
    private static void producer() {
        queue.put(new MyDelay(1000,"测试消息1"));
        queue.put(new MyDelay(3000,"测试消息2"));
    }

    static class MyDelay implements Delayed{

        public long delayTime=System.currentTimeMillis();

        @Getter
        @Setter
        private String msg;

        public MyDelay(long delayTime, String msg) {
            this.delayTime = (this.delayTime+delayTime);
            this.msg = msg;
        }

        @Override
        public long getDelay(TimeUnit timeUnit) {
            return timeUnit.convert(delayTime-System.currentTimeMillis(),TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed delayed) {
            if (this.getDelay(TimeUnit.MILLISECONDS)> delayed.getDelay(TimeUnit.MILLISECONDS)) {
                return 1;
            }else if (this.getDelay(TimeUnit.MILLISECONDS)<delayed.getDelay(TimeUnit.MILLISECONDS)){
                return -1;
            }
            return 0;
        }
    }
}

MQ中间件
MQ 除了实现商品秒杀功能以外 还可以用来实现
消息通讯
1对1聊天
在这里插入图片描述

群聊
在这里插入图片描述
日志的采集和转发
在这里插入图片描述
常用的MQ中间件有RabbitMQ、Kafka 和Redis等,Redis 属于轻量级的消息队列
而RabbitMQ、Kafka 属于比较成熟且比较稳定和高效的MQ中间件

MQ的特点是什么?
特点是

  • 先进先出: 消息队列的顺序一般在入列时就基本确定了,最先到达消息队列的信息
    一般情况下也会先转发给订阅的消费者,我们把这种实现了先进先出的数据结构称之为队列.

  • 发布、订阅工作模式:生产者也就是消息的创建者,负责创建和推送数据到消息服务器 消费者也就是消息的接收方,用于处理数据和确认消息的消费

  • 消息队列也是MQ服务器中最重要的组成元素之一,它负责消息的存储,这三者是MQ中的三个重要角色.它们之间的消息传递与转发都是通过发布以及订阅的工作模式来进行的 即生产者把消息推送到消息队列,消费者订阅到相关的消息后进行消费. 在消息非阻塞的情况下,此模式基本可以实现同步操作的效果 并且此种工作模式会把请求的压力转移给MQ服务器,以减少了应用服务器本身的并发压力

  • 持久化:持久化是把消息从内存存储到磁盘的过程,并且在服务器重启或者发生宕机的情况下重新启动服务器之后是保证数据不会丢失的一种手段,也是目前主流MQ中间件都会提供的重要功能

  • 分布式:MQ的一个主要特性就是要应对大流量、大数据的高并发环境的功能 一个单体的MQ服务器是很难应对这种高并发的压力的所以MQ服务器都会支持分布式应用的部署,以分摊和降低高并发对MQ系统的冲击

  • 消息确认:消息消费确认是程序稳定性和安全性的一-个重要考核指标
    假如消费者在拿到消息之后突然宕机了,那么MQ服务器会误认为此消息已经被消费者消费了
    从而造成消息丢失的问题,而目前市面,上的主流MQ都实现了消息确认的功能
    保证了消息不会丢失,从而保证了系统的稳定性

引入MQ系统会带来的问题扩展
增加了系统的运行风险
●引入MQ系统,则意味着新增了一套系统
●其他的业务系统会对MQ系统进行深度依赖
●系统部署的越多意味着发生故障的可能性就越大
如果MQ系统挂掉的话可能会导致整个业务系统瘫痪
增加了系统的复杂性
●引入MQ系统后,需要考虑消息丢失
●消息重复消费,消息的顺序消费等问题
●同时还需要引入新的客户端来处理MQ的业务
●增加了编程的运维门槛,增加了系统的复杂性

MQ的注意事项
不要过度依赖MQ,比如发送短信验证码或邮件等功能
这种低频但有可能比较耗时的功能可以使用多线程异步处理即可
不用任何的功能都依赖MQ中间件来完成,但像秒杀抢购可能会导致超卖
(也就是把货卖多了,库存变成负数了)等短时间内高并发的请求,此时建议使用MQ中间件

Redis轻量级的消息中间件
Redis是一个高效的内存性数据库中间件,但使用Redis也可以实现消息队列的功能
早期的Redis (Redis 5.0之前)是不支持消息确认的,那时候我们可以通过
List数据类型的lpush和rpop方法来实现队列消息的存入和读取功能
或者使用Redis提供的发布订阅(pub/sub) 功能来实现消息队列,但这种模式不支持持久化
List虽然支持持久化但不能设置复杂的路由规则来匹配多个消息,并且他们二者都不支持消息消费确认
于是在Redis 5.0之后提供了新的数据类型Stream解决了消息确认的问题
但它同样不能提供复杂的路由匹配规则,因此在业务不复杂的场景下
可以尝试性的使用Redis提供的消息队列

RabbitMQ
RabbitMQ集群是由多个节点组成,但默认情况下每个节点并不是存储所有队列的完整拷贝
这是出于存储空间和性能的考虑,因为如果存储了队列的完整拷贝,那么就会有很多冗余的重复数据
并且在新增节点的情况下,不但没有新增存储空间,反而需要更大的空间来存储旧的数据
同样的道理,如果每个节点都保存了所有队列的完整信息,那么非查询操作的性能就会很慢
就会需要更多的网络带宽和磁盘负载来存储这些数据

为了能兼顾性能和稳定性, RabbitMQ集群的节点分为两种类型,即磁盘节点和内存节点

  • 对于磁盘节点来说显然它的优势就是稳定,可以把相关数据保存下来 若RabbitMQ因为意外情况宕机,重启之后保证了数据不丢失

  • 内存节点的优势是快,因为是在内存中进行数据交换和操作
    因此性能比磁盘节点要高出很多倍

  • 如果是.单个RabbitMQ那么就必须要求是磁盘节点 否则当RabbitMQ服务器重启之后所有的数据都会丢失,这样显然是不能接受的

  • 在RabbitMQ的集群中,至少需要一 个磁盘节点,这样至少能保证集群数据的相对可靠性

  • 如果集群中的某-个磁盘节点崩溃,此时整个RabbitMQ服务也不会处于崩溃的状态
    不过部分操作会受影响,比如不能创建队列、交换器、也不能添加用户及修改用户权限 更不能添加和删除集群的节点等功能

●对于RabbitMQ集群来说,我们启动集群节点的顺序:
●先启动磁盘节点再启动内存节点
●而关闭的顺序正好和启动的顺序相反
不然可能会导致RabbitMQ集群启动失败或者是数据丢失等异常问题

Kakfa
●Kafka是LinkedIn公司开发的基于ZooKeeper的多分区、多副本的分布式消息系统
它于2010年贡献给了Apache基金会,并且成为了Apache的顶级开源项目
●其中ZooKeeper的作用是用来为Kafka提供集群元数据管理以及节点的选举和发现等功能
●与RabbitMQ比较类似,一个典型的Kafka是由多个Broker. 多个生产者和消费者
以吸ZooKeeper集群组成的,其中Broker可以理解为一个代理
Kafka集群中的一台服务器称之为一一个Broker

●Kafka支持消息回溯,它可以根据Offset (消息偏移量)、TimeStamp (时间戳)等维度进行消息回溯
而RabbitMQ并不支持消息回溯
●Kafka的消息消费是基于拉取数据的模式,也就是消费者主动向服务器端发送拉取消息请求而RabbitMQ支持拉取数据模式和主动推送数据的模式
也就说RabbitMQ服务器会主动把消息推送给订阅的消费者

●在相同配置下,Kafka 的吞吐量通常会比RabbitMQ高一到两个级别
比如在单机模式下,RabbitMQ的吞吐量大概是万级别的处理能力
而Kafka则可以到达十万甚至是百万的吞吐级别

●Kafka从0.11版本就开始支持幂等性了
当然所谓的幂等性指的是对单个生产者在单分区.上的单会话的幂等操作
但对于全局幂等性则还需要结合业务来处理
●比如,消费者在消费完一条消息之后没有来得及确认就发生异常了
等到恢复之后又得重新消费原来消费过的消息
●类似这种情况,是无法在消息中间件层面来保证的
这个时候则需要引入更多的外部资源来保证全局幂等性
比如唯一的订单号、消费之前先做去重判断等,而RabbitMQ是没有幂等性功能支持的
●RabbitMQ支持多租户的功能,也就是常说的Virtual Host (vhost)
每一个vhost相当于一-个独立的小型RabbitMQ服务器
它们拥有自己独立的交换器、消息队列及绑定关系等,并且拥有自己独立权限;
而且多个vhost之间是绝对隔离的,但Kafka并不支持多租户的功能
●Kafka和RabbitMQ都支持分布式集群部署
并且都支持数据持久化和消息消费确认等MQ的核心功能
●对于MQ的选型要结合自己团队本身的情况
从性能、稳定性及二次开发的难易程度等维度来进行综合的考量并选择

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值