近期在做一个电商秒杀项目,在服务优化的阶段用到了 RabbitMQ 这个消息中间件,让秒杀请求不再瞬时冲击秒杀接口,而是利用消息中间件来让请求如队列般排队而来。
下面首先介绍最基础的一种 SpringBoot 集成 RabbitMQ 简单消息队列模式的方式:添加 pom 依赖、 yml 配置、配置类、消息发送者、消息接收者
1、添加 pom 依赖,这里需要添加 amqp,amqp 是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
2、yaml 配置文件,各参数说明已给出注释
spring:
rabbitmq:
host: 192.168.10.132
username: guest
password: guest
# 虚拟主机
virtual-host: /
port: 5672
listener:
simple:
# 消费者最小数量
concurrency: 10
# 消费者最大数量
max-concurrency: 10
# 限制消费者每次只处理一条消息,处理完再继续下一条消息
prefetch: 1
# 启动时是否默认启动容器,默认true
auto-startup: true
# 被拒绝时重新进入队列
default-requeue-rejected: true
template:
retry:
# 发布重试,默认false
enabled: true
# 重试时间 默认1000ms
initial-interval: 1000
# 重试最大次数,默认3次
max-attempts: 3
# 重试最大间隔时间,默认10000ms
max-interval: 1000
# 重试间隔的乘数。比如配2.0 第一次等10s,第二次等20s,第三次等40s
multiplier: 1.0
3、配置类
@Configuration
public class RabbitMQConfig {
@Bean
public Queue queue() {
return new Queue("queue", true);
}
}
4、消息生产者(发送者)
@Service
@Slf4j
public class MQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(Object msg) {
log.info("发送消息:" + msg);
rabbitTemplate.convertAndSend("queue", msg);
}
}
5、消息消费者(接收者)
@Service
@Slf4j
public class MQReceiver {
@RabbitListener(queues = "queue")
public void receive(Object msg) {
log.info("接收消息:" + msg);
}
}
这里写一个接口调用消息发送者进行消息的发送
/**
* 测试 rabbitmq
*/
@RequestMapping("/mq")
@ResponseBody
public void mq() {
mqSender.send("Hello");
}
rabbitmq_management UI 管控页面的情况
可见,根据上述测试情况,简单模式下,消息生产者与消息消费者已对接成功。
Fanout 交换机模式对应 RabbitMQ 消息队列的发布/订阅模式,此模式不处理路由键,只需要简单的将队里绑定到交换机上发送到交换机的消息都会被转发到与该交换机绑定的所有队列上Fanout交换机转发消息是最快的。
在上述 2 中的简单队列模式中修改配置类,改为两个队列、一个交换机
@Configuration
public class RabbitMQConfig {
private static final String QUEUE01 = "queue_fanout01";
private static final String QUEUE02 = "queue_fanout02";
private static final String EXCHANGE = "fanoutExchange";
@Bean
public Queue queue01() {
return new Queue(QUEUE01);
}
@Bean
public Queue queue02() {
return new Queue(QUEUE02);
}
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(EXCHANGE);
}
@Bean
public Binding binding01() {
return BindingBuilder.bind(queue01()).to(fanoutExchange());
}
@Bean
public Binding binding02() {
return BindingBuilder.bind(queue02()).to(fanoutExchange());
}
}
消息发送方中,设置交换机名
@Service
@Slf4j
public class MQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(Object msg) {
log.info("发送消息:" + msg);
rabbitTemplate.convertAndSend("fanoutExchange", "", msg);
}
}
消息接收方中配置两个监听的接收者
@Service
@Slf4j
public class MQReceiver {
@RabbitListener(queues = "queue_fanout01")
public void receive01(Object msg) {
log.info("QUEUE01接受消息:" + msg);
}
@RabbitListener(queues = "queue_fanout02")
public void receive02(Object msg) {
log.info("QUEUE02接受消息:" + msg);
}
}
编写测试接口
/**
*fanout模式:测试 rabbitmq
*/
@RequestMapping("/mq/fanout")
@ResponseBody
public void mq01() {
mqSender.send("Hello");
}
再次运行项目后,在 UI 管控页面即可看到我们自己注册的交换机
点开此交换机后也可看到我们绑定的两个队列
rabbitmq_management UI 管控页面的情况
Direct 交换机模式对应 RabbitMQ 消息队列的路由模式,所有发送到Direct Exchange的消息被转发到RouteKey中指定的Queue,注意,Direct模式可以使用RabbitMQ自带的Exchange(default Exchange),所以不需要将Exchange进行任何绑定(binding)操作,消息传递时,RouteKey必须完全匹配才会被队列接收,否则该消息会被抛弃。重点:routing key与队列queues 的key保持一致,即可以路由到对应的queue中。
配置类
@Configuration
public class RabbitMQConfigDirect {
private static final String QUEUE01 = "queue_direct01";
private static final String QUEUE02 = "queue_direct02";
private static final String EXCHANGE = "directExchange";
private static final String ROUTINGKEY01 = "queue.red";
private static final String ROUTINGKEY02 = "queue.green";
@Bean
public Queue queue01() {
return new Queue(QUEUE01);
}
@Bean
public Queue queue02() {
return new Queue(QUEUE02);
}
@Bean
public DirectExchange directExchange() {
return new DirectExchange(EXCHANGE);
}
@Bean
public Binding binding01() {
return BindingBuilder.bind(queue01()).to(directExchange()).with(ROUTINGKEY01);
}
@Bean
public Binding binding02() {
return BindingBuilder.bind(queue02()).to(directExchange()).with(ROUTINGKEY02);
}
}
消息发送方
@Service
@Slf4j
public class MQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send01(Object msg) {
log.info("发送red消息:" + msg);
rabbitTemplate.convertAndSend("directExchange", "queue.red", msg);
}
public void send02(Object msg) {
log.info("发送green消息:" + msg);
rabbitTemplate.convertAndSend("directExchange", "queue.green", msg);
}
}
消息接收方
@Service
@Slf4j
public class MQReceiver {
@RabbitListener(queues = "queue_direct01")
public void receive01(Object msg) {
log.info("QUEUE01接受消息:" + msg);
}
@RabbitListener(queues = "queue_direct02")
public void receive02(Object msg) {
log.info("QUEUE02接受消息:" + msg);
}
}
编写测试接口
/**
* 测试 RabbitMQ
*/
@RequestMapping("/mq/direct01")
@ResponseBody
public void mq01() {
mqSender.send01("Hello,Red");
}
/**
* 测试 RabbitMQ
*/
@RequestMapping("/mq/direct02")
@ResponseBody
public void mq02() {
mqSender.send02("Hello,Green");
}
Topic 交换机模式对应 RabbitMQ 消息队列的主题模式,所有发送到Topic Exchange的消息被转发到所有管线RouteKey中指定Topic的Queue上Exchange将RouteKey和某Topic进行模糊匹配,此时队列需要绑定一个Topic,对于routing key匹配模式定义规则举例如下:
配置类
@Configuration
public class RabbitMQConfigTopic {
private static final String QUEUE01 = "queue_topic01";
private static final String QUEUE02 = "queue_topic02";
private static final String EXCHANGE = "topicExchange";
private static final String ROUTINGKEY01 = "#.queue.#";
private static final String ROUTINGKEY02 = "*.queue.#";
@Bean
public Queue queue01() {
return new Queue(QUEUE01);
}
@Bean
public Queue queue02() {
return new Queue(QUEUE02);
}
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(EXCHANGE);
}
@Bean
public Binding binding01() {
return BindingBuilder.bind(queue01()).to(topicExchange()).with(ROUTINGKEY01);
}
@Bean
public Binding binding02() {
return BindingBuilder.bind(queue02()).to(topicExchange()).with(ROUTINGKEY02);
}
}
消息发送方
@Service
@Slf4j
public class MQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send01(Object msg) {
log.info("发送消息(被01队列接受):" + msg);
rabbitTemplate.convertAndSend("topicExchange", "queue.red.message", msg);
}
public void send02(Object msg) {
log.info("发送消息(被两个queue接受):" + msg);
rabbitTemplate.convertAndSend("topicExchange", "message.queue.green.abc", msg);
}
}
消息接收方
@Service
@Slf4j
public class MQReceiver {
@RabbitListener(queues = "queue_topic01")
public void receive01(Object msg) {
log.info("QUEUE01接受消息:" + msg);
}
@RabbitListener(queues = "queue_topic02")
public void receive02(Object msg) {
log.info("QUEUE02接受消息:" + msg);
}
}
编写测试接口
/**
* 测试 RabbitMQ
*/
@RequestMapping("/mq/topic01")
@ResponseBody
public void mq01() {
mqSender.send01("Hello,Red");
}
/**
* 测试 RabbitMQ
*/
@RequestMapping("/mq/topic02")
@ResponseBody
public void mq02() {
mqSender.send02("Hello,Green");
}