springboot 是目前以Java 语言开发的主流框架了,rabbitmq 是常用的消息队列中的一种,因为其功能比较完备且社区活跃,可以解决开发过程中遇到的bug,结合erlang 语言的并发优势,性能较好,所以适合中小公司的企业级项目使用。下面我们结合其他几个常用的消息队列一起了解一下这个中间件:
使用MQ 的优劣
优势:
应用解耦:消息队列较少服务之间的耦合性,不同的服务可以通过消息队列进行通信,而不用关心彼此的实现细节,提高了系统的容错性和可维护性。
异步处理:消息队列本身是异步的,只要保存自己的数据并且将请求发送到MQ 就可以快速的给予反馈信息,极大的提升了系统的吞吐量。
流量削峰:当上下游系统处理能力存在差距的时候,利用消息队列做一个通用的“载体”,在下游有能力处理的时候,再进行分发与处理。
日志处理:日志处理是指将消息队列用在日志处理中,比如kafka 的应用,解决大量日志传输的问题。
消息通讯:消息队列一般的哦内置了高效的通信机制,因此也可以用在纯消息通讯的地方,比如实现点对点消息队列,或者聊天室等。
劣势:
系统可用性降低:MQ一旦故障了,系统A就没法发送消息给MQ啦,然后系统BCD也没法消费到消息。整个系统就崩溃了,没有办法正常运转啦。
系统复杂性变高:硬生生加了个MQ,你怎么保证消费没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性。问题一大堆,痛苦不已。
系统一致性问题:系统ABC执行成功了,但是系统D执行失败了。但是给用户返回执行成功了,结果后台逻辑实际上是差了一点。完全没有执行完。
以上就是使用消息队列带来的优劣了,不过劣势可以通过技术减低,例如可用性问题可以通过搭建MQ 的高可用环境,进而提升MQ 的可用性等。
RabbitMQ 介绍
RabbitMQ 是一个由Erlang 语言开发的 AMQP(Advanved Message Queue Protocol,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。)的开源实现,也就是消息服务中间件,本质上是个队列,遵循FIFO 先进先出原则。多用于分布式系统之间进行通信,用来提升系统的异步通信、扩展解耦能力。再来看下rabbitmq 的架构图:
组成的部分主要由:producer(生产者)、 broker(消息接收和发送服务器)、 consumer(消费者)组成,一起来看下其他关键节点代表的含义:
-
Producer:消息生产者。
-
Consumer:消息消费者。
-
Broker:接收和分发消息的应用,RabbitMQ Server,服务器实体
-
Connection(连接):Producer 和 Consumer 通过TCP 连接到 RabbitMQ Server。
-
Channel(信道):如果每一次访问RabbitMQ 就建立一个Connection,在数据量大的时候开销就很大,效率较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread 创建单独的channel 进行通信。AMQP method 包含了channel id 帮助客户端和message broker 识别channel,所以channel 之间是完全隔离的。Channel 作为轻量级的Connnection极大减少了操作系统建立TCP connection 的开销。
-
Exchange(交换器):生产者将消息发送到 Exchange(交换器),由 Exchange 将消息路由到一个或多个 Queue 中(或者丢弃);Exchange 并不存储消息;Exchange Types 常用有 Fanout、Direct、Topic 三种类型,每种类型对应不同的路由规则。
-
Queue(队列):是 RabbitMQ 的内部对象,用于存储消息;消息消费者就是通过订阅队列来获取消息的,RabbitMQ 中的消息都只能存储在 Queue 中,生产者生产消息并最终投递到 Queue 中,消费者可以从 Queue 中获取消息并消费;多个消费者可以订阅同一个 Queue,这时 Queue 中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。
-
Binding(绑定):是 Exchange(交换器)将消息路由给 Queue 所需遵循的规则。
-
Routing Key(路由键):消息发送给 Exchange(交换器)时,消息将拥有一个路由键(默认为空), Exchange(交换器)根据这个路由键将消息发送到匹配的队列中。
-
Binding Key(绑定键):指定当前 Exchange(交换器)下,什么样的 Routing Key(路由键)会被下派到当前绑定的 Queue 中。
-
Virtual host:出于多租户和安全因素设计的,把AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的namespace 概念。当多个不同的用户使用同一个RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的vhost 创建exchange / queue 等。
另外,再说下 Exchange Types(交换器类型)的三种常用类型:
-
Direct:完全匹配,消息路由到那些 Routing Key 与 Binding Key 完全匹配的 Queue 中。比如 Routing Key 为cleint-key,只会转发cleint-key,不会转发cleint-key.1,也不会转发cleint-key.1.2。
-
Topic:模式匹配,Exchange 会把消息发送到一个或者多个满足通配符规则的 routing-key 的 Queue。其中*表号匹配一个 word,#匹配多个 word 和路径,路径之间通过.隔开。如满足a.*.c的 routing-key 有a.hello.c;满足#.hello的 routing-key 有a.b.c.helo。
-
Fanout:忽略匹配,把所有发送到该 Exchange 的消息路由到所有与它绑定 的Queue 中。
RabbitMQ 工作模式
简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing 路由模式、Topic 主题模式、RPC远程调用模式(不太算MQ)。
这边整合大概写下简单,发布订阅和在主题模式,配置安装这里暂时不讲,先上代码:
创建springboot 的项目,选址需要的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
在application.yml 文件中进行相应配置
spring:
rabbitmq:
addresses: 127.0.0.1 # mq 地址
username: guest # 默认为guest
password: guest # 默认为guest
virtual-host: /mohen # 默认为/
简单模式
生产端配置类:用于队列 ,交换机的定义及绑定关系
@Configuration
public class RabbitConfig {
public static final String SIMPLE_QUEUE_NAME = "simple_queue";
@Bean
public Queue simpleQueue() {
/**
durable(): 是否持久化,当mq 重启之后还在
exclusive(): 是否独占:只能有一个消费者监听这个队列,当Connection 关闭时,是否删除队列
autoDelete(): 是否自动删除,当没有Consumer 监听时,自动删除
withArgument(): 参数
*/
return QueueBuilder.durable(SIMPLE_QUEUE_NAME).build();
}
}
生产端测试类:
@SpringBootTest
@RunWith(SpringRunner.class)
class SbmqProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void simpleQueue() {
// 简单模式下,路由key 默认为队列名称,交换机为默认
rabbitTemplate.convertAndSend(RabbitConfig.SIMPLE_QUEUE_NAME, "hello world");
/**源码
public void convertAndSend(String routingKey, Object object) throws AmqpException {
this.convertAndSend(this.exchange, routingKey, object, (CorrelationData)null);
}
*/
}
}
消费端监听类:
@Component
public class SimpleListener {
@RabbitListener(queues = "simple_queue")
public void simpleListener(Message message) {
System.out.println(message);
System.out.println(new String(message.getBody()));
}
}
发布订阅
依赖和配置不用变,主要是队列这块新增了交换机组件,我们来看下代码:
@Configuration
public class RabbitConfig {
public static final String FANOUT_EXCHANGE_NAME = "fanout_exchange";
public static final String PS_QUEUE_NAME = "ps_queue";
public static final String PS_QUEUE_NAME02 = "ps_queue02";
@Bean("fanout_exchange")
public Exchange fanoutExchange() {
/**
* 交换机类型:
* DIRECT: 定向
* FANOUT: 扇形(广播):发送消费给每一个与之绑定的队列
* TOPIC: 通配符
* HEADERS: 参数
*
* durable: 是否持久化
* autoDelete: 自动删除
* internal: 内部使用
* arguments: 参数
*/
return ExchangeBuilder.fanoutExchange(FANOUT_EXCHANGE_NAME).durable(true).build();
}
@Bean("ps_queue")
public Queue psQueue01() {
return QueueBuilder.durable(PS_QUEUE_NAME).build();
}
@Bean("ps_queue02")
public Queue psQueue02() {
return QueueBuilder.durable(PS_QUEUE_NAME02).build();
}
@Bean
public Binding bindQueueExchange01(@Qualifier("ps_queue") Queue queue, @Qualifier("fanout_exchange") Exchange exchange) {
// 如果交换机类型为fanout, routingKey(路由键,绑定规则) 设置为""
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
@Bean
public Binding bindQueueExchange02(@Qualifier("ps_queue02") Queue queue, @Qualifier("fanout_exchange") Exchange exchange) {
// 如果交换机类型为fanout, routingKey(路由键,绑定规则) 设置为""
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
}
生产端测试类:
@SpringBootTest
@RunWith(SpringRunner.class)
class SbmqProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void psQueue() {
rabbitTemplate.convertAndSend(RabbitConfig.FANOUT_EXCHANGE_NAME, "", "ps");
}
}
消费端监听类:
@Component
public class PsListener {
@RabbitListener(queues = "ps_queue")
public void psListener(Message message) {
System.out.println("队列1: "+ new String(message.getBody()));
}
@RabbitListener(queues = "ps_queue02")
public void ps02Listener(Message message) {
System.out.println("队列2: "+ new String(message.getBody()));
}
}
主题模式
直接上代码:
@Configuration
public class RabbitConfig {
public static final String TOPIC_EXCHANGE_NAME = "topic_exchange";
public static final String TOPIC_QUEUE_NAME = "topic_queue";
public static final String TOPIC_QUEUE_NAME02 = "topic_queue02";
@Bean("topic_exchange")
public Exchange topicExchange() {
return ExchangeBuilder.topicExchange(TOPIC_EXCHANGE_NAME).durable(true).build();
}
@Bean("topic_queue")
public Queue topicQueue() {
return QueueBuilder.durable(TOPIC_QUEUE_NAME).build();
}
@Bean("topic_queue02")
public Queue topicQueue02() {
return QueueBuilder.durable(TOPIC_QUEUE_NAME02).build();
}
@Bean
public Binding bindQueueExchange05(@Qualifier("topic_queue") Queue queue, @Qualifier("topic_exchange") Exchange exchange) {
//*: 代表单个单词; #: 代表单个或多个单词
return BindingBuilder.bind(queue).to(exchange).with("order.*").noargs();
}
@Bean
public Binding bindQueueExchange06(@Qualifier("topic_queue02") Queue queue, @Qualifier("topic_exchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("#.error").noargs();
}
}
生产端测试类:
@SpringBootTest
@RunWith(SpringRunner.class)
class SbmqProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void topicQueue() {
rabbitTemplate.convertAndSend(RabbitConfig.TOPIC_EXCHANGE_NAME, "log.info", "topic_info");// 队列1
rabbitTemplate.convertAndSend(RabbitConfig.TOPIC_EXCHANGE_NAME, "log.error", "topic_error");// 队列2
rabbitTemplate.convertAndSend(RabbitConfig.TOPIC_EXCHANGE_NAME, "order.error", "topic_order_error");// 队列2
}
}
消费端监听类:
@Component
public class TopicListener {
@RabbitListener(queues = "topic_queue")
public void topicListener(Message message) {
System.out.println("队列1: "+ new String(message.getBody()));
}
@RabbitListener(queues = "topic_queue02")
public void topic02Listener(Message message) {
System.out.println("队列2: "+ new String(message.getBody()));
}
}
以上就是springboot 整合rabbitmq 的入门介绍,下一篇再深入介绍下rabbitmq 的高级用法。
RabbitMQ 高级特性http://t.csdn.cn/69fqy
如有问题,欢迎道友指正,感谢!