目录:

  • 什么是消息中间件
  • MQ的作用
  • JMS规范与AMQP协议
  • RabbitMQ组件
  • 消息过期
  • RabbitMQ实现延迟队列
  • 持久化
  • 事务
  • 发送方确认机制
  • RabbitMQ管理
  • RabbitMQ集群
  • 镜像队列
  • 关于RabbitMQ性能优化的建议
  • RabbitMQ实战

1、什么是消息中间件

Message Queue Middleware,简称MQ,是一种利用高效可靠的消息传递机制进行与平台无关的数据交互的技术。

2、MQ的作用

异步:类似于短信业务,将需要发送的消息放入MQ中,让其它主流程业务正常运行,异步监听短信消息。

解耦:利用MQ隐含的、基于数据的接口达到解耦;就像家里的电器(如冰箱、烤炉、热水器等),它们并不是直接接在电路上的,而是通过插座达到通电的目的。

削峰:请求一进来,便将数据放入MQ,然后依次执行,降低后端服务压力。

冗余:某些情况下处理数据失败就会丢失数据,此时可以将这些数据备份到MQ中,直到处理完再舍弃这些消息。

3、JMS规范与AMQP协议

Java Message Service,仅适用于java平台的消息中间件规范。

元素:连接工厂、JMS连接、JMS会话、JMS目的、JMS生产者、JMS消费者、Broker。

Advanced Message Queuing Protocol,支持不同语言的消息中间件规范。

组件:

a、生产者、消费者。

b、消息:包括有效载荷与标签;有效载荷是存储需要传输的数据,标签是描述有效载荷属性东西

c、信道、交换器、队列、路由键:生产者将消息通过信道发送到交换器,交换器通过路由键将消息路由到不同的队列后供消费者消费

RabbitMQ特性:高效可靠易扩展、消息确认机制、队列消息持久化、消息拒收、默认交换器、mandatory(消息防丢失)。

4、RabbitMQ组件

a、交换器

交换器的类型:

  • fanout:仅需交换器匹配。
  • direct:BindingKey和RoutingKey完全匹配。
  • topic:广播,类似于direct;但BindingKey允许使用通配符,#可匹配多个或零个单词,*匹配一个单词。
  • header:根据消息内容中的headers属性进行匹配。

public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException

创建交换器参数说明:

  • exchange:交换器名称。
  • type:交换器类型。
  • durable:是否持久化。
  • autoDelete:是否自动删除,前提是必须要有解绑动作,且是全部与这个交换器解绑。
  • internal:是否内置路由器,客户端无法直接发送消息到交换器,只能通过交换器路由到内置路由器。
  • argument:其它结构化参数。

argument:alternate-exchange(备份交换器)

  1. Map<String, Object> args = new HashMap<String, Object>();
  2. args.put("alternate-exchange", "exchangeName");

public AMQP.Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException

删除交换器参数说明:

  • exchange:交换器名称。
  • ifUnused:是否未使用,true=交换器未使用时才会被删除,false=直接删除。

b、队列

Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;

定义队列参数说明:

  • queue:队列名称。
  • durable:是否持久化。
  • exclusive:是否排他,true=仅对首次创建它的连接可见,并在连接段开始自动删除;适用于同一个客户端同时发送读写消息的场景。
  • autoDelete:是否自动删除,当连接该队列的消费者都断开连接后,队列删除。
  • arguments:定义队列的参数列表。

arguments:

  • x-message-ttl:消息过期时间,单位ms。
  • x-expries:静置消息删除时间,单位ms。
  • x-max-length:队列消息最大长度。
  • x-max-length-bytes:队列最大占用空间大小,单位B。
  • x-dead-letter-exchange:死信队列交换器名称。
  • x-dead-letter-routing-key:死信队列路由键。
  • x-max-priority:队列优先级。
  • x-queue-mode:将消息保存在磁盘上,不存在内存中,当消费者开始消费时才加载到内存中。

通过x-dead-letter-exchangex-dead-letter-routing-key实现死信队列(DLX):

