简介
文章持续更新,且遇切积。
文章以上产到gitee:https://gitee.com/XuLiZhao/cloud-basic
RabbitMQ相关概念
Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网 络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多 个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
Connection:publisher/consumer 和 broker 之间的 TCP 连接
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCPConnection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线 程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销。
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到
queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
Queue:消息最终被送到这里等待 consumer 取走
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据。
RabbitMQ的工作模式
简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing 路由模式、Topics 主题模式、RP远程调用模式
快速入门
maven依赖
spring环境下使用 <!--rabbitmq java 客户端依赖--> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.9.0</version> </dependency> springboot环境下使用 <!--AMQP依赖,包含RabbitMQ--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
生产者
public void testSendMessage() throws IOException, TimeoutException { // 1.建立连接 ConnectionFactory factory = new ConnectionFactory(); // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码 factory.setHost("192.168.187.131"); //ip:默认值为localhost factory.setPort(5672); //端口,默认值5672 factory.setVirtualHost("/"); //虚拟机,默认值 / factory.setUsername("xihai"); //用户名 默认值为安装时设置的值guest factory.setPassword("123456"); //密码,默认值guest // 1.2.建立连接 Connection connection = factory.newConnection(); // 2.创建通道Channel Channel channel = connection.createChannel(); // 3.创建队列 /** * Description: * 参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments * 1.queue:队列名称,如果没有则会创建,有则不会 * 2.durable:是否持久化,当为true时会写入到数据库中,mq重启后还会存在 * 3.exclusive:是否独占,只有一个消费者监听次队列,当Connection关闭时,是否删除队列 * 4.autoDelete:是否自动删除。当没有Consumer时,自动删除队列 * 5.arguments:参数 **/ String queueName = "simple.queue"; channel.queueDeclare(queueName, false, false, false, null); // 4.发送消息 /** * Description: * 参数:(String exchange, String routingKey, BasicProperties props, byte[] body) * 1.exchange:交换机名称。简单模式下使用默认的 "" * 2.routingKey:路由名称 * 3.props:配置信息 * 4.body:字节数据,真实发送数据 **/ String message = "hello, rabbitmq!"; channel.basicPublish("", queueName, null, message.getBytes()); System.out.println("发送消息成功:【" + message + "】"); // 5.关闭通道和连接 channel.close(); connection.close(); }
消费者
public static void main(String[] args) throws IOException, TimeoutException { // 1.建立连接 ConnectionFactory factory = new ConnectionFactory(); // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码 factory.setHost("192.168.187.131"); factory.setPort(5672); factory.setVirtualHost("/"); factory.setUsername("xihai"); factory.setPassword("123456"); // 1.2.建立连接 Connection connection = factory.newConnection(); // 2.创建通道Channel Channel channel = connection.createChannel(); // 3.创建队列 String queueName = "simple.queue"; channel.queueDeclare(queueName, false, false, false, null); // 4.接收消息 /** * Description: * 参数:(String queue, boolean autoAck, Consumer callback) * 1.queue:队列 * 2.autoAck:是否自动确认 * 3.callback:回调对象 **/ channel.basicConsume(queueName, true, new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 5.处理消息。回调方法,收到消息后会自动调用此方法 /** * Description: * [consumerTag, envelope, properties, body] * 1.consumerTag:标识 * 2.envelope:获取一些信息,路由key * 3.properties:配置信息 * 4.body:数据 **/ System.out.println("consumerTag:"+consumerTag); System.out.println("Exchange:"+envelope.getExchange()); System.out.println("RoutingKey:"+envelope.getRoutingKey()); System.out.println("properties:"+properties); String message = new String(body); //将字节数据转换为字符串 System.out.println("接收到消息:【" + message + "】"); } }); //主线程,与上面回调线程异步 System.out.println("等待接收消息。。。。"); }
整合SpringBoot
环境配置
依赖就是上面那个依赖。
#配置文件 spring: rabbitmq: host: 192.168.187.131 # rabbitMQ的ip地址 port: 5672 # 端口 username: xihai password: 123456 virtual-host: /
简单模式
生产者和消费者实现一对一的通信。
消息发送
@RunWith(SpringRunner.class) @SpringBootTest public class SpringAmqpTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void testSendMessage2SimpleQueue() { String queueName = "simple.queue"; String message = "hello, spring amqp!"; rabbitTemplate.convertAndSend(queueName, message); } }
这里存在一个问题,由于之前我使用上面spring的代码已经创建一个队列了,所以这里的消息发送才可以运行,但是当这个simple.queue的队列不存在时,这个发送代码是不会帮你创建队列的。
消息接收
#创建一个类将它定义到spring容器中,使用@RabbitListener监听需要监听的队列 @Component public class SpringRabbitListener { @RabbitListener(queues = "simple.queue") public void listenSimpleQueue(String msg) { System.out.println("消费者接收到simple.queue的消息:【" + msg + "】"); } }
工作队列 Work Queue
一个生产者对应多个消费者,但是消息只能使用一次,阅后即焚。
生产者
//没20毫秒发送一条消息,发送50次 @Test public void testSendMessage2WorkQueue() throws InterruptedException { String queueName = "simple.queue"; String message = "hello, message__"; for (int i = 1; i <= 50; i++) { rabbitTemplate.convertAndSend(queueName, message + i); Thread.sleep(20); } }
消费者
@RabbitListener(queues = "simple.queue") public void listenWorkQueue1(String msg) throws InterruptedException { System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now()); Thread.sleep(20); } @RabbitListener(queues = "simple.queue") public void listenWorkQueue2(String msg) throws InterruptedException { System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now()); Thread.sleep(200); }
消息预取机制:当接收队列中的消息时,消费者默认是按顺序每次每人取一条消息的,所以展现出来的特征就是:多个消费者平均分配消息,消息次序固定。
添加如下配置(与port处于同一列)
listener: simple: prefetch: 1 #每次只能获取一条消息,获取完成后才能获取下一条
Publish/Subscribe 发布与订阅模式
生产者对应多个消费者,通过交换机,同一消息可以被不同消费者接收。
生产者@Test public void testSendFanoutExchange() { // 交换机名称 String exchangeName = "xihai.fanout"; // 消息 String message = "hello, every one!"; // 发送消息 rabbitTemplate.convertAndSend(exchangeName, "", message); }
消费者配置类
消费者的配置与之前有些不同,因为在发布和订阅模式下使用到了交换机机制,需要到配置类中进行配置
@Configuration public class FanoutConfig { // 声明交换机 @Bean public FanoutExchange fanoutExchange(){ return new FanoutExchange("itcast.fanout"); } // 声明队列 @Bean public Queue fanoutQueue1(){ return new Queue("fanout.queue1"); } // 将队列和交换机进行绑定 @Bean public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){ return BindingBuilder .bind(fanoutQueue1) .to(fanoutExchange); } } //建议配置多个队列绑定同一交换机以便观察效果
消费者主体
@RabbitListener(queues = "fanout.queue1") public void listenFanoutQueue1(String msg) { System.out.println("消费者接收到fanout.queue1的消息:【" + msg + "】"); } @RabbitListener(queues = "fanout.queue2") public void listenFanoutQueue2(String msg) { System.out.println("消费者接收到fanout.queue2的消息:【" + msg + "】"); }
创建队列的多种方式
//通过对象创建 @Bean public org.springframework.amqp.core.Queue SimpleQueue() { return QueueBuilder.nonDurable("test_queue").build(); //return new Queue("test_queue"); } //通过接收消息时创建 @RabbitListener(queuesToDeclare = @Queue("simple.queue")) public void listenSimpleQueue(String msg) { ... } //3.queue和exchange绑定使自动创建 @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue1"), exchange = @Exchange(name = "xihai.direct", type = ExchangeTypes.DIRECT), key = {"red", "blue"} )) public void listenDirectQueue1(String msg){ System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】"); }
路由模式
当生产者发送消息时设置一个routingKey值,消费者会在创建完成后与路由建立练习并设置上他们之间通信的key值(可以设置多个),生产者发送消息到达路由后,路由根据它与队列绑定时指定的key进行查询,发现key值匹配即向该队列发送信息。
生产者//第二个参数为routingKey @Test public void testSendDirectExchange() { // 交换机名称 String exchangeName = "xihai.direct"; // 消息 String message = "hello, xihai!"; // 发送消息 rabbitTemplate.convertAndSend(exchangeName, "xihai", message); }
消费者
//bindings属性中绑定队列,交换机,key。ExchangeTypes.DIRECT为默认值,表示交换机类型为direct。这里的key你可以把它理解为BoundingKey。 @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue1"), exchange = @Exchange(name = "xihai.direct", type = ExchangeTypes.DIRECT), key = {"xihai", "lizhao"} )) public void listenDirectQueue1(String msg){ System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】"); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue2"), exchange = @Exchange(name = "xihai.direct", type = ExchangeTypes.DIRECT), key = {"xihai", "shengge"} )) public void listenDirectQueue2(String msg){ System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】"); }
Topics 主题模式
主题模式与路由模式类似,不同的是routingKey必须是多个单词组成的列表,并且以
.
分隔。而当队列与交换机绑定时可以使用通配符。#
:表示0个或多个单词。*
:指代一个单词
生产者@Test public void testSendTopicExchange() { // 交换机名称 String exchangeName = "xihai.topic"; // 消息 String message = "今天天气不错,我的心情好极了!"; // 发送消息 rabbitTemplate.convertAndSend(exchangeName, "china.weather", message); }
消费者
@RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue1"), exchange = @Exchange(name = "xihai.topic", type = ExchangeTypes.TOPIC), key = "china.#" )) public void listenTopicQueue1(String msg){ System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】"); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue2"), exchange = @Exchange(name = "xihai.topic", type = ExchangeTypes.TOPIC), key = "#.news" )) public void listenTopicQueue2(String msg){ System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】"); }
拓展
消息转换器
查看RabbitMQ消息传递时默认的消息格式
生产者
@Test public void testSendObjectQueue(){ Map<String, Object> msg = new HashMap<>(); msg.put("name","汐海"); msg.put("age",21); rabbitTemplate.convertAndSend("object.queue",msg); } }
消费者
@RabbitListener(queues = "object.queue") public void listenObjectQueue(Map<String,Object> msg){ System.out.println("接收到object.queue的消息:" + msg); }
使用Json的序列化方式代替上述java的序列化方式。
//引入json依赖 <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
生产者配置类中声明Json消息转换器
@Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); }
接收时也要进行如上配置,引依赖并配置。
准备更新RabbitMQ高级特性,前面一直使用的是yml文件的配置,在下一篇中我好多中方式都会涉及。具体的也列举不出来,到时候会给源码,里面都有详细注释,但也需要一定基础才能看懂。最后开个小头,就准备去开下一篇博客了。
配置类
@Configuration
public class RabbitConfig {
@Bean
public CachingConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("192.168.187.131");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xihai");
connectionFactory.setPassword("123456");
connectionFactory.setVirtualHost("/");
return connectionFactory;
}
public RabbitAdmin rabbitAdmin(){
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory());
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
}
默认spring容器自动加载了这些类对象。我们在spring配置类中添加相关配置参数后,就会创建
CachingConnectionFactory
的对象,只是其中的一些参数不是默认了。
自动装配位置如下,各位感兴趣也可以阅读它的源码
进入springboot的自动装配类的这个位置,我们可以看到这里有很多AMQP的配置:
RabbitProperties:属性配置类,映射配置文件 prefix = “spring.rabbitmq”,开头的属性
RabbitAutoConfiguration :自动装配类,里面配置了CachingConnectionFactory和RabbitTemplate两个类和AmqpAdmin接口,而RabbitAdmin是AmqpAdmin接口的实现类