RabbitMQ-消息队列
概念解析
消息队列具有三个重要特点:解耦、销峰控流、广播,根据这三个特性,用现实生活例子,解释其作用
解耦:比方说我要租房,哪我就需要知道都有哪些房源,但房源分布在各个业主手里,常规情况下,我需要一个个去询问,这样就造成了我的工作量特别多,我只是想租个房子,但有许多额外的事要做,这就违背了功能单一的设计模式。现实生活中,有房屋中介代理,这就解决了我和业主的直接联系,我只需要找中介即可,业主只需要在中间挂牌自己的房子,避免了从消费者(我)和生产者(业主)复杂的联系。
以业务-程序来举例,12306上购票,选好票后,等待支付,并且要一定频率的给客户发送支付通知,如果把选票和支付,做在一起,那么这块就特别的重,不方便横行扩展
销峰控流:以传统nginx代理多服务实例为例,如果在代理模块业务比较多,就会存在两个风险问题:1、入口压力大,入口就会成为瓶颈,都挤在门这 2、一般来说,入口就那么大,不方便水平扩展。使用消息队列,以异步方式,把压力分摊到下一层,下一层可以使用多实例的方式,水平扩展,增加处理能力。
广播:以传统的告知所有人消息为例,最初要挨家挨户的去说,送信的人工作就很重,后来有了喇叭,有事就通过喇叭喊,送信的人一下子就轻松下来了,他的活就很单一了(模块功能单一),当然前提是每户能听到喇叭响(消费者订阅了某些队列)。实时上,消息队列要比大喇叭喊要全面的多,不仅可以广播消息,还能够针对性对用户订阅了哪些消息进行发送,并且能够收到用户的已收确认消息。
组成
最简单消息队列模型为:生产者、Broker(可以理解成一台服务器上起一个进程),消费者
但是有些消息要给A消费,有些消息要被B消费,所以就有了队列QUEUE(kafka中使用topic):
哪要把哪些消息分给队列1、哪些消息分给队列2呢,这时就需要对消息进行路由,就产生了消息交换机Exchanger
但这时还不行,有交换机可以进行消息分发,还是不知道按照什么规则进行分发呢,这时就有了路由规则routingKey和建立交换机和队列的绑定binding
现在业务是通了,但是是个单例模式,需要还需要转变成高可用、分布式模式
特例应用(定时消费)
真实消费队列中间件是没有对消息定时消费的功能的,但有消息定时失效的功能,可以把失效的消息转发到另外一个队列,消费者监控这个队列,即可实现消息定时消费的功能。
原生demo
1、交换机给队列广播模式:fanout
生产者:
package mq3;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class FanoutProducer {
public static void main(String[] args) {
// 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
// 设置 RabbitMQ 地址
factory.setHost("192.168.154.117");
factory.setVirtualHost("/");
Connection conn = null;
Channel channel = null;
String exchangerName = "fanout_exchanger_test1";
String queueName1 = "fanout_queue_test_1";
String queueName2 = "fanout_queue_test_2";
try {
conn = factory.newConnection();
channel = conn.createChannel();
// 设置交换机
channel.exchangeDeclare(exchangerName, "fanout");
// 设置队列
channel.queueDeclare(queueName1, true, false, false, null);
channel.queueDeclare(queueName2, true, false, false, null);
// 绑定交换机和队列
channel.queueBind(queueName1, exchangerName, "");
channel.queueBind(queueName2, exchangerName, "");
// 生产数据
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()) {
String line = scanner.nextLine();
channel.basicPublish(exchangerName, "", null, line.getBytes());
}
scanner.close();
}catch(Exception e ) {
e.printStackTrace();
}finally{
try {
channel.close();
conn.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
消费者:
package mq3;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
public class FanoutConsumer1 {
private static Channel channel;
private static Connection conn;
public static void main(String[] args) {
// 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
// 设置 RabbitMQ 地址
factory.setHost("192.168.154.117");
factory.setVirtualHost("/");
conn = null;
channel = null;
String queueName1 = "fanout_queue_test_1";
try {
conn = factory.newConnection();
channel = conn.createChannel();
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
System.out.println(new String(body));
}
};
channel.basicConsume(queueName1, true, consumer);
}catch(Exception e) {
e.printStackTrace();
}
}
}
2、交换机给队列按照字段完全匹配分发模式:direct,通过字段"direct1"和"direct2"
生产者:
package mq3;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class DirectProducer {
public static void main(String[] args) {
// 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
// 设置 RabbitMQ 地址
factory.setHost("192.168.154.117");
factory.setVirtualHost("/");
Connection conn = null;
Channel channel = null;
String exchangerName = "direct_exchanger_test1";
String queueName1 = "fanout_queue_test_1";
String queueName2 = "fanout_queue_test_2";
try {
conn = factory.newConnection();
channel = conn.createChannel();
// 设置交换机
channel.exchangeDeclare(exchangerName, "direct");
// 设置队列
channel.queueDeclare(queueName1, true, false, false, null);
channel.queueDeclare(queueName2, true, false, false, null);
// 绑定交换机和队列
channel.queueBind(queueName1, exchangerName, "direct1");
channel.queueBind(queueName2, exchangerName, "direct2");
// 生产数据
Scanner scanner = new Scanner(System.in);
int num = 0;
while(scanner.hasNextLine()) {
String line = scanner.nextLine();
num++;
if (num % 2 == 0)
channel.basicPublish(exchangerName, "direct1", null, line.getBytes());
else
channel.basicPublish(exchangerName, "direct2", null, line.getBytes());
}
scanner.close();
}catch(Exception e ) {
e.printStackTrace();
}finally{
try {
channel.close();
conn.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
3、交换机给队列按照字段部分匹配分发模式:topic(通配符有两点注意:1、以"."进行分割 2、"*"代表1个或多个标识符,"#"代表0个或多个标识符)
下面这个例子表示:对于队列fanout_queue_test_1发送全部消息,对于队列fanout_queue_test_2只发送customer.normal消息
package mq3;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class TopicProducer {
public static void main(String[] args) {
// 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
// 设置 RabbitMQ 地址
factory.setHost("192.168.154.117");
factory.setVirtualHost("/");
Connection conn = null;
Channel channel = null;
String exchangerName = "topic_exchanger_test1";
String queueName1 = "fanout_queue_test_1";
String queueName2 = "fanout_queue_test_2";
try {
conn = factory.newConnection();
channel = conn.createChannel();
// 设置交换机
channel.exchangeDeclare(exchangerName, "topic");
// // 设置队列
// channel.queueDeclare(queueName1, true, false, false, null);
// channel.queueDeclare(queueName2, true, false, false, null);
// 绑定交换机和队列
channel.queueBind(queueName1, exchangerName, "customer.*");
channel.queueBind(queueName2, exchangerName, "customer.normal");
// 生产数据
Scanner scanner = new Scanner(System.in);
int num = 0;
while(scanner.hasNextLine()) {
String line = scanner.nextLine();
num++;
if (num % 2 == 0)
channel.basicPublish(exchangerName, "customer.normal", null, line.getBytes());
else
channel.basicPublish(exchangerName, "customer.all", null, line.getBytes());
}
scanner.close();
}catch(Exception e ) {
e.printStackTrace();
}finally{
try {
channel.close();
conn.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
整合springboot使用
1、pom引用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、配置application.yml设置rabbitmq访问地址
spring:
rabbitmq:
host: 192.168.154.117
port: 5672
username: guest
password: guest
virtual-host: springboot
3、编写交换机、队列、绑定关系
设置广播fanout
@Configuration
public class FanoutRabbitConfig {
//队列 起名:TestFanoutQueue
@Bean
public Queue TestFanoutQueue() {
return new Queue("TestFanoutQueue",true);
}
//Fanout交换机 起名:TestFanoutExchange
@Bean
FanoutExchange TestFanoutExchange() {
// return new FanoutExchange("TestFanoutExchange",true,true);
return new FanoutExchange("TestFanoutExchange",true,false);
}
//绑定 将队列和交换机绑定, 并设置用于匹配键:TestFanoutRouting
@Bean
Binding bindingFanout() {
return BindingBuilder.bind(TestFanoutQueue()).to(TestFanoutExchange());
}
}
设置direct
@Configuration
public class DirectRabbitConfig {
//队列 起名:TestDirectQueue
@Bean
public Queue TestDirectQueue() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// return new Queue("TestDirectQueue",true,true,false);
//一般设置一下队列的持久化就好,其余两个就是默认false
System.out.println("queue init....");
return new Queue("TestDirectQueue",true);
}
//Direct交换机 起名:TestDirectExchange
@Bean
DirectExchange TestDirectExchange() {
// return new DirectExchange("TestDirectExchange",true,true);
return new DirectExchange("TestDirectExchange",true,false);
}
//绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
}
@Bean
DirectExchange lonelyDirectExchange() {
return new DirectExchange("lonelyDirectExchange");
}
}
设置topic
@Configuration
public class TopicRabbitConfig {
// 队列 起名:TestTopicQueue
@Bean
public Queue TestTopicQueue() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// return new Queue("TestTopicQueue",true,true,false);
// 一般设置一下队列的持久化就好,其余两个就是默认false
System.out.println("queue init....");
return new Queue("TestTopicQueue", true);
}
// Topic交换机 起名:TestTopicExchange
@Bean
TopicExchange TestTopicExchange() {
return new TopicExchange("TestTopicExchange", true, false);
}
// 绑定 将队列和交换机绑定, 并设置用于匹配键:TestTopicRouting
@Bean
Binding bindingTopic() {
return BindingBuilder.bind(TestTopicQueue()).to(TestTopicExchange()).with("message.*");
}
}
4、编写生产者
@RestController
public class SendMessageController {
@Autowired
RabbitTemplate rabbitTemplate; //使用RabbitTemplate,这提供了接收/发送等等方法
@GetMapping("/sendDirectMessage")
public String sendDirectMessage() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "test message, hello!";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String,Object> map=new HashMap<>();
map.put("messageId",messageId);
map.put("messageData",messageData);
map.put("createTime",createTime);
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);
return "ok";
}
@GetMapping("/sendFanoutMessage")
public String sendFanoutMessage() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "test message, hello!";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String,Object> map=new HashMap<>();
map.put("messageId",messageId);
map.put("messageData",messageData);
map.put("createTime",createTime);
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
rabbitTemplate.convertAndSend("TestFanoutExchange", "", map);
return "ok";
}
@GetMapping("/sendTopicMessage")
public String sendTopicMessage() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "test message, hello!";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String,Object> map=new HashMap<>();
map.put("messageId",messageId);
map.put("messageData",messageData);
map.put("createTime",createTime);
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
rabbitTemplate.convertAndSend("TestTopicExchange", "message."+messageId, map);
return "ok";
}
}
5、编写消费者
@Component
@RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue
public class ConsumerDirect {
@RabbitHandler
public void process(Object testMessage) {
System.out.println("DirectReceiver消费者收到消息 : " + testMessage.toString());
}
}