1:什么是消息队列
消息队列,即MQ,Message Queue
消息队列是典型的:生产者,消费者模式。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产者和消费者都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。
例如:
- 商品服务对商品增删改以后,无需去操作索引库,只是发送一条消息,也不关心消息被谁接收。
- 搜索服务接收消息,处理索引库。
如果以后还有其他系统也依赖商品服务的数据,同样监听消息即可,商品服务无需任何代码修改。
2:AMQP和JMS
MQ是消息通信的模型,并不是具体实现,现在实现MQ的主要有两种方式:AMQP,JMS。
两者之间的区别和联系:
- JMS是定义了统一的接口,来对消息操作进行统一。AMQP是通过规定协议里啊统一数据交互的格式。
- JMS限定了必须使用java语言,AMQP只是协议,不规定实现方式,因此是跨语言的。
- JMS规定了两种消息模型,而AMQP的消息模型更加丰富。
2:五中消息模型
RabbitMQ提供了6中消息模型,但是第6中其实是RPC,并不是MQ,因此我们只需要学习5中就好。
其中 3,4, 5 这三种都属于订阅模型,只不过在进行路由的方式不同。
我们开始一一简绍5中模型的使用方法,我们采用的是原生的代码。
1:基本消息模型(Hello,World)
RabbitMQ是一个消息代理:它接收和转发消息。你可以把它想象成一个邮局:当你把右键放在邮箱里时,你可以确定邮差最终会把右键发送给你的收件人,在这个比喻中,RabbitMQ是邮政信箱,邮局,和邮递员。
基本数据模型原理图解析:
P(Producer):生产者,一个发送消息的用户应用程序。
C(consument):消费者,消费者是一个主要用来等待接收消息的用户应用程序。
队列(红色区域):rabbitMQ内部类似于邮箱的一个概念。相当于生产者发送消息到队列中,消费者监听队列的是否有新消息到达,如果有就取出消息。队列是存储消息的缓冲区
我们下面正式开始编写代码:我们将创建一个生产者,一个消费者。
我们在下面的所用代码中都将使用连接工具类。
public static Connection getConnection()throws Exception {
//定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("*****");
//端口
factory.setPort(5672);
//设置账号信息,用户名、密码、vhost
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
// 通过工程获取连接
Connection connection = factory.newConnection();
return connection;
}
1:代码编写
1:生产者端
public static final String QUEUE_NAME = "My_Demo";
//测试普通模式发送消息
@Test
public void send01() throws Exception{
//获取到连接以及通道
Connection connection = getConnection();
Channel channel = connection.createChannel();
//创建队列,只有创建队列才可以发送消息
//队列是幂等的,只有当它不存在的时候在可以被创建
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//消息内容(将对象装换为JSON串,这里网上代码很多)
User user = new User(1,"admin","1234");
String data = JsonUtils.objectToJson(user);
channel.basicPublish("", QUEUE_NAME, null, data.getBytes());
//关闭通道和连接
channel.close();
connection.close();
}
channel.basicPublish("", QUEUE_NAME, null, data.getBytes());
basicPublish方法
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
throws IOException;
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
throws IOException;
- exchange 交换机名称
- routingKey 路由键
- props 消息参数
- body 消息体,即真正需要发送的消息
- mandatory true时,交换器无法根据自动的类型和路由键找到一个符合条件的队列,那么RabbitMq会调用Basic.Ruturn命令将消息返回给生产者,为false时,出现上述情况消息被直接丢弃
- immediate true,如果交换器在消息路由到队列时发现没有任何消费者,那么
这个消息将不会存和队列,当与路由匹配的所有队列都没有消费者时,会Basic.Return返回给生产者3.0去掉了immediate 参数
immediate和mandatory 都是消息传递过程中,不可达目的地是,将消息返回给生产者的功能
2:消费者获取消息
@Test
public void ConsuMent02() throws Exception {
// 获取到连接
Connection connection = getConnection();
// 创建通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义队列的消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
//获取消息内容然后处理
String msg = new String(body, "UTF-8");
System.err.println("*********** HelloConsumer" + " get message :[" + msg +"]");
}
};
// 监听队列,第二个参数:是否自动进行消息确认。
channel.basicConsume(QUEUE_NAME, true, consumer);
}
我们会发现,消费者获取到了消息,但是程序没有停止,一直在监听对流中是否有新的消息。一旦有新消息进入队列,就会立即打印。
2:消息确认机制(ACK)
通过刚才的案例可以看出,消息一旦消费者接受,队列中的消息就会被删除。
如果消费者进去消息后,还没执行操作就关掉了,或者抛出了异常?,消息消费失败,但是RabbitMQ无从得知,这样消息就丢失了。
因此:rabbitMQ有一个Ack机制。当消费者获取消息后,回想RabbitMQ发送回执ACK,告诉消息以及被接收,不过这种回执ACK分两种情况:
- 自动ACK:消息一旦被接收,消费者自动发送ACK
- 手动ACK:消息接收后,不会发送ACK,需要手动调用。
我们需要根据消息的重要性来决定使用哪种方式:
- 如果消息不太重要,丢失也没有影响,那么自动ACK回比较方便。
- 如果消息非常重要,不容丢失,那么最好在消费完成好手动ACK。
我们上面测试的入门Hello,world 中是自动ACK的,如果要手动ACK,需要改动消费者端的代码:
//手动确认消息
@Test
public void ConSument02() throws Exception{
//获取连接
Connection connection = getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//接收消息
// 定义队列的消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
//获取消息内容然后处理
String msg = new String(body, "UTF-8");
System.err.println("*********** HelloConsumer" + " get message :[" + msg +"]");
//手动确认消息代码 ↓
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//第二个参数代表 进行手动确认消息
channel.basicConsume(QUEUE_NAME, false, consumer);
}
注意到最后一行代码:
channel.basicConsume(QUEUE_NAME,false,consumer);
如果第二个参数为true,就会自定进行ACK,如果为false,则需要手动ACK。
//手动确认消息代码 ↓
channel.basicAck(envelope.getDeliveryTag(), false);
以上是RabbitMQ入门,及第一章消息模型的代码。后序的消息模型代码,和整合SpringBoot,请查看我的博客目录。(在此推荐大家搜索**http://yun.itheima.com/
** 黑马程序员。RabbitMQ文章是学习了黑马视频所写。)