Spring AMQP(重点掌握)

2,516 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15 天,点击查看活动详情

4.Spring AMQP(重点掌握)

4.1.简介

Sprin有很多不同的项目,其中就有对AMQP的支持:

1527089338661.png

Spring AMQP的页面:projects.spring.io/spring-amqp…

1527089365281.png

注意这里一段描述:

Spring-amqp是对AMQP协议的抽象实现,而spring-rabbit 是对协议的具体实现,也是目前的唯一实现。底层使用的就是RabbitMQ。

4.2.依赖和配置

添加AMQP的启动器:

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

application.yml中添加RabbitMQ地址: 手动在控制台 新建一个虚拟机 /heima

server:
  port: 8081
spring:
  application:
    name: producer-application
  rabbitmq:
    virtual-host: /heima
    username: guest
    password: guest
    addresses: 127.0.0.1:5672

4.3.快速入门

我们以直连 direct 为例,看看Spring中如何发送消息、接收消息。

image.png

4.3.1.生成者发送消息

Spring为AMQP提供了统一的消息处理模板:RabbitTemplate,非常方便的发送消息,其发送方法:

1599899378264.png

比较常用的3个方法,分别是:

  • 指定交换机、RoutingKey和消息体
  • 指定消息(默认队列)
  • 指定RoutingKey和消息,这里的RoutingKey其实是队列名称

代码如下: 向指定队列直接发送消息

@RestController
public class TestController {
​
    @Resource
    private RabbitTemplate rabbitTemplate;
​
    @GetMapping("/send")
    public String testSend(){
        String uuid = UUID.randomUUID().toString();
        for(int i=0;i<10;i++){
            System.out.println("消息发送时间:" + System.currentTimeMillis());
//            直接向指定的队列投递消息
            rabbitTemplate.convertAndSend("heima-queue","hello world" + (i+1));
        }
        return "ok";
    }
​
}

启动main 浏览器 输入地址 :

http://localhost:8081/send

结果:

image.png

4.3.2.消费消息

@Component
public class TestConsumer {
​
    @RabbitListener(queues = "heima-queue")//  注意只需要 队列名称一致即可!
    public void receive(String msg, Channel channel, Message message) throws IOException {
        //会话唯一ID
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println("收到消息id:" + deliveryTag);
            Thread.sleep(1000);
            System.out.println("message:" +message.toString());
        }catch (Exception e){
        }
    }
​
}

运行后查看日志:

1599899772187.png

springboot默认也是ack自动确认!

1599899784341.png

4.3.3 broker服务器的消息签收

试想一下: 如果我们接受消息,进行业务处理时,出现异常,那么我们该如何处理消息呢?

  • 消息获取之后 业务代码异常,我们一般会捕捉异常,将消息放回到消息队列中 常见现象: 网络波动或延迟
  • 还有一种处理方案: 消息不再放回队列,通知broker 直接丢弃消息

此种情况一般都是手动处理ack 开启手动处理ack模式:

配置文件

server:
  port: 8082
spring:
  application:
    name: consumer-application
  rabbitmq:
    virtual-host: /hei
    username: guest
    password: guest
    addresses: 127.0.0.1:5672
    listener:
      direct:
        acknowledge-mode: manual   # 消费者端 手动确认ack  broker队列消息手动通知删除

消费端与broker之间的消息签收概览:

1599963654786.png

我们的默认配置是自动签收,也就是只要消费端监听到队列的消息后不管有没有处理完自己的业务会先给broker服务器发送已签收的状态。

一般项目开发需要自己手动签收,只有消费端成功执行完业务后才告知服务器说我们已经成功消费了这一条消息。通过这种手动签收的方式来达到消费端与mq broker之间的可靠性保障

在消费端配置文件中设置属性 spring.rabbitmq.listener.simple.acknowledge-mode=manual

消费端消费示例:

   @RabbitListener(queues = "hei-queue")
    public void receive(String msg, Channel channel, Message message) throws IOException {
        //会话唯一ID
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
//         System.out.println(1/0);//  模拟业务代码 出现异常
            System.out.println("deliveryTag = "+deliveryTag+"-------------------message:" +msg);
            channel.basicAck(deliveryTag,false);//  手动确认ack  通知broker 删除当前消息
            //   multiple:是否批量处理.true: 一次性将ack所有小于deliveryTag的消息删除
            //   false   从 deliveryTag 消息索引开始  一条一条确认 删除
        }catch (Exception e){
            //  消息 重新放回队列机制配置
//            channel.basicNack(deliveryTag,false,false);//  第三个参数 : true  表示 出现代码业务一次 消息 重新放回队列中 再次获取
        }
    }