DLX实质就是一个交换器,只是在队列满足以下条件时会将消息转到DLX里:

  • 消息被拒,且requeue=false
  • 队列过期或队列达到最大长度

Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;

删除队列参数说明:

  • queue:队列名称。
  • ifUnused:无消费者消费时删除。
  • ifEmpty:队列无消息时删除。

c、生产者

void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException;

发送消息参数说明:

  • exchange:交换器名称。
  • routingKey:路由键。
  • mandatory:true=当发送的消息无法根据交换器类型和路由键匹配到合适的队列上时自身未满足条件),将消息返回给生产者;false=将消息丢弃。
  • immediate:true=当路由到队列上时无任何消费者他人不满足条件),则将消息返回给生产者;false=将消息丢弃。
  • props:消息的属性。
  • body:消息体。

d、消费者

消费者有两种方式消费,一种是推(自动监听),一种是拉(手动获取单条消息)。

推:String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer callback) throws IOException;

拉:GetResponse basicGet(String queue, boolean autoAck) throws IOException;

参数说明:

  • autoAck:是否自动确认消息已被消费。
  • noLocal:设置为ture时,不能将同一个connection生产的消息在此connection消费,也就是说一个connection不能同时为生产者和消费者
  • exclusive:是否排他。
  • callback:接收到消息的回调函数,用于处理具体逻辑。

消息的确认与拒绝:

当消费消息autoAck=false时,消费者可以手动确认消息已消费。

void basicAck(long deliveryTag, boolean multiple) throws IOException;

参数说明:

  • multiple:true=确认消费该信道上deliveryTag标签之前所有未确认的消息。

拒绝单条消息:void basicReject(long deliveryTag, boolean requeue) throws IOException;

拒绝多条消息:void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;

5、消息过期

RabbitMQ中消息过期分为两种,一种是队列过期,一种是消息过期

队列过期就是设置queueDeclare的arguments的x-message-ttl,消息过期是通过发送消息的props参数确定

  1. AMQP.BasicProperties.Builder publishBuilder = new AMQP.BasicProperties.Builder();
  2. // expiration单位ms
  3. publishBuilder.expiration("10000");
  4.  
  5. channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY_NAME, publishBuilder.build(),
  6. "ttl".getBytes());

对于第一种TTL来说,队列一但过期就会删除掉;但对于第二种TTL来说,队列过期不会马上删除,而是等队列要被消费时再判断是否要删除

那为什么会不一样呢,我们都知道mq对性能的要求是非常高的,如果第二种TTL的方式也要及时删除的话势必要扫描整个队列,这样的话,若队列长度较大是性能便会非常的差。

而第一种为什么可以做到及时删除呢,我们知道队列具有先进先出的特性,所以先入队的肯定要比后入队的要先过期,所以只要删除头部的就好啦

而第二种的消息过期时间都是不固定的,考虑到MQ的性能,所以采用了上述的方式。

6、RabbitMQ实现延迟队列

a、DB轮询:通过job或其它逻辑将订单表的必要字段查出(如:orderId、createTime、status),当订单超过xx时间,将状态置为失效。

b、JDK DelayQueue:java api提供的延迟队列的实现,通过poll()、take()方法获取超时任务。

c、Redis sortedSet:通过zset类型的score来实现。

d、RabbitMQ TTL + DLX:使用RabbitMQ的过期时间和死信队列实现。

7、持久化

a、客户端不要设置自动确认消息(autoAck),而是由服务端确认。

b、发送端确认机制、镜像队列。

8、事务

a、开启事务:channel.txSelect()

b、提交事务:channel.txCommit()

c、回滚事务:channel.txRollback()

9、发送方确认机制

因为事务太过重量了,严重影响的RabbitMQ的吞吐量,所以RabbitMQ提供了一种更为轻量的方式,来保证生产者发送的消息真真的到达了RabbitMQ

a、首先需要将信道设置为发送方确认模式

channel.confirmSelect();

b、然后通过waitForConfirms()或waitForConfirmsOrDie()确认消息已发送成功

