springboot 集成rabbit mq消息队列

上一篇讲了如何在Mac上安装rabbit mq,现在在springboot框架下使用rabbit mq,初次尝试消息队列的使用。

1.消息队列的基础概念

MQ全称(Message Queue)又名消息队列,是一种异步通讯的中间件。我们可以将它理解成邮局,发送者将消息传递到邮局,然后由邮局帮我们发送给具体的消息接收者(消费者),具体发送过程与时间我们无需关心,它也不会干扰我进行其它事情。常见的MQ有kafka、activemq、zeromq、rabbitmq 等等…
我们先来了解下消息队列中涉及的一些术语的含义。

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

工作原理:

消息队列中最重要的三个概念是,生产者,消息队列,消费者是。生产者发消息到消息队列中去,消费者监听指定的消息队列,并且当消息队列收到消息之后,接收消息队列传来的消息,并且给予相应的处理。消息队列常用于分布式系统之间互相信息的传递。

对于RabbitMQ来说,除了这三个基本模块以外,还添加了一个模块,即交换机(Exchange)。
它使得生产者和消息队列之间产生了隔离,生产者将消息发送给交换机,而交换机则根据调度策略把相应的消息转发给对应的消息队列。那么RabitMQ的工作流程如下所示:
springboot 集成rabbit mq消息队列_第1张图片
左侧 P 代表 生产者,也就是往 RabbitMQ 发消息的程序。
中间即是 RabbitMQ,其中包括了 交换机 和 队列。
右侧 C 代表 消费者,也就是往 RabbitMQ 拿消息的程序。

交换机的主要作用:是接收相应的消息并且绑定到指定的队列.交换机有四种类型,分别为Direct, topic, headers, Fanout.
  Direct:RabbitMQ默认的交换机模式,也是最简单的模式。即创建消息队列的时候,指定一个BindingKey。当发送者发送消息的时候,指定对应的Key,当Key和消息队列的BindingKey一致的时候,消息将会被发送到该消息队列中。
  Topic:转发信息主要是依据通配符,队列和交换机的绑定主要是依据一种模式(通配符+字符串),而当发送消息的时候,只有指定的Key和该模式相匹配的时候,消息才会被发送到该消息队列中。
  Headers:也是根据一个规则进行匹配,在消息队列和交换机绑定的时候会指定一组键值对规则,而发送消息的时候也会指定一组键值对规则,当两组键值对规则相匹配的时候,消息会被发送到匹配的消息队列中。
  Fanout:是路由广播的形式,将会把消息发给绑定它的全部队列,即便设置了key,也会被忽略。

接下来,我们看在springboot中如何使用Rabbit MQ消息队列(以Direct 模式为例)。

  1. 在pom.xml中引入依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>
  1. 在application.yml 中配置消息队列
### RabbitMQ 相关配置
rabbitmq:
  host: localhost
  port: 5672
  username: guest
  password: guest
  virtual-host: /
  #开启发送确认
  publisher-confirms: true
  #开启发送失败退回
  publisher-returns: true
  #开启ack
  listener:
    simple:
      acknowledge-mode: manual

ps:
这里端口是5672,不是15672…15672是管理端的端口!

这里先贴出我的代码结构:
springboot 集成rabbit mq消息队列_第2张图片

  1. 编写Rabbitmq的配置类,RabbitConfig.java
@Configuration
public class RabbitConfig {
    private static final Logger log = LoggerFactory.getLogger(RabbitConfig.class);

    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return factory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory,MessageConverter messageConverter) {
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //消息发送失败后回到队列,yml配置 publisher-returns: true
        rabbitTemplate.setMandatory(true);
        //消息确认, yml配置 publisher-confirms: true
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause)
                -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause));
        //消息确认, yml配置 publisher-returns: true
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey)
                -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message));
        rabbitTemplate.setMessageConverter(messageConverter);
        return rabbitTemplate;
    }

    @Bean
    public MessageConverter messageConverter() {
        return new ContentTypeDelegatingMessageConverter(new Jackson2JsonMessageConverter());
    }
}

ps:
如果你设置了手动ACK,不仅要在application.yml中开启manual,也一定要在配置类中添加:factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); 不然会double ACK,会导致channel error。

  1. 编写生产者,RabbitmqSender.java
@Service("rabbitmqSender")
public class RabbitmqSender {
    private static final Logger logger = LoggerFactory.getLogger(RabbitmqSender.class);
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void send (String exchange, String routingKey, Object message){
        logger.info("RabbitmqSender: 使用消息队列发送:  "+"发送时间:"+new Date()+"   发送内容:" +message);
        rabbitTemplate.convertAndSend(exchange, routingKey, message);
    }
}
  1. 编写消费者,监听消息队列。
@Component
@RabbitListener(queues = "student")
public class RabbitmqReciever {
    private static final Logger log = LoggerFactory.getLogger(RabbitmqReciever.class);

    @RabbitListener(queues = "student")
    public void listenerAutoAck(Message message, Channel channel) {
        // TODO 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            log.info("消息队列消费者监听到消息::"+new String(message.getBody(),"utf-8"));
            // TODO 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            try {
                // TODO 处理失败,重新压入MQ
                log.error(e.getMessage());
                channel.basicRecover();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

ps:
我这里监听的是student队列,我并没有在代码中创建exchange和queue,而是在rabbit mq的管理平台上创建了对应的exchange 和 queue 还有绑定的key值,这样,我们在项目中需要使用到消息队列推送消息的时候,就把对应的exchange,queue,key的值传入即可。

RabbitMQ 管理平台上的设置:
springboot 集成rabbit mq消息队列_第3张图片
springboot 集成rabbit mq消息队列_第4张图片
springboot 集成rabbit mq消息队列_第5张图片

  1. 使用消息队列推送消息。此处测试在service层查找用户的方法中使用发送消息。
    @Override
    public Student findByName(String name) {
/*        String key = "stu_name"+name;
        ValueOperations operations = redisTemplate.opsForValue();
        boolean hasKey = redisTemplate.hasKey(key);
        Student student = new Student();
        if(hasKey){
            student = operations.get(key);
            log.info("从缓存中获取数据!");
        }else{
            student = studentDao.findByName(name);
            log.info("从数据库中获取数据,并加入缓存!");
            //插入缓存
            operations.set(key,student,5,TimeUnit.MINUTES);
        }*/

        Student student = new Student();
        student = studentDao.findByName(name);
        String message = name + "学生已经找到!";
        rabbitmqSender.send("student.exchange","student.key",message);

        return student;
    }
  1. 结果展示。
    springboot 集成rabbit mq消息队列_第6张图片
    springboot 集成rabbit mq消息队列_第7张图片

你可能感兴趣的:(springboot基础学习,rabbit,mq,Rabbit,MQ)