Spring Boot 集成RabbitMQ一篇就够

面对日益复杂的信息平台,消息队列使用是解决什么问题呢?初步总结一下可以解决如下场景问题:

  • 业务解耦:A系统需要耦合B、C、D系统,在消息队列之前可以通过共享数据、接口调用等方式来实现业务,现在可以通过消息中间件进行解耦。
  • 削峰填谷:在互联网经常会出现流量突然飙升的情况,以前很多时候就是通过性能优化、加服务器等方式,可以通过消息中间件缓存相关任务,然后按计划的进行处理。
  • 异步:可以通过消息推送及短信发送进行说明,业务平台并不关注具体消息的发送细则,完全可以通过消息队列的方式,直接下发任务,由任务消费者进行处理。

本文将通过Rabbit MQ、Spring Boot集成使用来进行分享,大致章节如下:

  • RabbitMQ环境搭建及常用命令
  • Spring Boot接入
  • 5种常用模式
  • 消息持久化
  • 消息延迟发送
  • 消息可靠性发送

Part-1:环境搭建

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。
RabbitMQ服务器是用Erlang语言编写的,
而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。

  • 安装erlang
yum install erlang  
  • 安装rabbitmq及常用命令

    这里是在官网下载的rpm包进行安装:下载地址如下:https://www.rabbitmq.com/download.html
#安装
rpm -ivh --nodeps rabbitmq-server-3.8.7-1.el6.noarch.rpm    
#开启后台管理功能   
sudo rabbitmq-plugins enable rabbitmq_management    
#开机自启动
chkconfig rabbitmq-server on           

# Rabbitmq常用命令
#启动服务
service rabbitmq-server start
#查看rabbitmq状态
service rabbitmq-server status
#重启服务
service rabbitmq-server restart   
#停止服务
service rabbitmq-server stop    

#查看账号列表
sudo rabbitmqctl list_users
#添加账号
sudo rabbitmqctl add_user username password
#设置用户标签
sudo rabbitmqctl set_user_tags username tagname
#删除用户
sudo rabbitmqctl delete_user username
#设置visualhost权限
sudo rabbitmqctl set_permissions -p / username ".*" ".*" ".*"

Part-2:Spring Boot接入

  • maven引用Spring Boot AMQP插件
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 交换机初始化
public static final String FANOUT_EXCHANGE = "fanout.exchange";
@Bean(name = FANOUT_EXCHANGE)
public FanoutExchange fanoutExchange() {
    return new FanoutExchange(FANOUT_EXCHANGE, true, false);
}
  • 队列初始化
public static final String FANOUT_QUEUE1 = "fanout.queue1";
@Bean(name = FANOUT_QUEUE1)
public Queue fanoutQueue1() {
    return new Queue(FANOUT_QUEUE1, true, false, false);
}
  • 交换机队列绑定
@Bean
public Binding bindingSimpleQueue1(@Qualifier(FANOUT_QUEUE1) Queue fanoutQueue1,
                                   @Qualifier(FANOUT_EXCHANGE) FanoutExchange fanoutExchange) {
    return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
  • 发送者
@Component
public class RabbitMQSenderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    //发送消息,不需要实现任何接口,供外部调用。
    public void send(String exchange,
                     String routingkey,
                     Message message) {

        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
        System.out.println("开始发送消息 : " + message);
        rabbitTemplate.convertAndSend(exchange, routingkey, message, correlationId);
        System.out.println("结束发送消息 : " + message);
    }
}
  • 接收者
@Component
class RabbitMQReciver {
    
    @RabbitListener(queues = RabbitFanoutExchangeConfig.FANOUT_QUEUE1)
    public void reciveLogAll(String msg) throws Exception {
        System.out.println("log.all:" + msg);
    }
}
  • 配置信息
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=rabbitmq
spring.rabbitmq.password=rabbitmq
spring.rabbitmq.virtual-host=study

Part-3:消息队列常用5中模式

  • Simple Work Queue 简单工作队列
    在这里插入图片描述

PS:该模式在Spring boot中是很少用到的一个场景,一般都会通过Exchange进行消息分配到队列从而为以后扩展预留一个入口。

  • Publish/Subscribe 发布订阅模式
    在这里插入图片描述

该模式性能最好,拿到消息直接放入队列

  • Routing 路由模式
    在这里插入图片描述

该模式通过routing key 进行全字匹配,匹配上将相关消息放入相关队列。

  • Topics 主题模式
    在这里插入图片描述

该模式通过routng key进行模糊匹配,匹配上将相关信息放入相关队列,具体匹配规则如下:

字符*:匹配单个单词,比如log.* 可以匹配log.all、log.error等

字符#:可以匹配0个或多个标识符,如log.# 可以匹配log.business.all,log.service.info等

  • Header 模式
    在这里插入图片描述

通过message header头部信息进行比对,可以根据定义全匹配、部分匹配等规则

Part-4:消息持久化

消息持久化需要对交换机持久化、队列持久化、消息持久化,代码如下:

#初始化Exchange,name是交换机名,durable是否持久化,autoDelete当前会话结束删除
public AbstractExchange(String name, boolean durable, boolean autoDelete)

