springboot集成rabbitmq

在开始整合之前,让我们先了解下rabbitmq,先来灵魂三问,rabbitmq是什么,它解决了什么问题,如何使用它。

带着这三大问题开始我今天的主题,rabbitmq是一款消息中间件,它可以为你的应用提供通用的消息发送与接收平台,并保证消息的安全性,在谈如何使用之前,我们先抛开rabbitmq来讨论下消息队列,使用消息队列的三大核心目的:业务解耦、流量削峰、消息异步。

业务解耦: 假如公司有a、b、c、d四个系统,a系统做完处理之后要通知bcd三个系统做响应处理,没有引入消息队列的时候我们可能会使用rpc调用,这时候新的业务线e上线了,它也需要a的通知,b系统业务下线了,它不要接受a的通知,a系统的同学疯了。這时候引入消息队列,a将消息发送到交换机,其它系统自己选择是否从交换机拉取消息,这样e系统的上线和b系统的下线与a无关,完成解耦。

流量削峰: 主要目的是保护数据库不被高并发请求打死,假设系统平时流量不高,数据库每秒能处理2000个请求,但每到中午流量激增到每秒5000个请求,這时候引入消息队列,让数据库慢慢处理请求,这样数据库不会被打死,有人会担心一旦引入了消息队列是否会影响平时的性能呢?毕竟消息队列也是有时间消耗的,消息队列在没有积压的情况下,延迟都在ms级别,rabbitmq更是可达微秒级,可以忽略不计的。

消息异步:和解耦场景类似,毕竟在a系统同步通知bcd系统需要等待bcd系统处理完业务之后才会返回,a系统反馈给用户的处理等待时间是a+b+c+d,假设abcd系统处理耗时相当,用户等待时间*4,这对用户是一种伤害,将关联性不强的操作异步处理可以给用户更好的体验,值得考虑。

使用消息队列解决特定问题的同时也引入了问题,它的缺点如下三点

1、系统复杂度增加:引入了mq之后,我们要考虑消息如何不被重复消费、消息丢失怎么办、保证消息传递的顺序性等

2、系统可用性降低:引入的依赖越多,系统可用性就越低,如何保证mq不挂掉是个问题

3、数据一致性:业务解耦之后,其它系统异常,数据未被正常写入怎么办?

带着如上问题进入今天的主题,springboot如何整合rabbitmq

第一步 引入maven依赖 主要引入spring-boot-starter-amqp  可以查看springboot 官方文档

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.eqxiu</groupId>
    <artifactId>eqxiu-hdh5-rmq</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eqxiu-hdh5-rmq</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

第二步添加配置文件

spring.rabbitmq.listener.concurrency=2  //最小消息监听线程数
spring.rabbitmq.listener.max-concurrency=2 //最大消息监听线程数
spring.rabbitmq.listener.prefetch=2  //每次从队列拉取消息数
spring.rabbitmq.listener.simple.retry.max-attempts=5  //消息消费失败重试次数
spring.rabbitmq.listener.simple.retry.enabled=true // 开启失败重试
spring.rabbitmq.listener.simple.retry.initial-interval=2000 // 重试的时间间隔 第一次2000ms 第二次是 multiplier*2000 ,第三次是 multiplier*2000*multiplier
spring.rabbitmq.listener.simple.retry.max-interval=10000 // 重试最大时间间隔
spring.rabbitmq.listener.simple.retry.multiplier=5 // 应用于上一重试间隔的乘数
#重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列)
spring.rabbitmq.listener.simple.default-requeue-rejected=true

第三步编写消息发送者&接受者代码, 先交代下rabbitmq的几个基本名称, 队列(queue)、交换机(exchange)、路由(routing-key), rabbitmq有四种交换机,归类入下

交换类型行为
直连(direct)绑定密钥必须与路由密钥完全匹配-不支持通配符。
主题(topic)与直接相同,但绑定密钥中允许使用通配符。“#”匹配零个或多个点分隔的单词,“ *”恰好匹配一个这样的单词。
扇型(fanout)路由和绑定键将被忽略-所有发布的消息都将进入所有绑定的队列。
头(header) 

交换机通过路由和队列绑定,消息发送者将消息发到交换机,由交换机推送到对应的队列,消息接收者通过监听队列来拉取消息,建议看下官网网站翻译的amqp协议部分http://rabbitmq.mr-ping.com/AMQP/AMQP_0-9-1_Model_Explained.html,接下来逐一演示用法

直连交换机

@Component
public class RabbitMqConfig {

    // 创建一队列 名字叫 direct.queue
    @Bean
    Queue queue() {
        return new Queue("direct.queue", false);
    }
    // 创建一交换机 名字叫 directExchange
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange("directExchange");
    }
    // 将交换机和队列绑定,路由key=direct.key
    @Bean
    public Binding directBingding() {
        return BindingBuilder.bind(queue()).to(directExchange()).with("direct.key");
    }
}

// 消息发送类
@Controller
public class homeController {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping(value = {"/direct"})
    @ResponseBody
    public void directMsg() {
        rabbitTemplate.convertAndSend("directExchange", "direct.key", msg);
    }
}

//消息接收类
@Component
public class Consumner {
    @RabbitListener(queues = "direct.queue")
    public void receiveDirect(byte[] content) {
        System.out.println("direct:" + new String(content));
    }
}

