我走过最长的路是你的套路

女:二号男嘉宾,假如我们牵手成功后,你会买名牌包包给我吗?

男:那你会听话吗?

女:会 听话。

男:听话 咱不买!

OK那么消息队列MQ有什么套路呢?(这个话题转换生硬度连我自己都怕!)

  1. 使用消息队列场景和好处
  2. 使用消息队列会带来什么问题,有什么解决方案
  3. 如何使用MQ(以ActiveMQ为例的简单例子)

1.消息队列的应用场景和好处:

  • 异步-流量削峰

  我们先来看下传统的服务器接收处理请求的流程

  如上图,在不使用消息队列服务器的时候,用户的请求都直怼数据库,在高并发的情况下数据库压力剧增,不仅使得响应速度变慢,还可能因此而挂掉数据库,导致用户页面直接报错,项目经理找上门,然后*#!%@!#** ......(PS:尽管是某服务挂了,但某宝的用户页面提示信息一定会甩锅给网络不通哦~)

  我们再来看加入消息队列服务器之后的接收处理请求的流程会发生什么变化

  如上图,在使用消息队列之后,即使在高并发的情况下用户的请求数据发送给消息队列之后立即返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理消息速度比数据库要快很多,因此响应速度(用户体验感)得到大幅改善。

  因此我们可以得出消息队列具有很好的流量削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削去高峰期的并发事务。如在某些电商平台的一些秒杀活动中,合理使用消息队列可以抵御活动刚开始大量请求涌入对系统的冲击。
  因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票等。

  • 异步-系统解耦

 我看也先来看看传统的系统数据传输模式

  如上图,主系统和其他系统的耦合性太强,都是直接调用,稍微有一点改动或者新增模块,双方都得改代码,过于麻烦

  然后,我们再来看看加入了消息队列之后,系统的结构会发生什么变化

  如上图,我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。

  消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。

  另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。

   除发布订阅模式之外,消息队列还有其他的传输模式

  点对点模型

  基础模型中,只有一个发送者、一个接收者和一个分布式队列。

  生产者消费者模型

  如果发送者和接收者都可以有多个部署实例,甚至不同的类型;但是共用同一个队列,这就变成了标准的生产者消费者模型。在该模型,三个角色一般称为生产(Producer)、分布式队列(Queue)、消费者(Consumer)。

   中途小结:消息队列对系统的并发处理的能力和扩展性有所提升

