SpringBoot 是为了简化 Spring 应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 WEB 工程

MQ全称(Message Queue)又名消息队列,是一种异步通讯的中间件。可以将它理解成邮局,发送者将消息传递到邮局,然后由邮局帮我们发送给具体的消息接收者(消费者),具体发送过程与时间我们无需关心,它也不会干扰我进行其它事情。常见的MQ有kafka、activemq、zeromq、rabbitmq 等等,各大MQ的对比和优劣势可以自行Google

rabbitmq

RabbitMQ是一个遵循AMQP协议,由面向高并发的erlanng语言开发而成,用在实时的对可靠性要求比较高的消息传递上,支持多种语言客户端。支持延迟队列(这是一个非常有用的功能)….

基础概念

Broker:简单来说就是消息队列服务器实体
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列
Queue:消息队列载体,每个消息都会被投入到一个或多个队列
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来
Routing Key:路由关键字,exchange根据这个关键字进行消息投递
vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离
producer:消息生产者,就是投递消息的程序
consumer:消息消费者,就是接受消息的程序
channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务

基于Centos7.x安装请参考: http://blog.battcn.com/2017/08/20/linux/linux-centos7-ribbitmq/

常见应用场景

  1. 邮箱发送:用户注册后投递消息到rabbitmq中,由消息的消费方异步的发送邮件,提升系统响应速度
  2. 流量削峰:一般在秒杀活动中应用广泛,秒杀会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。用于控制活动人数,将超过此一定阀值的订单直接丢弃。缓解短时间的高流量压垮应用。
  3. 订单超时:利用rabbitmq的延迟队列,可以很简单的实现订单超时的功能,比如用户在下单后30分钟未支付取消订单
  4. 还有更多应用场景就不一一列举了…..

导入依赖

在 pom.xml 中添加 spring-boot-starter-amqp的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

属性配置

在 application.properties 文件中配置rabbitmq相关内容,值得注意的是这里配置了手动ACK的开关

1
2
3
4
5
6
7
spring.rabbitmq.username=battcn
spring.rabbitmq.password=battcn
spring.rabbitmq.host=192.168.0.133
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
# 手动ACK 不开启自动ACK模式,目的是防止报错后未正确处理消息丢失 默认 为 none
spring.rabbitmq.listener.simple.acknowledge-mode=manual

具体编码

定义队列

如果手动创建过或者RabbitMQ中已经存在该队列那么也可以省略下述代码…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.battcn.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* RabbitMQ配置
*
* @author Levin
* @since 2018/4/11 0011
*/
@Configuration
public class RabbitConfig { public static final String DEFAULT_BOOK_QUEUE = "dev.book.register.default.queue";
public static final String MANUAL_BOOK_QUEUE = "dev.book.register.manual.queue"; @Bean
public Queue defaultBookQueue() {
// 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
return new Queue(DEFAULT_BOOK_QUEUE, true);
} @Bean
public Queue manualBookQueue() {
// 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
return new Queue(MANUAL_BOOK_QUEUE, true);
}
}

实体类

创建一个Book类

1
2
3
4
5
6
7
8
public class Book implements java.io.Serializable {

    private static final long serialVersionUID = -2164058270260403154L;

    private String id;
private String name;
// 省略get set ...
}

控制器

编写一个Controller类,用于消息发送工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.battcn.controller;

import com.battcn.config.RabbitConfig;
import com.battcn.entity.Book;
import com.battcn.handler.BookHandler;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; /**
* @author Levin
* @since 2018/4/2 0002
*/
@RestController
@RequestMapping(value = "/books")
public class BookController { private final RabbitTemplate rabbitTemplate; @Autowired
public BookController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
} /**
* this.rabbitTemplate.convertAndSend(RabbitConfig.DEFAULT_BOOK_QUEUE, book); 对应 {@link BookHandler#listenerAutoAck}
* this.rabbitTemplate.convertAndSend(RabbitConfig.MANUAL_BOOK_QUEUE, book); 对应 {@link BookHandler#listenerManualAck}
*/
@GetMapping
public void defaultMessage() {
Book book = new Book();
book.setId("1");
book.setName("一起来学Spring Boot");
this.rabbitTemplate.convertAndSend(RabbitConfig.DEFAULT_BOOK_QUEUE, book);
this.rabbitTemplate.convertAndSend(RabbitConfig.MANUAL_BOOK_QUEUE, book);
}
}