boolean waitForConfirms(long timeout) throws InterruptedException, TimeoutException;

void waitForConfirmsOrDie(long timeout) throws IOException, InterruptedException, TimeoutException;

  1. public class PublisherConfirmProduct {
  2.  
  3. private static final String EXCHANGE_NAME = "demo.exchange";
  4. private static final String ROUTING_KEY = "demo.routingkey";
  5. private static final String QUEUE_NAME = "demo.queue";
  6. private static final String MESSAGE = "Hello World!";
  7.  
  8. /**
  9. * 单条确认
  10. */
  11. public static void commonConfirm() throws Exception {
  12. Connection connection = RabbitMqUtils.getConnection();
  13. Channel channel = initChannel(connection);
  14.  
  15. channel.confirmSelect();
  16. for (int i = 0; i < 100; i++) {
  17. channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, MESSAGE.getBytes());
  18. if (channel.waitForConfirms()) {
  19. // 逐条确认是否发送成功
  20. System.out.println("send success!");
  21. }
  22. }
  23.  
  24. RabbitMqUtils.close(connection, channel);
  25. }
  26.  
  27. /**
  28. * 批量确认
  29. */
  30. public static void batchConfirm() throws Exception {
  31. Connection connection = RabbitMqUtils.getConnection();
  32. Channel channel = initChannel(connection);
  33.  
  34. channel.confirmSelect();
  35. for (int i = 0; i < 100; i++) {
  36. channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, MESSAGE.getBytes());
  37. }
  38.  
  39. // 批量确认是否发送成功,如果某一次确认失败这一批都要重新发送
  40. if (channel.waitForConfirms()) {
  41. System.out.println("send success!");
  42. }
  43.  
  44. RabbitMqUtils.close(connection, channel);
  45. }
  46.  
  47. /**
  48. * 异步确认
  49. */
  50. public static void asyncConfirm() throws Exception {
  51. Connection connection = RabbitMqUtils.getConnection();
  52. Channel channel = initChannel(connection);
  53. channel.basicQos(1);
  54.  
  55. channel.confirmSelect();
  56.  
  57. // 定义一个未确认消息集合
  58. final SortedSet<Long> unConfirmSet = Collections.synchronizedNavigableSet(new TreeSet<>());
  59. for (int i = 0; i < 100; i++) {
  60. channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, MESSAGE.getBytes());
  61. unConfirmSet.add(channel.getNextPublishSeqNo());
  62. }
  63.  
  64. channel.addConfirmListener(new ConfirmListener() {
  65. @Override
  66. public void handleNack(long deliveryTag, boolean multiple) throws IOException {
  67. System.err.println(format("拒绝消息 deliveryTag:{0}, multiple:{1}", deliveryTag, multiple));
  68. }
  69.  
  70. @Override
  71. public void handleAck(long deliveryTag, boolean multiple) throws IOException {
  72. System.err.println(format("确认消息 deliveryTag:{0}, multiple:{1}", deliveryTag, multiple));
  73. if (multiple) {
  74. // multiple为true,则deliveryTag之前的所有消息全部被确认
  75. unConfirmSet.headSet(deliveryTag + 1).clear();
  76. } else {
  77. // 否则只确认一条消息
  78. unConfirmSet.remove(deliveryTag);
  79. }
  80. }
  81. });
  82.  
  83. TimeUnit.SECONDS.sleep(5);
  84. System.out.println(unConfirmSet.size());
  85.  
  86. RabbitMqUtils.close(connection, channel);
  87. }
  88.  
  89. private static Channel initChannel(Connection connection) throws IOException {
  90. Channel channel = connection.createChannel();
  91. channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
  92. channel.queueDeclare(QUEUE_NAME, true, false, false, null);
  93. channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
  94. return channel;
  95. }
  96.  
  97. public static void main(String[] args) throws Exception {
  98. // commonConfirm();
  99. // batchConfirm();
  100. asyncConfirm();
  101. }
  102. }

10、RabbitMQ管理

a、vhost

