概述
RabbitMQ入门到精通,作为RabbitMQ入门是一个很好的范例.
RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现,是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们.目前类似于RabbitMQ的消息中间件有很多, RabbitMq、ActiveMq、ZeroMq、kafka都是可选择,RabbitMQ解决的问题
- 信息的发送者和接收者如何维持这个连接,如果一方的连接中断,这期间的数据如何方式丢失
- 如何降低发送者和接收者的耦合度
- 如何让Priority高的接收者先接到数据
- 如何做到load balance?有效均衡接收者的负载
- 如何有效的将数据发送到相关的接收者?也就是说将接收者subscribe 不同的数据,如何做有效的filter
- 如何做到可扩展,甚至将这个通信模块发到cluster上
- 如何保证接收者接收到了完整,正确的数据
RabbitMQ基本概念
架构
名词解释:
- RabbitMQ Server: RabbitMQ 服务核心,是一种投递服务,在应用程序(生成者)与服务器(消费者)之间扮演着路由器的角色,接收生成者消息,同时将消费者路由至消费者
- ClientA&B:生成者,生成消息,消息的构成:有效载荷payload(数据的载体),标签label(描述payload,包含消费者相关信息)
- Client1&2&3:消费者,消费消息(注:消息到达消费者时,其label已经清空,也就是消费者不能识别消息来自何处)
RabbitMQ server 构成
- Connection:TCP连接.生成者与消费者通过TCP与RabbitMQ连接
- Channel:信道,虚拟连接,一个Connection下可建立无数Channel,消息在Channel中进行传输(Channel节约通信开销,提高性能,Connection比喻成电缆,Channel就是独立光纤)
- Exchange&Binding&Queue:交换,绑定,队列.AMQP的三大构成要素.生产者把消息发布到交换器上,消息最终到达队列,并被消费者接收,绑定决定了消息如何从路由器路由到特定的队列.交换器分
- vhost:一个vhost对应一个独立的RabbitMQ Server,实现逻辑上的分离,互相干扰
概念补充:
Exchange
一个消息的传递
- 生成者发布Messgae至Exchange,指定RootingKey(或者为空串),同时绑定Queue(也绑定RootingKey 与Message 中的RootingKey匹配)--queue需提前申明创建
- 消费者读取Queue中Message进行消费(1.进行消息确认,消息从Queue中删除2.转发,消息转移至下一个消费者3.拒绝,消息删除或转发)
Exchange 有三种类型:direct, fanout,topic,还有一种header
- Direct : 直连,通过RoutingKey匹配Queue
- Fanout:广播,忽略RoutingKey匹配,发送至所有Queue
- Topic:规则匹配.按照一定的规则匹配RoutingKey
- header:用键值对取代RootingKey进行匹配
安装好的RabbitMQ有几个默认Exchange(好像是8个)
RabbitMQ安装
window下安装RabbitMQ
- 下载Erlang,安装(exe直接安装,zip安装需要配系统变量ERLANG_HOME)
- 下载RabbitMQ.下一步安装...
- 激活RabbitMQ管理插件,cmd 执行"C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.6\sbin\rabbitmq-plugins.bat" enable rabbitmq_management
- 重启RabbitMQ cmd执行net stop RabbitMQ && net start RabbitMQ
- 访问http://localhost:15672 使用guest/guest(guest账户在localhost下具有所有权限)登录进入ui界面
RabbitMQ基本使用
为方便测试使用ui创建一个全权限账户zl/123(也可以使用
RabbitMQ 命令)
Direct
package producerAndconsume;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* Hello world!
*
*/
public class DirectMQ
{
//交换器名称
public final static String EXCHANGE_NAME = "test.amq.direct";
//队列名称
public final static String QUEUE_NAME = "test.queue";
//RoutingKey
public final static String ROUTINGKEY = "test.routingkey";
public static ConnectionFactory factory;
public static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static {
/**
* 创建连接连接到MabbitMQ
*/
factory = new ConnectionFactory();
//设置MabbitMQ所在主机ip或者主机名
factory.setHost("localhost");
//指定用户 密码
factory.setUsername("zl");
factory.setPassword("123");
//指定端口
factory.setPort(AMQP.PROTOCOL.PORT);
}
public static void init() throws Exception{
//创建一个连接
Connection connection = factory.newConnection();
//创建一信道
Channel channel = connection.createChannel();
//指定一个队列
boolean durable = false; //设置消息持久化 RabbitMQ不允许使用不同的参数重新定义一个队列,所以已经存在的队列,我们无法修改其属性。
//申明一个exchange
channel.exchangeDeclare(EXCHANGE_NAME, "direct", durable);
/* queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments)
* queue:队列名称
* durable:持久化(重启不丢失消息)
* exclusive:排他性,指针对此connection可见
* autoDelete:不使用时自动删除
* arguments:其他参数
*/
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
channel.close();
connection.close();
}
public static void main(String[] argv) throws Exception {
init();
//生产者要比消费者先启动
new Thread(new Runnable() {
public void run() {
try {
comsume();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
TimeUnit.SECONDS.sleep(1);
produce();
/*
* result print
* 2017-09-04 20:45:09 send message: hello world!
2017-09-04 20:45:09 Received Message:'hello world!'
*/
}
public static void comsume() throws Exception {
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//绑定queue
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, ROUTINGKEY);
//创建队列消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
//指定消费队列
boolean ack = false ; //打开应答机制
// 指定消费队列
channel.basicConsume(QUEUE_NAME, ack, consumer);
//设置最大服务转发消息数量 只有在消费者空闲的时候会发送下一条信息。 (即该消费者处理的最大吞吐量)
int prefetchCount = 10;
channel.basicQos(prefetchCount);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(sf.format(new Date())+" Received Message:'" + message + "'");
//发送应答
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),true);
}
}
public static void produce() throws Exception{
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//发送的消息
String message = "hello world!";
channel.basicPublish(EXCHANGE_NAME, ROUTINGKEY, null, message.getBytes());
System.out.println(sf.format(new Date())+" send message: "+ message);
//关闭连接与信道
channel.close();
connection.close();
}
}
Topic
package producerAndconsume;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* Hello world!
*
*/
public class TopicMQ
{
//交换器名称
public final static String EXCHANGE_NAME = "test.amq.topic";
//队列名称 1
public final static String QUEUE_NAME = "test.queue";
//RoutingKey one
public final static String TOPIC_ROUTINGKEY_ONE = "test.routingkey.one";
//RoutingKey one
public final static String TOPIC_ROUTINGKEY_TWO = "test.routingkey.two";
public static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static ConnectionFactory factory;
static {
/**
* 创建连接连接到MabbitMQ
*/
factory = new ConnectionFactory();
//设置MabbitMQ所在主机ip或者主机名
factory.setHost("localhost");
//指定用户 密码
factory.setUsername("zl");
factory.setPassword("123");
//指定端口
factory.setPort(AMQP.PROTOCOL.PORT);
}
public static void init() throws Exception{
//创建一个连接
Connection connection = factory.newConnection();
//创建一信道
Channel channel = connection.createChannel();
//指定一个队列
boolean durable = false; //设置消息持久化 RabbitMQ不允许使用不同的参数重新定义一个队列,所以已经存在的队列,我们无法修改其属性。
//申明一个exchange
channel.exchangeDeclare(EXCHANGE_NAME, "topic", durable);
/* queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments)
* queue:队列名称
* durable:持久化(重启不丢失消息)
* exclusive:排他性,指针对此connection可见
* autoDelete:不使用时自动删除
* arguments:其他参数
*/
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
channel.close();
connection.close();
}
public static void main(String[] argv) throws Exception {
init();
//生产者要比消费者先启动
new Thread(new Runnable() {
public void run() {
try {
comsume();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
TimeUnit.SECONDS.sleep(1);
produce(TOPIC_ROUTINGKEY_ONE);
produce(TOPIC_ROUTINGKEY_TWO);
/*
result print
2017-09-04 20:46:52 send message: test.routingkey.one
2017-09-04 20:46:52 Received Message:'test.routingkey.one'
2017-09-04 20:46:52 send message: test.routingkey.two
2017-09-04 20:46:52 Received Message:'test.routingkey.two'
*
*/
}
public static void comsume() throws Exception {
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
/* routingkey匹配规则
*可以匹配一个标识符。
#可以匹配0个或多个标识符。*/
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "test.routingkey.*");
//创建队列消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
//指定消费队列
boolean ack = false ; //打开应答机制
// 指定消费队列
channel.basicConsume(QUEUE_NAME, ack, consumer);
//设置最大服务转发消息数量 只有在消费者空闲的时候会发送下一条信息。 (即该消费者处理的最大吞吐量)
int prefetchCount = 10;
channel.basicQos(prefetchCount);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(sf.format(new Date()) +" Received Message:'" + message + "'");
//发送应答
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),true);
}
}
public static void produce(String routingkey) throws Exception{
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//发送的消息
String message = routingkey;
channel.basicPublish(EXCHANGE_NAME, routingkey, null, message.getBytes());
System.out.println(sf.format(new Date()) +" send message: "+ message);
//关闭连接与信道
channel.close();
connection.close();
}
}
fanout
package producerAndconsume;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* Hello world!
*
*/
public class FanoutMQ
{
//交换器名称
public final static String EXCHANGE_NAME = "test.amq.fanout";
public static ConnectionFactory factory;
public static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static {
/**
* 创建连接连接到MabbitMQ
*/
factory = new ConnectionFactory();
//设置MabbitMQ所在主机ip或者主机名
factory.setHost("localhost");
//指定用户 密码
factory.setUsername("zl");
factory.setPassword("123");
//指定端口
factory.setPort(AMQP.PROTOCOL.PORT);
}
public static void init() throws Exception{
//创建一个连接
Connection connection = factory.newConnection();
//创建一信道
Channel channel = connection.createChannel();
//指定一个队列
boolean durable = false; //设置消息持久化 RabbitMQ不允许使用不同的参数重新定义一个队列,所以已经存在的队列,我们无法修改其属性。
//申明一个exchange
channel.exchangeDeclare(EXCHANGE_NAME, "fanout", durable);
channel.close();
connection.close();
}
public static void main(String[] argv) throws Exception {
init();
//生产者要比消费者先启动
new Thread(new Runnable() {
public void run() {
try {
comsume();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
TimeUnit.SECONDS.sleep(1);
produce();
/*
* result print
2017-09-04 21:03:16 send message: hello world!
2017-09-04 21:03:16 amq.gen-y1Apmus-2O0LGRUi8Du_NA Received Message:'hello world!'
*/
}
public static void comsume() throws Exception {
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 创建一个非持久的、唯一的且自动删除的队列
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "");
//创建队列消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
//指定消费队列
boolean ack = false ; //打开应答机制
// 指定消费队列
channel.basicConsume(queueName, ack, consumer);
//设置最大服务转发消息数量 只有在消费者空闲的时候会发送下一条信息。 (即该消费者处理的最大吞吐量)
int prefetchCount = 10;
channel.basicQos(prefetchCount);
while (true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(sf.format(new Date())+" "+ queueName+" Received Message:'" + message + "'");
//发送应答
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),true);
}
}
public static void produce() throws Exception{
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//发送的消息
String message = "hello world!";
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(sf.format(new Date())+" send message: "+ message);
//关闭连接与信道
channel.close();
connection.close();
}
}
RPC
package producerAndconsume;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import com.rabbitmq.client.AMQP;
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;
public class RpcMQ
{
private static final String RPC_QUEUE_NAME = "rpc_queue";
public static ConnectionFactory factory;
public static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static {
/**
* 创建连接连接到MabbitMQ
*/
factory = new ConnectionFactory();
//设置MabbitMQ所在主机ip或者主机名
factory.setHost("localhost");
//指定用户 密码
factory.setUsername("zl");
factory.setPassword("123");
//指定端口
factory.setPort(AMQP.PROTOCOL.PORT);
}
public static void main(String[] argv) throws Exception {
server();
for (int i = 0; i < 2; i++) {
RPCClient fibonacciRpc =new RpcMQ().new RPCClient();
System.out.println(" [x] Requesting check hello "+i);
String response = fibonacciRpc.call("hello "+i);
System.out.println(" [.] Got '" + response + "'");
fibonacciRpc.close();
}
/* result print
[x] Awaiting RPC requests
[x] Requesting check hello 0
[.] Got 'check 'hello 0' success'
[x] Requesting check hello 1
[.] Got 'check 'hello 1' success'
*/
}
public static void server() throws Exception{
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
//可以运行多个服务器进程。通过channel.basicQos设置prefetchCount属性可将负载平均分配到多台服务器上。
channel.basicQos(10);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
AMQP.BasicProperties replyProps = new AMQP.BasicProperties
.Builder()
.correlationId(properties.getCorrelationId())
.build();
String response = "";
try {
String message = new String(body,"UTF-8");
response += "check '"+message+"' success";
System.out.println(sf.format(new Date())+" get message " + message+" and return "+response);
}
catch (RuntimeException e){
System.out.println("get exception: " + e.toString());
}
finally {
channel.basicPublish( "", properties.getReplyTo(), replyProps, response.getBytes("UTF-8"));
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//打开应答机制autoAck=false
channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
System.out.println(" [x] Awaiting RPC requests");
}
class RPCClient {
private Connection connection;
private Channel channel;
private String replyQueueName;
public RPCClient() throws Exception {
connection = factory.newConnection();
channel = connection.createChannel();
// 注册'回调'队列,这样就可以收到RPC响应
replyQueueName = channel.queueDeclare().getQueue();
}
// 发送RPC请求
public String call(String message) throws Exception {
final String corrId = UUID.randomUUID().toString();
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().correlationId(corrId).replyTo(replyQueueName).build();
channel.basicPublish("", RPC_QUEUE_NAME, props, message.getBytes("UTF-8"));
final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
if (properties.getCorrelationId().equals(corrId)) {
response.offer(new String(body, "UTF-8"));
}
}
});
return response.take();
}
public void close() throws Exception {
connection.close();
}
}
}