2.使用消息队列会带来什么问题:

  • 可用性降低: 在加入MQ之前,你不用考虑MQ服务器挂掉的情况,引入MQ之后你就需要去考虑了,可用性降低。
  • 复杂性提高: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等问题。因此需要考虑的东西更多,系统复杂性增大。
  • 数据一致性:消息队列带来的异步确实可以提高系统响应速度,但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了。

  2.1有什么解决方案

  • 对于可用性问题

  引入消息队列后,系统的可用性下降。实际项目中发送MQ消息,如果不做集群,其中mq机器出了故障宕机了,那么mq消息就不能发送了,系统就崩溃了,所以我们需要集群MQ,当其中一台MQ出了故障,其余的MQ机器可以接着继续运转,在生产中,没人使用单机的消息队列。如果有,那肯定为了用而用(显得技术复杂一下,好忽悠多收点钱),对于这个问题,需要对MQ集群技术有比较深刻的理解,各种消息中间件的集群方式不同,下面以ActiveMq的集群为例(Zookeeper+ActiveMq),先看图

  

  这种应用场景叫做master/slave(主/备模式),在这种场景下,我有三台服务器(主和备),任何情况下,只有“主”在工作,“备”是在主出现故障时,接替“主”来提供服务。在zookeeper的支持下,这一过程是这样实现的,Zookeeper提供目录和节点的服务,当我的三台服务器启动时,会在zookeeper的指定目录下创建对应自己的临时节点(这个过程称为“注册”),所谓临时节点,是靠心跳(定时向zookeeper服务器发送数据包)维系,当主服务器出现故障(无法向zookeeper服务器发送数据包,zookeeper会删除改临时节点。服务器向zookeeper注册时,zookeeper会分配序列号,我们认为序列号小的那个,就是“主”,序列号大的那个,就是“备”。

  当我们的客户端(通常是web server)需要访问服务时,需要连接zookeeper,获得指定目录下的临时节点列表,也就是已经注册的服务器信息,获得序列号小的那台“主”服务器的地址,进行后续的访问操作。以达到“总是访问主服务器”的目的。当“主”服务器发生故障,zookeeper从指定目录下删除对应的临时节点,同时可以通知关心这一变化的所有客户端,高效且迅速的传播这一信息。当下个请求来的时候,还是连接zookeeper,但是此时其实是访问备用的MQ。

  对于如何配置集群,这里就不演示,自行网上搜教程,一大把的!

  • 对于复杂性问题
  1. 如何保证消息不被重复消费呢?

  要回答好这个问题,首先要知道为什么消息会被重复消费,大多都是因为网络不通导致,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。所以解决问题的方式有如下三种思路

  1.如果消息是做数据库的插入操作,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
  2.如果你拿到这个消息做redis的set的操作,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
  3.如果上面两种情况还不行,准备一个第三服务方来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。

  2.如何保证消息的可靠性传输呢?

  其实这个问题是第一个问题的扩展,换而言之,我们要保证可靠性传输,其实就是保证防止生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据而已

  其实这些问题早在中间件开发者已经考虑到了,也提供了一些可配置的文件给我们自行设定相关参数,消息队列一般都会持久化到磁盘这个不用担心,然后生产者数据丢失的话MQ的事务会回滚,可以尝试重新发送,消费者丢的的话一般都是采用了自动确认消息模式导致消费信息被删,只要修改为手动确认就行了,也就是说消费者消费完之后,调用一个MQ的确认方法就行了

  3.如何保证从消息队列里拿到的数据按顺序执行?

  通过算法,将需要保持先后顺序的消息放到同一个消息队列中,然后只用一个消费者去消费该队列。

  (1)rabbitmq:拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理

  (2)kafka:一个topic,一个partition,一个consumer,内部单线程消费,写N个内存queue,然后N个线程分别消费一个内存queue即可

  4.如何解决消息队列的延时以及过期失效问题?有几百万消息持续积压几小时,怎么解决?

  这个问题是生产环境出现事故后的,考察你如何快速的解决问题,,消息队列的延迟和过期失效是消息队列的自我保护机制,目的是为了防止本身被挤爆,当然是可以关闭保护,比如当某个消息消费失败5次后,就把这个消息丢弃等,尽量不要关掉保护机制,那么问题来了,那些被丢弃的消息难道就不要了吗?其实并不是,我们可以针对该业务,查询出来将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq里面去,把丢的数据给他补回来。

  5.数据是通过push还是pull方式给到消费端,各自有什么弊端?

  • Push模型实时性好,但是因为状态维护等问题,难以应用到消息中间件的实践中,因为
  • 在Broker端需要维护Consumer的状态,不好适用于Broker去支持大量的Consumer的场景
    Consumer的消费速度是不一致的,Broker进行推送难以处理不同的Consumer的状况
    Broker难以应对Consumer无法消费消息的情况,因为不知道Consumer的宕机是短暂的还是永久的)
    另外推送消息(量可能会很大)也会加重Consumer的负载或者压垮Consumer。

    如果对应只有1个Consumer,用push比pull好。

  • Pull模式实现起来会相对简单一些,但是实时性取决于轮训的频率,在对实时性要求高的场景不适合使用。

3.如何使用MQ(以ActiveQM为例)

  附上官网:http://activemq.apache.org/

  附上启动服务访问地址:http://127.0.0.1:8161/admin/   用户名/密码 admin/admin

  附上代码,jar包自己下 https://pan.baidu.com/s/1SUBoypW-w_--KeFj_HsOtg

  发布订阅模式

  生产者-发布