Vhost(virtual host),其本质是一个独立的小型RabbitMQ服务器,拥有自己独立的队列、交换器以及绑定关系等,并且它拥有自己独立的权限。

客户端连接RabbitMQ时必须制定一个vhost,默认使用"/"。

也就是说一个RabbitMQ服务可以拥有很多个子RabbitMQ服务,这些子RabbitMQ服务可以为其它很多应用程序提供服务;这样可以减少RabbitMQ服务器搭建的成本。

b、角色与权限

权限控制是以vhost为单位,创建用户时至少要指定一个vhost;对于用户来说可以跨vhost授权,不同的vhost可以有不同的权限。

角色:

  • none:无任何角色,新创建的用户默认角色为none。
  • management:可以访问web管理页面。
  • policymaker:包含management的所有权限,并可以管理策略(Policy) 和参数(Parameter)。
  • monitoring:包含management的所有权限,并可以看到所有连接、信道及节点相关信息。
  • administartor:包含minitoring的所有权限,并可以管理用户、虚拟主机、权限、策略、参数等。

11、RabbitMQ集群

a、为什么要集群:人多力量大,通过线性扩展来提高RabbitMQ的吞吐量

b、集群后就能保证RabbitMQ的消息不丢失嘛:当然不能保证,默认情况下RabbitMQ的消息是不会在集群中复制的,节点宕机后消息就会丢失。

c、队列和交换器再集群中是以什么样的形式存在:队列不会复制,其它节点只存储了此队列所在节点的元数据;而交换器本质就是一个hashMap的映射,节点间会进行复制

d、关于集群的建议:集群中至少要有一个磁盘节点(也就是持久化的RabbitMQ节点),虽然磁盘节点挂掉了依然可以发送和接受消息,但却不能执行创建队列、交换器、绑定关系等等操作高可用的话建议至少两个磁盘节点,如果不确认如何选择磁盘节点与内存节点时建议全部选择磁盘节点,但这样的话会在一定程度上影响RabbitMQ的吞吐量。

12、镜像队列

RabbitMQ集群并不能保证队列的复制,而镜像队列就是用来解决这一问题的。它可以将对队列镜像到其它集群节点中,若集群中一个节点失效了它会自动切换到另一个镜像的节点上,来保证服务的可用性。

13、关于RabbitMQ性能优化的建议

影响RabbitMQ性能的因素有很多,主要的分为硬件性能软件性能

硬件性能:如网络、内存、CPU等等。

软件性能:消息持久化、消息确认、路由算法与绑定规则等等。

1、消息持久化:持久化会写入磁盘,多一次IO操作,设置非持久化可提升性能(durable=false)。

2、消息确认:消费者订阅队列时,设置自动确认也可以提升性能(autoAck=true)。

