vlambda博客
学习文章列表

消息队列一点 都 ! 不 ! 难 !

前言

近期公司用到了消息队列方面的知识,才发现原来我竟然没学过这种技术(尴尬)所以就想写一篇文章加深自己的印象,如果这篇文章对你有帮助的话,希望可以给我一个在看,您的在看.就是我继续写下去的动力.


正文

本文围绕以下几点进行阐述:

  1. 什么是消息队列(消息中间件,后统称MQ)?

  2. 常见的MQ有哪些?有什么异同?

  3. 我们为什么要使用MQ,其使用场景有哪些?

  4. 我们使用消息队列可能会出现什么问题?


一、什么是消息队列?

相信没了解过这门技术的人肯定对这个名称比较陌生,那我们先将这两个词拆开,可以分为“消息”,“队列”。

首先“消息”是什么呢?让我们看一下百度百科

消息我们可以理解为在计算机中或在整个计算机网络中传递的数据。

那什么是“队列”呢?

队列是一种先进先出(FIFO)的数据结构。

那MQ到底是一个什么东西呢?

消息队列就是一个保存消息的容器,它具有先进先出的特性。

有了这些还不够,我们再来看一个最简单的架构模型:

  • Producer:消息生产者,负责产生和发送消息到 Broker;

  • Broker:消息处理中心。负责消息存储、确认、重试等,一般其中会包含多个 queue;

  • Consumer:消息消费者,负责从 Broker 中获取消息,并进行相应处理;

也就是说:

发送消息到MQ的叫生产者,从消息队列里面取数据的叫消费者。

二、常见的MQ有哪些?有什么异同?

现在市面上有很多不同类型的MQ,当前使用较多的 MQRabbitMQRocketMQActiveMQKafkaZeroMQMetaMQ 等,而部分 数据库RedisMySQL 以及 phxsql 也可实现消息队列的功能。

具体有什么不同我就不详细给大家介绍了,给大家贴一个图出来:

消息队列一点 都 ! 不 ! 难 !

具体选择什么样的技术,要看大家的使用情况。没有最好的技术,只有最合适的技术


三、我们为什么要使用MQ,其使用场景有哪些?

换句话说,我们为什么要用MQ呢,还不是因为它好用。。什么什么???我看了半天你就给我说这些,来来来写文章的你给我出来,看我不打死你......好了,回归正题,我现在来介绍一下使用它的好处。

  • 异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;

  • 应用耦合:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;

  • 限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况;

  • 消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理;

3、1  异步

比如我们现在想做一个用户注册的这么一个业务,业务流程大概是是这样,用户先提交注册用的信息,提交到业务系统之后业务系统将会给用户发送一个邮箱验证的邮件,然后再向注册用户的手机发送注册成功的短信,这样做程序执行的时间大概是150ms(发送邮件50ms,发送短信50ms,注册信息写入50ms)。

消息队列一点 都 ! 不 ! 难 !

好的,现在注册一个用户是150ms,程序安安静静的跑了一个星期,这时候来了一个新的需求,可能现在要求用户注册要给100初始积分,这时候我们就要引入用户的积分系统,现在可能程序的执行时间就变成了200ms......如果要是再有什么需求的话,我的程序岂不是要慢死了???这时候系统的执行时间可能就是 ------>>>> +∞

你现在转念一想,我是一个有梦想的程序猿,我不能让我的系统这么慢,这时候你就在想我现在可以用什么技术去让我写的代码执行的更快呢???这时候你突然看见了这篇文章,恍然大悟,原来可以用MQ来优化,这时候的业务流程可能就是这样:消息队列一点 都 ! 不 ! 难 !

这时候我们的运行时间可能就是注册信息写入50ms,将消息发送到消息服务器中50ms,这时候我们系统执行的时间就是100ms(这时候不管你再引入几个业务系统我也不怕,我现在只关心将业务系统需要的数据发送到MQ中,并不需要关心业务系统是否执行成功,虽然是这样说,但是真正的情况并不能不管业务方是否执行成功)。

肯定有细心的小伙伴会说,如果我发消息的时候,消息服务挂掉了怎么办?我怎么能保证我一定能将消息发到消息服务中?我将消息发送到消息服务中,我怎么能保证消费者能消费到正确的信息?(重复消费、消息丢失、顺序消费)另外我凭空引入了MQ岂不是变相增加了我这个系统的复杂度?我怎么保证我这个系统是一直是可用的?另外我如果要是遇到了网络波动的问题怎么办????

这些问题在分布式系统也有对应的问题,只是在MQ中这些问题格外明显,这些我会再抽出一章的时间去写对应的问题以及其对应的解决办法。好的,我们来讲下一个优点!!!!

3、2 解耦

3y的例子简直是太好了,我就不写了直接搬过来,hhhhh,大家请看

现在我有一个系统A,系统A可以产生一个userId

消息队列一点 都 ! 不 ! 难 !系统A可以产生一个UserId

然后,现在有系统B和系统C都需要这个userId去做相关的操作

消息队列一点 都 ! 不 ! 难 !系统A给系统B和系统C传入userId这个值

写成伪代码可能是这样的:

public class SystemA {

  // 系统B和系统C的依赖
  SystemB systemB = new SystemB();
  SystemC systemC = new SystemC();

  // 系统A独有的数据userId
  private String userId = "Java3y";

  public void doSomething() {

      // 系统B和系统C都需要拿着系统A的userId去操作其他的事
      systemB.SystemBNeed2do(userId);
      systemC.SystemCNeed2do(userId);

  }
}

结构图如下:

消息队列一点 都 ! 不 ! 难 !结构图