public class JMSProducer {

    private static final String USERNAME=ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名
private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码
private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址
private static final int SENDNUM=10; // 发送的消息数量 public static void main(String[] args) { ConnectionFactory connectionFactory; // 连接工厂
Connection connection = null; // 连接
Session session; // 会话 接受或者发送消息的线程
Destination destination; // 消息的目的地
MessageProducer messageProducer; // 消息生产者 // 实例化连接工厂
connectionFactory=new ActiveMQConnectionFactory(JMSProducer.USERNAME, JMSProducer.PASSWORD, JMSProducer.BROKEURL); try {
connection=connectionFactory.createConnection(); // 通过连接工厂获取连接
connection.start(); // 启动连接
session=connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); // 创建Session
// destination=session.createQueue("FirstQueue1"); // 创建消息队列
destination=session.createTopic("FirstTopic1");
messageProducer=session.createProducer(destination); // 创建消息生产者
sendMessage(session, messageProducer); // 发送消息
session.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
if(connection!=null){
try {
connection.close();
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} /**
* 发送消息
* @param session
* @param messageProducer
* @throws Exception
*/
public static void sendMessage(Session session,MessageProducer messageProducer)throws Exception{
for(int i=0;i<JMSProducer.SENDNUM;i++){
TextMessage message=session.createTextMessage("ActiveMQ 发送的消息"+i);
System.out.println("发送消息:"+"ActiveMQ 发布的消息"+i);
messageProducer.send(message);
}
}
}

