在Java语言(或者其他语言)中,经典的生产者-消费者模式,催生了消息队列这个中间件服务,常用的消息队列有ActiveMQ、RabbitMQ、RocketMQ和Kafka等,Redis也有提供消息队列的功能,不过很少用Redis当作消息中间件来使用。今天就通过一个简单的RabbitMQ的生产-消费示例,来解释一下消息队列的相关概念。
首先要有一个RabbitMQ的服务,服务怎么安装可参照我以前写的博客,启动服务后可通过http://localhost:15672来访问,端口15672是通过浏览器访问RabbitMQ服务的端口,也可以说是客户端端口,而RabbitMQ服务的端口是5672,我们要连接RabbitMQ服务要通过5672端口来访问,浏览器输入http://localhost:15672访问,会让输入用户名和密码,通过自己新建的用户名和密码输入访问,如下图所示:
服务启动正常,下面通过一个简单的小示例进行一次生产者-消费者的测试。
生产者代码如下:
package com.pig.amqp.test;
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.MessageProperties;
public class RabbitProducer {
//下面的这些常量在项目开发中应配置在配置文件中
private static final String EXCHANGE_NAME = "exchange_demo";
private static final String ROUTING_KEY = "routing_demo";
private static final String QUEUE_NAME = "queue_demo";
private static final String IP_ADDRESS = "127.0.0.1";
private static final int PORT = 5672;
private static final String USERNAME = "root";
private static final String PWD = "root";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PWD);
//从连接工厂中获取连接
Connection conn = factory.newConnection();
//从连接中获取一个通道
Channel channel = conn.createChannel();
//声明交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, null);
//声明队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//绑定路由、交换器、队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
String message = "hello world";
//发送消息
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
//释放资源
channel.close();
conn.close();
}
}
运行一下,查看队列里的消息,可以看出,增加了一条消息,这个界面下面还有个get message,可以查看生产的什么消息:
下面是消费者的代码:
package com.pig.amqp.test;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Address;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
public class RabbitConsumer {
private static final String QUEUE_NAME = "queue_demo";
private static final String IP_ADDRESS = "127.0.0.1";
private static final int PORT = 5672;
private static final String USERNAME = "root";
private static final String PWD = "root";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//设置服务连接地址,消费者可消费多个生产消息服务地址上的消息
Address[] addresses = new Address[] {
new Address(IP_ADDRESS, PORT)
};
//设置连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(USERNAME);
factory.setPassword(PWD);
//获取连接
Connection conn = factory.newConnection(addresses);
//获取通道
Channel channel = conn.createChannel();
//可设置同一时刻最多接收未被ack的消息数量
channel.basicQos(1024);
//设置消费者和回调方法
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
BasicProperties properties,
byte[] body)
throws IOException {
System.out.println("receive msg:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//进行消费
channel.basicConsume(QUEUE_NAME, consumer);
//消费完后释放资源
TimeUnit.SECONDS.sleep(5);
channel.close();
conn.close();
}
}
运行完后可以看到接收到了刚才发送的消息:
从上述例子可以大概知道,RabbitMQ的模型架构如下:
对于生产者,也就是Producer,是创建消息的一方;对于消费者,就是Consumer,是消费消息的一方,也称为订阅消息;Broker就是消息中间间的服务节点。
在上述例子中出现了exchange、routing、queue等词语,下面解释一下这些概念:
Queue:队列,是RabbitMQ的内部对象,用于存储消息,在RabbitMQ中,消息只能存在队列中,生产者向队列中生产消息,消费者通过指定的这个队列来消费消息,可以指定多个消费者,这时队列中的消息会使用轮询算法给多个消费者进行处理。
Exchange:交换器,在RabbitMQ中,生产者是将消息发送到Exchange交换器上,然后由交换器将消息路由到队列中,如果路由不到,会返回给生产者,或者直接丢弃。
RoutingKey:路由键,生产者将消息发送给交换器时,要指定一个路由键,用来指定这个消息的路由规则
Binding:绑定,要想使用路由规则,在RabbitMQ中要通过绑定将交换器和队列绑定起来,并且指定一个绑定键,这样RabbitMQ就能正确的将消息路由到队列中
在上述代码中,还有一个单词是"direct",这是交换器的类型:
Exchange Type:交换器的类型,分为4种:fanout、direct、topic和headers。
fanout是将所有发送到该交换器的消息路由到所有与该交换器绑定的队列中,而忽略路由规则;
direct是会把消息路由到那些 BindingKey 和 RoutingKey 完全匹配的队列中。本例中都是ROUTING_KEY = "routing_demo",完全匹配;
topic:与direct的完全匹配不同,topic是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,关于匹配规则可自行搜索
headers:此类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中 的 headers 属性进行匹配,headers类型的交换器性能差且不实用,基本不使用这个。
掌握好生产者 (Producer ) 、消费者 (Consumer) 、队列 (Queue) 、交换器 (Exchange) 、路由键 (RoutingKey )、绑定 (Binding) 、 连接 (Connection) 和通道(Channel,或者信道) 等基本术语,对于学习消息队列非常有帮助!