ok,一切平安无事度过了几个天。

某一天,系统B的负责人告诉系统A的负责人,现在系统B的SystemBNeed2do(String userId)这个接口不再使用了,让系统A别去调它了

于是,系统A的负责人说"好的,那我就不调用你了。",于是就把调用系统B接口的代码给删掉了

public void doSomething() {

// 系统A不再调用系统B的接口了
//systemB.SystemBNeed2do(userId);
systemC.SystemCNeed2do(userId);

}

又过了几天,系统D的负责人接了个需求,也需要用到系统A的userId,于是就跑去跟系统A的负责人说:"老哥,我要用到你的userId,你调一下我的接口吧"

于是系统A说:"没问题的,这就搞"

消息队列一点 都 ! 不 ! 难 !系统A需要调用系统D的接口

然后,系统A的代码如下:

public class SystemA {

  // 已经不再需要系统B的依赖了
  // SystemB systemB = new SystemB();

  // 系统C和系统D的依赖
  SystemC systemC = new SystemC();
  SystemD systemD = new SystemD();

  // 系统A独有的数据
  private String userId = "Java3y";

  public void doSomething() {


      // 已经不再需要系统B的依赖了
      //systemB.SystemBNeed2do(userId);

      // 系统C和系统D都需要拿着系统A的userId去操作其他的事
      systemC.SystemCNeed2do(userId);
      systemD.SystemDNeed2do(userId);

  }
}

时间飞逝:

  • 又过了几天,系统E的负责人过来了,告诉系统A,需要userId。

  • 又过了几天,系统B的负责人过来了,告诉系统A,还是重新掉那个接口吧。

  • 又过了几天,系统F的负责人过来了,告诉系统A,需要userId。

  • ……

于是系统A的负责人,每天都被这给骚扰着,改来改去,改来改去…….

还有另外一个问题,调用系统C的时候,如果系统C挂了,系统A还得想办法处理。如果调用系统D时,由于网络延迟,请求超时了,那系统A是反馈fail还是重试??

最后,系统A的负责人,觉得隔一段时间就改来改去,没意思,于是就跑路了。

然后,公司招来一个大佬,大佬经过几天熟悉,上来就说:将系统A的userId写到消息队列中,这样系统A就不用经常改动了。为什么呢?下面我们来一起看看:

消息队列一点 都 ! 不 ! 难 !系统A将userId写到消息队列中,系统C和系统D从消息队列中拿数据

系统A将userId写到消息队列中,系统C和系统D从消息队列中拿数据。这样有什么好处

  • 系统A只负责把数据写到队列中,谁想要或不想要这个数据(消息),系统A一点都不关心

  • 即便现在系统D不想要userId这个数据了,系统B又突然想要userId这个数据了,都跟系统A无关,系统A一点代码都不用改。

  • 系统D拿userId不再经过系统A,而是从消息队列里边拿。系统D即便挂了或者请求超时,都跟系统A无关,只跟消息队列有关

这样一来,系统A与系统B、C、D都解耦了。

此部分引自------------------Java3y

3、3 削峰/限流

我再来给大家说一个场景,某一个风和日丽的中午,由于公司的成功运营,运营小哥宣布公司app的用户已经突破了100w大关,全公司的人都在庆祝,这时候开发小A在心里面想,"原来我做的系统这么牛*,这么多用户访问都不出问题,看来我代码写的很好啊,哈哈哈哈哈哈",就这样系统平静的运行了几天.小A每天都沉浸在自己写的代码之中.

突然有一天中午,有80W个用户同时访问系统,在这期间进行了大量的操作,大量的请求涌入到我们的系统之中,高峰期每秒钟能到5000个请求,大量的请求涌入Mysql,每秒钟大约要执行5000个sql,

大概就像这样,系统直接承受不住多出来的3000次访问请求,mysql直接就给搞崩了,这时候就不是杀一个程序猿出去祭天那么简单的了,但是我们现在用到接触到MQ了,可以用现在这种方法解决,这时候的流程图就是这样:

像这样,就能保证消Mysql系统就可以根据系统的能力去消费最大峰值的信息,并且不会因为一时高峰流量搞得整个系统都不能使用.

四、我们使用消息队列可能会出现什么问题??

经过了上面的介绍,我们发现虽然MQ可以做的事情挺多的,但是随时而来使用这门技术暴露的问题其实也挺多的,比如我上面刚说的

  • 消息服务挂掉了怎么办?我怎么保证我这个系统是一直是可用的

  • 我怎么能保证我一定能将消息发到消息服务中?

  • 我将消息发送到消息服务中,我怎么能保证消费者能消费到正确的信息?(重复消费、消息丢失、顺序消费

  • 另外我凭空引入了MQ岂不是变相增加了我这个系统的复杂度

  • 另外我如果要是遇到了网络波动的问题怎么办????

这些问题在本篇文章就不介绍了,我想准备下一期写一下关于这些问题的文章,下一期准备用RabbitMq举例,并贴出具体代码,

是不是有小伙伴会问,"你怎么不用ActiveMQ,RockerMQ,Kafka这些去举例呢?",不瞒大家说,这些我还不会(尬),以后可能会出几期这个的教程,那本期就结束了,如果大家觉得有帮助的话,希望可以给一个!谢谢大家~



参考资料:

  • 什么是消息队列? ------------Java3y

  • 浅谈消息队列及常见的消息中间件------------掘金零壹技术栈

  • Java工程师面试突击------------中华石杉老师  (这个链接不好找😂,也不知道哪一个是石杉老师真正的链接)

  • 消息队列的使用场景是怎样的------------知乎问答