topic交换机

注意代码中定义了两个队列,这两个队列与同一交换机绑定但使用了不同的routing-key, queue2采用的通配的方式

所以在发送消息topocMsg() 的方法中, queue1只能接受一条消息,而queue2可以接受到两个消息,执行结果如下:

topic 1hello topic queue!1
topic.#hello topic queue!1
topic.#hello topic queue!2
@Component
public class RabbitMqConfig {

    public static final String TOPIC_QUEUE1 = "topic.queue1";
    public static final String TOPIC_QUEUE2 = "topic.queue2";

    @Bean
    Queue queue1() {
        return new Queue(TOPIC_QUEUE1, true);
    }

    @Bean
    Queue queue2() {
        return new Queue(TOPIC_QUEUE2, true);
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(topic_Exchange);
    }

    @Bean
    public Binding topicBinding1() {
        return BindingBuilder.bind(queue1()).to(topicExchange()).with("topic.key1");
    }

    @Bean
    public Binding topicBinding2() {
        return BindingBuilder.bind(queue2()).to(topicExchange()).with("topic.#");
    }
}

// 消息发送类
@Controller
public class homeController {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping(value = {"/topic"})
    @ResponseBody
    public void topocMsg() {
        rabbitTemplate.convertAndSend(RabbitMqConfig.topic_Exchange, "topic.key1", msg + "1");
        rabbitTemplate.convertAndSend(RabbitMqConfig.topic_Exchange, "topic.key2", msg + "2");
    }
}

//消息接收类
@Component
public class Consumner {
    @RabbitListener(queues = RabbitMqConfig.TOPIC_QUEUE1)
    public void receiveTopic1(String content) {
        System.out.println("topic 1" + content);
    }

    @RabbitListener(queues = RabbitMqConfig.TOPIC_QUEUE2)
    public void receiveTopic2(String content) {
        System.out.println("topic.#" + content);
    }

}

fanout交换机

fanout又称为扇形交换机,扇形像不像大喇叭的形状?我觉得可以理解为广播模式,这里偷个懒,队列和topic公用了,fanout交换机的特点是不需要指定routing-key,所有与交换机绑定的队列都可以监听到消息

@Component
public class RabbitMqConfig {

    public static final String TOPIC_QUEUE1 = "topic.queue1";
    public static final String TOPIC_QUEUE2 = "topic.queue2";

    @Bean
    Queue queue1() {
        return new Queue(TOPIC_QUEUE1, true);
    }

    @Bean
    Queue queue2() {
        return new Queue(TOPIC_QUEUE2, true);
    }

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(FANOUT_EXCHANGE);
    }
    @Bean
    public Binding FanoutBinding1() {
        return BindingBuilder.bind(queue1()).to(fanoutExchange());
    }
    @Bean
    public Binding FanoutBinding2() {
        return BindingBuilder.bind(queue2()).to(fanoutExchange());
    }
}

// 消息发送类
@Controller
public class homeController {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping(value = {"/fanout"})
    @ResponseBody
    public void fanoutMsg() {
        rabbitTemplate.convertAndSend(RabbitMqConfig.FANOUT_EXCHANGE, "",msg + "fanout");
    }
}

//消息接收类
@Component
public class Consumner {
    @RabbitListener(queues = RabbitMqConfig.TOPIC_QUEUE1)
    public void receiveTopic1(String content) {
        System.out.println("topic 1" + content);
    }

    @RabbitListener(queues = RabbitMqConfig.TOPIC_QUEUE2)
    public void receiveTopic2(String content) {
        System.out.println("topic.#" + content);
    }

}

header交换机

顾名思义,就是带有key-value形式的头信息的交换机

@Component
public class RabbitMqConfig {

    public static final String HEADER_QUEUE = "header.queue";
    public static final String HEADERS_EXCHANGE = "headersExchange";

    @Bean
    public HeadersExchange headersExchange() {
        return new HeadersExchange(HEADERS_EXCHANGE);
    }

    @Bean
    public Queue headersQueue() {
        return new Queue(HEADER_QUEUE);
    }

    @Bean
    public Binding headersBinding() {
        Map<String, Object> headerKeys = new HashMap<>();
        headerKeys.put("superbing", "bing");
        return BindingBuilder.bind(headersQueue()).to(headersExchange()).whereAll(headerKeys).match();
    }
}

// 消息发送类
@Controller
public class homeController {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping(value = {"/header"})
    @ResponseBody
    public void headerMsg() {
        MessageProperties properties = new MessageProperties();
        properties.setHeader("superbing", "bing");
        Message obj = new Message(msg.toString().getBytes(), properties);
        rabbitTemplate.convertAndSend(RabbitMqConfig.HEADERS_EXCHANGE, "", obj);
    }
}

//消息接收类
@Component
public class Consumner {
    @RabbitListener(queues = RabbitMqConfig.HEADER_QUEUE)
    public void receiveHeader(byte[] content) {
        System.out.println("header:" + new String(content));
    }
}

至此,springboot整合rabbitmq就完成了,至于前面遗留的引入mq之后系统可用性降低,复杂度增加,数据一致性问题,我也没想好,你说气人不!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值