消息消费者

默认情况下 spring-boot-data-amqp 是自动ACK机制,就意味着 MQ 会在消息消费完毕后自动帮我们去ACK,这样依赖就存在这样一个问题:如果报错了,消息不会丢失,会无限循环消费,很容易就吧磁盘空间耗完,虽然可以配置消费的次数但这种做法也有失优雅。目前比较推荐的就是我们手动ACK然后将消费错误的消息转移到其它的消息队列中,做补偿处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.battcn.handler;

import com.battcn.config.RabbitConfig;
import com.battcn.entity.Book;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; import java.io.IOException; /**
* BOOK_QUEUE 消费者
*
* @author Levin
* @since 2018/4/11 0011
*/
@Component
public class BookHandler { private static final Logger log = LoggerFactory.getLogger(BookHandler.class); /**
* <p>TODO 该方案是 spring-boot-data-amqp 默认的方式,不太推荐。具体推荐使用 listenerManualAck()</p>
* 默认情况下,如果没有配置手动ACK, 那么Spring Data AMQP 会在消息消费完毕后自动帮我们去ACK
* 存在问题:如果报错了,消息不会丢失,但是会无限循环消费,一直报错,如果开启了错误日志很容易就吧磁盘空间耗完
* 解决方案:手动ACK,或者try-catch 然后在 catch 里面讲错误的消息转移到其它的系列中去
* spring.rabbitmq.listener.simple.acknowledge-mode=manual
* <p>
*
* @param book 监听的内容
*/
@RabbitListener(queues = {RabbitConfig.DEFAULT_BOOK_QUEUE})
public void listenerAutoAck(Book book, Message message, Channel channel) {
// TODO 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
final long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.info("[listenerAutoAck 监听的消息] - [{}]", book.toString());
// TODO 通知 MQ 消息已被成功消费,可以ACK了
channel.basicAck(deliveryTag, false);
} catch (IOException e) {
try {
// TODO 处理失败,重新压入MQ
channel.basicRecover();
} catch (IOException e1) {
e1.printStackTrace();
}
}
} @RabbitListener(queues = {RabbitConfig.MANUAL_BOOK_QUEUE})
public void listenerManualAck(Book book, Message message, Channel channel) {
log.info("[listenerManualAck 监听的消息] - [{}]", book.toString());
try {
// TODO 通知 MQ 消息已被成功消费,可以ACK了
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
// TODO 如果报错了,那么我们可以进行容错处理,比如转移当前消息进入其它队列
}
}
}

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.battcn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* @author Levin
*/
@SpringBootApplication
public class Chapter11Application { public static void main(String[] args) {
SpringApplication.run(Chapter11Application.class, args);
}
}

测试

完成准备事项后,启动Chapter11Application 访问 http://localhost:8080/books 将会看到如下内容,就代表一切正常….

1
2
2018-05-22 19:04:26.708  INFO 23752 --- [cTaskExecutor-1] com.battcn.handler.BookHandler           : [listenerAutoAck 监听的消息] - [com.battcn.entity.Book@77d8be18]
2018-05-22 19:04:26.709 INFO 23752 --- [cTaskExecutor-1] com.battcn.handler.BookHandler : [listenerManualAck 监听的消息] - [com.battcn.entity.Book@8bb452]

