SpringBoot2.x + RabbitMQ

前言
本文旨在提供基于SpringBoot2.x与RabbitMQ的快速集成。引入MQ的优缺点,提供了消息不丢失的解决方案。
此处只提供思路,具体的代码需要用户自行实现,比如生产端失败消息的再次投递:可以将失败的消息信息写入数据库,使用定时任务扫描数据库表,将消息重新投递到MQ当中。重复的消息:在消费端可以利用数据表的唯一键约束来去掉重复消息,或者利用redis的天生幂等性去重。

引入MQ的优缺点

MQ的优点是解耦,异步,削峰:
1.解耦:解除了不同模块之间的耦合,数据生产者只需要将消息推送到MQ即可发送消息,而数据消费者也只需根据需要监听MQ即可获取消息,生产者与消费者没有直接连接在一起。
2.异步:生产者将消息推送到MQ之后即可返回,而无需等待消费者执行结束,大大提高了系统响应速度。
3.削峰:假设某个微服务每秒钟能消费2000条消息,而在系统高峰期,每秒产生了5000条消息,多余的3000条将会在MQ中积压下来,等待高峰期过去,慢慢消费积压下来的消息。
MQ的缺点是增加了系统复杂度:
1.需要考虑确保消息 生产者->MQ(broker)->消费者 之间传递过程中消息100%不丢失。
2.在确保消息不丢失的同时,不可避免的带来了消息重复投递的问题,需要确保消息不重复消费。
3.确保系统的高可用

消息100%不丢失需要确保生产者、MQ、消费者三方均不丢失

a. 生产者不丢失消息需要开启confirm机制(此处不考虑事务机制,因为事务机制会造成吞吐量太低)。生产者为每一条消息设置唯一id,当消息成功投递到MQ交换机,交换机成功投递到队列后才会认为该条消息投递成功。
b. MQ需要开启消息数据持久化和元数据持久化,MQ的集群化部署。
c. 消费者需要开启手动ack机制(默认情况下会自动ack,自动删除消息),当消息被成功投递给消费者并且被成功消费后才可以删除该条消息。

代码实现

1.引入pom.xml依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2.application.yml

spring:
  rabbitmq:
    port: 5672           
    host: 127.0.0.1     
    publisher-confirms: true   #生产者将消息投递到mq的确认
    publisher-returns: true    #交换机将消息成功投递到队列的确认
    template:
      mandatory: true     #优先回调CallReturn()而不是CallConfirm()
    listener:
      simple:
        acknowledge-mode: manual   #开启手动ack

3.入口启动类配置 -----> @EnableRabbit

@EnableRabbit
@SpringBootApplication
public class RabbitMqApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(RabbitMqApplication.class);
    }
}

4.MQ配置类
创建交换机、队列、将交换机绑定到队列、将消息设置为json格式,设置消息投递的回调方法

@Slf4j
@Configuration
public class RabbitMqConfiguration
{
    @Resource
    AmqpAdmin amqpAdmin;

    @Autowired
    RabbitTemplate rabbitTemplate;

    //初始化设置为消息为json格式,如不设置,默认为byte[]格式并要求对象类消息实现Serializable接口
    @Bean
    public Jackson2JsonMessageConverter initMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    @PostConstruct
    public void initRabbitTemplate(){
        /**
         * 只要消息抵达Broker就会调用,ack=true
         * @param correlationData 当前消息的唯一id
         * @param ack 消息是否成功收到
         * @param cause 失败的原因
         */
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback()
        {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause)
            {
                log.info("confirm...correlationData:{}===>ack:{}===>cause:{}",correlationData,ack,cause);
            }
        });

        /**
         * 消息抵达队列,但消息没有成功投递给指定的队列就调用
         * @param message 投递失败的消息详细信息
         * @param replyCode 回复状态码
         * @param replyText 失败原因
         * @param exchange 这条消息所在的交换机
         * @param routingKey 这条消息的路由键
         */
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback()
        {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey)
            {
                log.info("Fail message:{}==>replayCode:{}==>replyText:{}==>exchange:{}==>routingKey:{}",message,replyCode,replyText,exchange,routingKey);
            }
        });
    }

    @PostConstruct
    public void createExchange()
    {
        DirectExchange directExchange = new DirectExchange("hello-exchange", true, false, null);
        amqpAdmin.declareExchange(directExchange);
    }

    @PostConstruct
    public void createQueue()
    {
        Queue queue = new Queue("hello-queue", true, false, false, null);
        amqpAdmin.declareQueue(queue);
    }

    @PostConstruct
    public void createBinding(){
        Binding binding = new Binding("hello-queue",Binding.DestinationType.QUEUE,"hello-exchange","hello.java",null);
        amqpAdmin.declareBinding(binding);
    }
}

5.生产者代码 UUID生成消息的唯一id

@Service
public class ProducerService
{
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMsg()
    {
        UserDto userDto = new UserDto();
        userDto.setId(1L);
        userDto.setUserName("xiaoming");
        rabbitTemplate.convertAndSend("hello-exchange","hello.java",userDto,new CorrelationData(UUID.randomUUID().toString()));

        TeacherDto teacherDto = new TeacherDto();
        teacherDto.setId(1L);
        teacherDto.setName("xiaohong");
        rabbitTemplate.convertAndSend("hello-exchange","hello.java",teacherDto,new CorrelationData(UUID.randomUUID().toString()));
    }
}

6.消费者代码 @RabbitListener可以标注类或方法。@RabbitHanler只可以标注方法,结合@RabbitListener做监听方法重载

@Component
@RabbitListener(queues = {"hello-queue"})
public class ConsumerService
{
    @RabbitHandler
    public void getUserMsg(Message msg, UserDto userDto, Channel channel)
    {
        MessageProperties messageProperties = msg.getMessageProperties();
        long deliveryTag = messageProperties.getDeliveryTag();
        log.info("User Info ==> {}",userDto);
        try
        {
            if(1==1){
                channel.basicAck(deliveryTag,false);
                log.info("成功签收消息");
            }else{
                channel.basicNack(deliveryTag,false,true);
                log.info("签收消息失败,消息重新进入队列");
            }
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
        }
    }

    @RabbitHandler
    public void getTeacherMsg(Message msg, TeacherDto teacherDto,Channel channel){
        MessageProperties messageProperties = msg.getMessageProperties();
        long deliveryTag = messageProperties.getDeliveryTag();
        log.info("Teacher Info ==> {}",teacherDto);
        try
        {
            if(1 != 1){
                channel.basicAck(deliveryTag,false);
                log.info("成功签收消息");
            }else{
                channel.basicNack(deliveryTag,false,true);
                log.info("签收消息失败,消息重新进入队列");
            }
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
        }
        log.info("Teacher info: ",teacherDto);
    }
}
  1. 例子中用到的Dto类
@Data
public class UserDto implements Serializable
{
    private Long id;
    private String userName;
}

@Data
public class TeacherDto
{
    private Long id;
    private String name;
    private int age;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值