#初始化Queue, name是队列名,durable是否持久化,exclusive排他性,autodelete当前会话结束删除
public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete) 

#发送消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);

Part-5:延迟发送

方案1:TTL机制实现

在这里插入图片描述

针对消息延迟发送可通过死性队列进行处理:

  • 消息超时:弊端第一条消息超时设置一分钟,第二条消息超时设置2秒中,那么第二条消息消费也要等第一条消息消费玩才能生效,时间不累加。
MessageProperties messageProperties=new MessageProperties();
messageProperties.setExpiration("2000"); //设置超时时间为2S
Message message=new Message(message_01.getBytes(),messageProperties);
  • 队列超时:队列超时就是任何一个消息放入都会在指定时间过期,如果消息自身设置了超时时间,那就看消息超时和队列超时那个时间短,以短时间为准。
@Bean(name = DELAY_QUEUE)
public Queue delayQueue() {
    Map<String, Object> queueProperties = new HashMap<>();
    queueProperties.put("x-dead-letter-exchange", DEAD_EXCHANGE);
    queueProperties.put("x-dead-letter-routing-key", "lsf.dead.letter");
    queueProperties.put("x-message-ttl",10000);#设置超时时间为10s
    return new Queue(DELAY_QUEUE, true, false, false, queueProperties);
}
方案2: rabbitmq_delay_message_exchange 插件实现

rabbitmq官网插件下载页面:https://www.rabbitmq.com/community-plugins.html

安装步骤如下:
第1步:选择rabbitmq_delayed_message_exchange进入具体插件下载页,选择相关版本号:rabbitmq_delayed_message_exchange-3.8.0.ez
第2步:copy文件到路径/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.7/plugins中
第3步:启用插件:rabbitmq-plugins enable rabbitmq_delay_message_exchange
第4步:重启Rabbitmq服务
在SpringBoot中实现,交换机声明为x-delayed-message,然后消息发送设置delay属性,代码如下:

//声明交换机为x-delayed-message
public static final String CUSTOM_DELAY_EXCHANGE = "lsf.custom.delay.exhange";
@Bean(name = CUSTOM_DELAY_EXCHANGE)
public Exchange customDelayExchange() {
    Map<String, Object> exchangeArgs = new HashMap<>();
    exchangeArgs.put("x-delayed-type","direct");

    return new CustomExchange(CUSTOM_DELAY_EXCHANGE,
            "x-delayed-message",
            true,
            false,
            exchangeArgs);
}

//消息发送
MessageProperties messageProperties=new MessageProperties();
messageProperties.setDelay(150000);
String message_01="第一条消息延迟:15s  "+sdf.format(date);
Message message=new Message(message_01.getBytes(),messageProperties);
rabbitmqService.send(RabbitCustomDeleyConfig.CUSTOM_DELAY_EXCHANGE,
        RabbitCustomDeleyConfig.CUSTOM_DELAY_MESSAGE,
        message);

PS:消息会在交换机中等待,设置时间到了就会被消费,比消息过期机制好,并不会出现第一条消息过期时间长,第二条消息过期时间短还需要等第一条消费后才消费第二条。

Part-6:可靠性发送与接收

  • 生产端可靠性发送–传递到Exchange相关处理方法:
    通过消息发送判断传递到Exchange的回调,开启回调
//连接开启回调
connectionFactory.setPublisherConfirms(true);
//绑定回调触发方法
rabbitTemplate.setConfirmCallback(new RabbitConfirmCallback());

实现RabbitTemplate.ConfirmCallback封装实现方法

package lsf.rabbit.config;

import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

public class RabbitConfirmCallback implements RabbitTemplate.ConfirmCallback {
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            System.out.println("消息发送成功:" + correlationData);
        } else {
            System.out.println("消息发送失败:" + cause);
        }
    }
}
  • 生产端可靠性发送–消息未传递到队列回调:
//开启消息没有传递到队列回调
connectionFactory.setPublisherReturns(true);
//开启回调和绑定回调方法
rabbitTemplate.setMandatory(true);//开启强制委托模式
rabbitTemplate.setReturnCallback(new RabbitReturnsCallback());

继承接口RabbitTemplate.ReturnCallback实现消息未到队列业务处理

package lsf.rabbit.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

public class RabbitReturnsCallback implements RabbitTemplate.ReturnCallback {
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        String result = String.format("消息发送ReturnCallback-未到达消息队列:{%s}--{%s}--{%s}--{%s}--{%s}",
                message, replyCode, replyText, exchange, routingKey);
        System.out.println(result);
    }
}
  • 消费者可靠性接收
    一般在队列中比如消息消费异常,不做处理就会将消息放回对头,很容易陷入死循环,所以可以通过死性队列的方式将消息序列化到另外的队列设置超时重试,
    或入库补偿机制等方式。死性队列可以参考前面章节
//配置确认模式为手动的
@RabbitListener(queues = RabbitRetryConfig.RETRY_NORMAL_QUEUE, ackMode = "MANUAL")

#手动拒绝消息,其中requeue为否再次入队
channel.basicReject(msg.getMessageProperties().getDeliveryTag(), false);
#手动否定确认,其中requeue为否再次入队
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, false);
参考资料:

Spring Boot AMQP:https://spring.io/projects/spring-amqp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值