初探RabbitMQ消息队列的更多相关文章

  1. RabbitMQ消息队列(一): Detailed Introduction 详细介绍

     http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...

  2. RabbitMQ消息队列1: Detailed Introduction 详细介绍

    1. 历史 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有 ...

  3. (转)RabbitMQ消息队列(九):Publisher的消息确认机制

    在前面的文章中提到了queue和consumer之间的消息确认机制:通过设置ack.那么Publisher能不到知道他post的Message有没有到达queue,甚至更近一步,是否被某个Consum ...

  4. (转)RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)

    在云计算环境中,很多时候需要用它其他机器的计算资源,我们有可能会在接收到Message进行处理时,会把一部分计算任务分配到其他节点来完成.那么,RabbitMQ如何使用RPC呢?在本篇文章中,我们将会 ...

  5. (转)RabbitMQ消息队列(六):使用主题进行消息分发

    在上篇文章RabbitMQ消息队列(五):Routing 消息路由 中,我们实现了一个简单的日志系统.Consumer可以监听不同severity的log.但是,这也是它之所以叫做简单日志系统的原因, ...

  6. (转)RabbitMQ消息队列(四):分发到多Consumer(Publish/Subscribe)

    上篇文章中,我们把每个Message都是deliver到某个Consumer.在这篇文章中,我们将会将同一个Message deliver到多个Consumer中.这个模式也被成为 "pub ...

  7. RabbitMQ消息队列应用

    RabbitMQ消息队列应用 消息通信组件Net分布式系统的核心中间件之一,应用与系统高并发,各个组件之间解耦的依赖的场景.本框架采用消息队列中间件主要应用于两方面:一是解决部分高并发的业务处理:二是 ...

  8. RabbitMQ消息队列(四):分发到多Consumer(Publish/Subscribe)

    上篇文章中,我们把每个Message都是deliver到某个Consumer.在这篇文章中,我们将会将同一个Message deliver到多个Consumer中.这个模式也被成为 "pub ...

  9. RabbitMQ消息队列(九):Publisher的消息确认机制

    在前面的文章中提到了queue和consumer之间的消息确认机制:通过设置ack.那么Publisher能不到知道他post的Message有没有到达queue,甚至更近一步,是否被某个Consum ...

随机推荐

  1. Java实现 LeetCode 318 最大单词长度乘积

    318. 最大单词长度乘积 给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母.你可以认为每个单词只包含小 ...

  2. SimpleDateFormat 和 Calendar 对于时间的处理

    import java.text.SimpleDateFormat;import java.util.Date;public class test { public static void main( ...

  3. vj提交时常见问题

  4. 1.Go 开始搞起

    link 1. IDE Go Land 服务器激活 2. 资源 中文网站 翻译组 翻译组wiki 待认领文章 入门指南 中文文档 fork 更新 github 中如何定期使用项目仓库内容更新自己 fo ...

  5. 有a1,a2,a3,a4,四个数组,四个数组重新组合成一个数组(A),间隔是10个元素

    好久没折腾Py了,这是去年年初2019.3月发在Q中的一个记录,因不从事这个,并且被在工厂耽误10几年,所以很少写Blog在这里,感觉这里比较正式,而在Q中只是随意性的记载, 但发布图片总是需要另外再 ...

  6. Python基础002---基础知识

    一.标识符 标识符是自己定义的,是开发人员在程序中自己定义的一些符号和名称,如变量名.函数名等.在 Python 里,标识符由字母(区分大小写).数字.下划线组成,且数字不能开头.常用的命名方法有小驼 ...

  7. 【精讲版】上位机C#/.NET与西门子PLC通信

    618来啦 亲们,腾讯课堂101机构打榜了,快来助力<新阁教育>,<免费赠送课程>! 1.手机QQ(微信请也来一遍)扫下方二维码↓,找到<新阁教育> 2.点击“支持 ...

  8. 学员操作——制作5s关闭广告

    <!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>广 ...

  9. pyhton 月份和天数的计算

    http://stackoverflow.com/questions/546321/how-do-i-calculate-the-date-six-months-from-the-current-da ...

  10. python 类(object)的内置函数

    python 类(object)的内置函数 # python 类(object)的内置函数 ### 首先 #### 以__双下划线开头的内置函数 __ #### __往往会在某些时候被自动调用,例如之 ...