  消费者-订阅

/**
* 消息监听-订阅者一
* @author Administrator
*
*/
public class Listener implements MessageListener{ @Override
public void onMessage(Message message) {
// TODO Auto-generated method stub
try {
System.out.println("订阅者一收到的消息:"+((TextMessage)message).getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
public class JMSConsumer {

    private static final String USERNAME=ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名
private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码
private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址 public static void main(String[] args) {
ConnectionFactory connectionFactory; // 连接工厂
Connection connection = null; // 连接
Session session; // 会话 接受或者发送消息的线程
Destination destination; // 消息的目的地
MessageConsumer messageConsumer; // 消息的消费者 // 实例化连接工厂
connectionFactory=new ActiveMQConnectionFactory(JMSConsumer.USERNAME, JMSConsumer.PASSWORD, JMSConsumer.BROKEURL); try {
connection=connectionFactory.createConnection(); // 通过连接工厂获取连接
connection.start(); // 启动连接
session=connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); // 创建Session
// destination=session.createQueue("FirstQueue1"); // 创建连接的消息队列
destination=session.createTopic("FirstTopic1");
messageConsumer=session.createConsumer(destination); // 创建消息消费者
messageConsumer.setMessageListener(new Listener()); // 注册消息监听
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

  end

  by the way

  我认为一个优秀的分布式消息队列,应该具备以下的能力:高吞吐、低时延(因场景而异),传输透明,伸缩性强,有冗灾能力,一致性顺序投递,同步+异步的发送方式,完善的运维和监控工具和开源

  

关于MQ,你必须知道的的更多相关文章

  1. C#刨根究底:《你必须知道的.NET》读书笔记系列

    一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...

  2. (转)【推荐】初级.NET程序员,你必须知道的EF知识和经验

    转自:http://www.cnblogs.com/zhaopei/p/5721789.html [推荐]初级.NET程序员,你必须知道的EF知识和经验   阅读目录   [本文已下咒.先顶后看,会涨 ...

  3. 《你必须知道的.NET》读书笔记一:小OO有大智慧

    此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.对象  (1)出生:系统首先会在内存中分配一定的存储空间,然后初始化其附加成员,调用构造函数执行初 ...

  4. 《你必须知道的.NET》读书笔记二:小OO有大原则

    此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.单一职责原则 (1)核心思想:一个类最好只做一件事,只有一个引起它变化的原因 (2)常用模式:Fa ...

  5. 《你必须知道的.NET》读书笔记三:体验OO之美

    此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.依赖也是哲学 (1)本质诠释:“不要调用我们,我们会调用你” (2)依赖和耦合: ①无依赖,无耦合 ...

  6. 《你必须知道的.NET》读书笔记:从Hello World认识IL

    通用的语言基础是.NET运行的基础,当我们对程序运行的结果有异议的时候,如何透过本质看表面,需要我们从底层来入手探索,这时候,IL便是我们必须知道的基础. 一.IL基础概念 1.1 什么是IL? IL ...

  7. MVC中你必须知道的13个扩展点

    MVC中你必须知道的13个扩展点 pasting 转:http://www.cnblogs.com/kirinboy/archive/2009/06/01/13-asp-net-mvc-extensi ...

  8. 前端开发必须知道的JS(二) 闭包及应用

    http://www.cnblogs.com/ljchow/archive/2010/07/06/1768749.html 在前端开发必须知道的JS(一) 原型和继承一文中说过下面写篇闭包,加之最近越 ...

  9. 《你必须知道的.NET》书中对OCP(开放封闭)原则的阐述

    开放封闭原则(OCP,Open Closed Principle)是面向对象原则的核心.由于软件设计本身所追求的墓边就是封装变化,降低耦合,而开放封闭原则就是对这一目标的直接体现.(你必须知道的.NE ...

  10. Apple的App Analytics统计平台你必须知道的Q&A整理与翻译

    Apple的App Analytics统计平台你必须知道的Q&A整理与翻译 Apple最近在iTunesConnect里最新发布了App Analytics统计平台,提供了现有友盟统计平台和自 ...

随机推荐

  1. Lenghth of Last Word

    description: Given a string s consists of upper/lower-case alphabets and empty space characters ' ', ...

  2. SpringBoot自定义拦截器实现IP白名单功能

    SpringBoot自定义拦截器实现IP白名单功能 转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/8993331.html 首先,相关功能已经上线了,且先让我先 ...

  3. File文件操作学习总结

    1.java.io.file用于表示文件(目录),也就是说程序员可以通过File类在程序中操作硬盘上的文件和目录, 2.File类只能用于表示文件(目录)的信息(名称和大小),不能对文件内容进行访问. ...

  4. 静态成员函数与pthread_create,纯虚函数匹配使用实例

    最近在浏览朋友写的代码,发现有一个细节非常值得学习,在这里将代码贴出来简单分享一下: #ifndef THREAD_H_ #define THREAD_H_ #include <pthread. ...

  5. 10个Python练手小程序,学习python的很好的资料

    [程序1] 题目:有1.2.3.4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少? 1.程序分析:可填在百位.十位.个位的数字都是1.2.3.4.组成所有的排列后再去  掉不满足条件的排列 ...

  6. Oracle数据库查询优化方案(处理上百万级记录如何提高处理查询速度)

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引.2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引 ...

  7. linux线程及互斥锁

    进程是资源管理的最小单元,线程是程序执行的最小单元.在操作系统的设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销. 就像进程有一个PID一样,每个线程也有 ...

  8. 查找linux设备的uuid

    [root@ ~]# blkid /dev/vdc /dev/vdc: UUID="bxxxx-xxx-41b9-8146-7da8bd645b92" TYPE="ext ...

  9. UnicodeEncodeError: 'ascii' codec can't encode character...的解决方法

    在python2.7下,因为想从数据库中读出来分类名进行写入到文件,提示 Traceback (most recent call last): File "test.py", li ...

  10. MySQL 8 新特性之降序索引

    MySQL 8.0终于支持降序索引了.其实,从语法上,MySQL 4就支持了,但正如官方文档所言,"they are parsed but ignored",实际创建的还是升序索引 ...