3、路由算法与绑定规则:fanout,只要绑定了交换器就可以匹配到,匹配规则少,性能肯定是绑定规则中最佳的;direct,除了交换器还需要匹配路由键,性能次之;topic,最复杂的匹配规则,相对其它两个性能最差。(比较fanout > direct > topic

14、RabbitMQ实战

见 https://www.cnblogs.com/bzfsdr/p/11906222.html

RabbitMQ学习笔记(八、RabbitMQ总结)的更多相关文章

  1. [RabbitMQ学习笔记] - 初识RabbitMQ

    RabbitMQ是一个由erlang开发的AMQP的开源实现. 核心概念 Message 消息,消息是不具名的,它由消息头和消息体组成,消息体是不透明的,而消息头则由 一系列的可选属性组成,这些属性包 ...

  2. 官网英文版学习——RabbitMQ学习笔记(一)认识RabbitMQ

    鉴于目前中文的RabbitMQ教程很缺,本博主虽然买了一本rabbitMQ的书,遗憾的是该书的代码用的不是java语言,看起来也有些不爽,且网友们不同人学习所写不同,本博主看的有些地方不太理想,为此本 ...

  3. RabbitMQ学习笔记(五) Topic

    更多的问题 Direct Exchange帮助我们解决了分类发布与订阅消息的问题,但是Direct Exchange的问题是,它所使用的routingKey是一个简单字符串,这决定了它只能按照一个条件 ...

  4. RabbitMQ学习笔记1-hello world

    安装过程略过,一搜一大把. rabbitmq管理控制台:http://localhost:15672/   默认账户:guest/guest RabbitMQ默认监听端口:5672 JAVA API地 ...

  5. (转) Rabbitmq学习笔记

    详见原文: http://blog.csdn.net/shatty/article/details/9529463 Rabbitmq学习笔记

  6. 官网英文版学习——RabbitMQ学习笔记(十)RabbitMQ集群

    在第二节我们进行了RabbitMQ的安装,现在我们就RabbitMQ进行集群的搭建进行学习,参考官网地址是:http://www.rabbitmq.com/clustering.html 首先我们来看 ...

  7. RabbitMQ学习笔记五:RabbitMQ之优先级消息队列

    RabbitMQ优先级队列注意点: 1.只有当消费者不足,不能及时进行消费的情况下,优先级队列才会生效 2.RabbitMQ3.5以后才支持优先级队列 代码在博客:RabbitMQ学习笔记三:Java ...

  8. Learning ROS forRobotics Programming Second Edition学习笔记(八)indigo rviz gazebo

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...

  9. python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑

    python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑 许多人在安装Python第三方库的时候, 经常会为一个问题困扰:到底应该下载什么格式的文件?当我们点开下载页时, 一般 ...

  10. Go语言学习笔记八: 数组

    Go语言学习笔记八: 数组 数组地球人都知道.所以只说说Go语言的特殊(奇葩)写法. 我一直在想一个人参与了两种语言的设计,但是最后两种语言的语法差异这么大.这是自己否定自己么,为什么不与之前统一一下 ...

随机推荐

  1. ARTS-S c++调用pytorch接口

    想跑通第1个参考资料上讲的例子,一定要注意gcc和gperftools的版本.因为LibTorch用了c++17的over-aligned新特性. centos默认的gcc是4.8.5不支持这个新特性 ...

  2. 使用iCamera 测试MT9F002 1400w高分辨率摄像头说明

    一.硬件准备 l MT9F002摄像头模块 l iCamera 硬件主板CC1601 l MT9F002转接板(FPC_MT9F002 CB1602) 二.软件准备 l 下载iCamera软件 l 安 ...

  3. Python递归函数如何写?正确的Python递归函数用法

    前言本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归 ...

  4. 使用笛卡尔积生成sku

    /// <summary> /// 生成SKU价格表 /// </summary> /// <param name="model"></p ...

  5. Vue中兄弟组件间传值-(Bus/总线/发布订阅模式/观察者)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. 《一头扎进》系列之Python+Selenium框架设计篇2- 价值好几K的框架,不看白不看,看了还想看

    1. 简介 上一篇介绍了自动化框架的架构,今天宏哥就带领小伙伴或者童鞋们开始开工往这个框架里开始添砖加瓦.主要是介绍一个框架unittest单元测试框架和一种设计思想POM. 2. unittest单 ...

  7. JS基础知识——原型与原型链

    1.如何准确判断一个变量的数组类型 2.写一个原型链继承的例子 3.描述new一个对象的过程 4.zepto(或其他框架中如何使用原型链) 知识点: (1)构造函数 function Foo(name ...

  8. django生命周期请求l流程图

    django思维导图链接:https://www.processon.com/view/link/5dddb0f8e4b074c442e5c68c

  9. 《Dotnet9》系列-开源C# WPF控件库2《Panuon.UI.Silver》强力推荐

    时间如流水,只能流去不流回! 点赞再看,养成习惯,这是您给我创作的动力! 本文 Dotnet9 https://dotnet9.com 已收录,站长乐于分享dotnet相关技术,比如Winform.W ...

  10. EXT grid单元格点击时判断当前行是否可编辑

    var c_gridColumns = new Ext.grid.ColumnModel({ columns: [//列模式 c_sm, { header: "内码", dataI ...