channel.basicNack(deliveryTag,false,true);
//requeue=true 表示 放回到队列中,如果 第三个参数 false  那么此消息不会放回队列中! 队列会直接删除此消息

示例1:演示逐条删除

发送10条消息到队列上,执行下面代码

image.png

结果:

image.png

消息内容 : 1-4

1603788040931.png

示例2: 演示批量删除消息

重新发送10条消息

image.png 演示批量删除: 代码

image.png 效果:

image.png 批量删除6条消息:

剩余消息内容: 7-8-9-10

1603788536316.png 示例3:

测试消息重新放回队列代码: 模拟异常信息 发一条消息测试最好!

在catch模块中添加:下面代码

channel.basicNack(deliveryTag,false,false); //requeue=false

image.png 效果:控制台一直在跳动

web界面:

image.png 阶段小结: mq消息确认最终方式:

  • yml配置文件:
spring:
  application:
    name: consumer-application
  rabbitmq:
    virtual-host: /hei
    username: guest
    password: guest
    addresses: 127.0.0.1:5672
    listener:
      simple:
        acknowledge-mode: manual  # 手动开启 ack 确认机制
  • 代码:

image.png

5. 综合案例-MQ完成邮件发送

1600573746843.png 当用户量骤增的情况下: 我们将邮件的发送功能和用户的核心业务拆分:

分析: 当用户进行数据库核心业务操作时,将邮件发送的工作交由 mq来完成,因此,当前的服务不需要等待邮件投递成功之后,再给用户响应! 只要数据库操作完成,立刻给客户响应

使用: SpringBoot+Email + RabbitMQ 完成案例实现!

5.1 SpringBoot 完成邮件的投递

准备工作: 登录自己的开通邮件的 以163为例 单独演示邮件发送代码实现:

1600574189645.png 生成自己唯一的授权码

1600574220514.png

  • 基于springboot环境导入email发送依赖 案例环境pom
 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
    </parent>
​
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
          <!--springboot邮件发送-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-mail</artifactId>
          </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies><build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  • 配置邮件发送参数 yaml文件
##    邮件发送配置
#  spring.mail.username=fanqixxxx@163.com
#  #spring.mail.password   填写授权码
#  spring.mail.password=xxxxxxx
#  #spring.mail.host   填写邮箱供应的SMTP地址
#  spring.mail.host=smtp.163.com
#  spring.mail.properties.mail.smtp.ssl.enable=true
   spring: 
    mail:
      username: tps520wx@163.com   
      password: xxxxxxx  # 自己的邮件授权码
      host: smtp.163.com
      properties:
        mail:
          smtp:
            ssl:
              enable: true
  • 编写测试类 完成邮件发送
@SpringBootTest
public class EmailSendTest {
​
    @Autowired
    private JavaMailSenderImpl javaMailSender;
    @Test
    public void  send(){
        try {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setSubject("传智健康官方邮件");
            message.setText("你好,请保持好验证码:7788,打死都不能泄露给你的同桌");
            message.setTo("tps520tps@163.com");
            message.setFrom("tps520wx@163.com");
            javaMailSender.send(message);
        } catch (MailException e) {
            e.printStackTrace();
        }
    }
}

查收邮件:

1600574300195.png

5.2 RabbitMQ优化邮件的投递

搭建项目环境: 在业务层 生产者 添加mq的邮件发送代码:

项目环境说明: 基于spring amqp+dubbo+zookeeper 完成邮件发送!

1603789531667.png 学员作业:

  • 基于之前的用户管理系统- 在用户注册的业务模块中,使用mq - 完成邮件发送功能!
  • 学员实战
  • 测试 : 基于apizza模拟用户注册
  1. 启动 生产者和消费者 和 监听器 3个模块
  2. 使用Apizza进行测试

image.png 3. 163邮箱查收邮件:

1600589118157.png

6.小结

  • 掌握 spring amqp 生产者和消费者

  • 理解三种模式区别 direct topic fanout

  • 理解 生产者和消费者 可靠性投递 和相关配置

    • 消费端 - broker

      默认: 自动确认 ack = true

      手动: ack : false 手动删除消息!

      1. yaml文件开启手动确认ack :
      listener:
        simple:
          acknowledge-mode: manual
      
    1. channel.basicAck(envelope.getDeliveryTag(),false);

Spring AMQP 开发 - 重点掌握

  • 开发mq规范: 虚拟机 队列 交换机 都会采用在web控制创建
  • 生产者开发: 注入:RabbitTemplate 调用: convertAndSend("队列名",Object message);
  • 消费端开发: @RabbitListener(queue="队列名") 监听队列 - 使用方法参数列表(Object message )接收信息
  • 生产者和消费者的yaml文件: 固定 : 虚拟机 主机名ip 账号和 密码