Rabbitmq 是基于amqp(高级消息队列协议)实现的队列技术,在他之上可以完成多种类型的消息转发模型。
下面列举一些常用的消息转发场景,在rabbitmq中是怎样实现的。
先来看一下rabbitmq消息转发的原理,便于理解消息转发以及怎样实现常见的消息转发模型。
1.生产者创建连接,并创建通道连接rabbitmq
2.使用routing key绑定exchange和队列,可以绑定多个routing key到不同的队列。
3.生产者生产消息发送给exchange
4.exchange根据routing key匹配到对应队列名字,把消息转发到指定的queue上,消息会暂存在队列中,等待消费者来消费。
1.消费者创建连接,并创建通道连接rabbitmq
2.消费者消费指定队列消息
注意:exchange、queue都必须要提前创建或者使用系统默认的也可以。
exchange分为4种,分别是
1.direct,直接转发。exchange通过精确匹配routing key发送消息给队列
2.fanout,广播。会将消息广播到所有绑定到这个exchange的队列,无视发送消息时指定的routingkey。
3.topic,发布、订阅。routing可以可以使用通配符(#、*)来根据主题发送消息到不同的队列。
4.Headers,头信息。根据头信息的参数来决定发送到哪个队列。
队列是用来存储消息,在代码中可以创建临时队列,临时队列的属性是durable (false)、exclusive(true)、autoDelete(true)的队列,消息处理完自动删除。
队列的几种属性
最简单的例子,生产者生产消息到队列,消费者从队列取数据,这个例子也是消息列队最基本、最常用的模型。
下面是建立连接的代码,后面的例子不再重复写这个。
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitmqUtil {
private static Connection connection;
// 获取连接
public static Connection getConnection() {
if (connection != null) {
return connection;
}
String userName = "admin";
String password = "admin";
String host = "192.168.1.248";
int port = 5672;
boolean recoveryEnabled = true;
// connection factory
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(userName);
factory.setPassword(password);
factory.setHost(host);
factory.setPort(port);
factory.setAutomaticRecoveryEnabled(recoveryEnabled);
try {
connection = factory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return connection;
}
// 获取通道
public static Channel getChannel() {
Channel channel = null;
Connection connection2 = getConnection();
try {
channel = connection2.createChannel();
} catch (IOException e) {
e.printStackTrace();
}
return channel;
}
}
生产者、消费者
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
//简单例子
public class Test1 {
private String queueName = "testQueue";
private String routingKey = "routingKey";
private String exchange = "testExchange";
public void produce() {
Channel channel = null;
try {
// 创建通道
channel = RabbitmqUtil.getChannel();
// 创建队列,持久、非专用、非自动删除的队列
channel.queueDeclare(queueName, true, false, false, null);
// 创建一个exchange,使用rabbitmq内置默认exchange也可以,默认的exchange是""一个direct类型
channel.exchangeDeclare(exchange, "direct");
// 使用routing key绑定exchange和queue
channel.queueBind(queueName, exchange, routingKey);
for (;;) {
channel.basicPublish(exchange, routingKey, null, "test".getBytes());
System.out.println("生产者:test");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
public void consume() {
Channel channel = null;
try {
channel = RabbitmqUtil.getConnection().createChannel();
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body, "UTF-8");
System.out.println(" 接收消息:'" + message + "'");
}
};
channel.basicConsume(queueName, true, defaultConsumer);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
public static void main(String[] args) {
final Test1 test1 = new Test1();
new Thread() {
@Override
public void run() {
test1.produce();
}
}.start();
new Thread() {
@Override
public void run() {
test1.consume();
}
}.start();
}
}
在rabbitmq中也可以实现点对点的通信,没有消息队列,所以不会存储消息,生产者和消费者之间直接通信。
说是没有队列,其实是创建了一个临时队列,这个队列不会持久化、自动删除、通道专用。
如下图。
在代码实现时,生产者只发送消息给exchnage,不绑定队列;消费者代码中创建临时队列,并绑定到exchange开始消费消息。
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
//点对点通信
public class Test2 {
private String routingKey = "routingKey";
private String exchange = "testExchange";
public void produce() {
Channel channel = null;
try {
// 创建通道
channel = RabbitmqUtil.getChannel();
// 创建一个exchange,使用rabbitmq内置默认exchange也可以,默认的exchange是""一个direct类型
channel.exchangeDeclare(exchange, "direct");
for (;;) {
channel.basicPublish(exchange, routingKey, null, "test".getBytes());
System.out.println("生产者:test");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
public void consume() {
Channel channel = null;
try {
channel = RabbitmqUtil.getConnection().createChannel();
// 在消费者代码中创建临时队列,并绑定到指定的exchange
String queue = channel.queueDeclare().getQueue();
channel.queueBind(queue, exchange, routingKey);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body, "UTF-8");
System.out.println(" 接收消息:'" + message + "'");
}
};
channel.basicConsume(queue, true, defaultConsumer);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
public static void main(String[] args) {
final Test2 test1 = new Test2();
new Thread() {
@Override
public void run() {
test1.produce();
}
}.start();
new Thread() {
@Override
public void run() {
test1.consume();
}
}.start();
}
}
像下图这样,两个队列使用相同的routing key绑定到同一个exchange上,消息会复制分发到两个队列中。代码不再实现,和例子1一样,只不过多创建一个队列。
利用这种模型可以实现多种场景的例子,例如日志收集,一个队列需要采集所有类型的日志,一个队列只采集错误日志,这样通过rabbitmq,就真正实现了一个消息录入,多种消息模式的转发。
会将消息广播到所有绑定到这个exchange的队列,无视发送消息时指定的routingkey。
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
//广播模式
public class Test3 {
private String queueName = "testQueue";
private String routingKey = "routingKey";
private String exchange = "testExchange1";
public void produce() {
Channel channel = null;
try {
// 创建通道
channel = RabbitmqUtil.getChannel();
// 创建队列,持久、非专用、非自动删除的队列
channel.queueDeclare(queueName, true, false, false, null);
// 创建一个exchange,使用rabbitmq内置默认exchange也可以,默认的exchange是""一个direct类型
channel.exchangeDeclare(exchange, "fanout");
// 使用routing key绑定exchange和queue
channel.queueBind(queueName, exchange, routingKey);
for (;;) {
//发送时的routingkey可以随意指定,所有绑定到这个exchange上的队列都会接收到消息
channel.basicPublish(exchange, "无视routingkey", null, "test".getBytes());
System.out.println("生产者:test");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
public void consume() {
Channel channel = null;
try {
channel = RabbitmqUtil.getConnection().createChannel();
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body, "UTF-8");
System.out.println(" 接收消息:'" + message + "'");
}
};
channel.basicConsume(queueName, true, defaultConsumer);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
public static void main(String[] args) {
final Test3 test1 = new Test3();
new Thread() {
@Override
public void run() {
test1.produce();
}
}.start();
new Thread() {
@Override
public void run() {
test1.consume();
}
}.start();
}
}
和发布订阅消息一样,可以使用通配符关键字订阅指定的消息。
发送消息的routingkey和绑定exchange的routingkey可以是一组用”.”分开的词。词里面可以使用通配符
“*”:表示任意一个关键词
“#”:表示0个或者多个关键词
注意:两个词以上的一定要用”.”号隔开,上面两个通配符只是通配关键词,并非单个字母,如”my*.my1”这样是错误的。
下图是引用官网
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
//发布订阅模式
public class Test4 {
private String queueName = "testQueue";
private String queueName1 = "testQueue1";
private String exchange = "testExchange2";
public void produce() {
Channel channel = null;
try {
// 创建通道
channel = RabbitmqUtil.getChannel();
// 创建两个队列接收订阅消息
channel.queueDeclare(queueName, true, false, false, null);
channel.queueDeclare(queueName1, true, false, false, null);
// 创建一个exchange
channel.exchangeDeclare(exchange, "topic");
// 给两个队列指定对应的订阅内容
channel.queueBind(queueName, exchange, "#.2.#");
channel.queueBind(queueName1, exchange, "#.2.?");
for (;;) {
// 发送两个routingkey消息内容
// 发送routingkey “my test1”,匹配两个队列
channel.basicPublish(exchange, "2.11", null, "test1".getBytes());
System.out.println("生产者:test1");
// 发送routingkey “my test2”,去掉后面的11,两个队列都能接收到
channel.basicPublish(exchange, "2.13.11", null, "test2".getBytes());
System.out.println("生产者:test2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
public void consume() {
Channel channel = null;
try {
channel = RabbitmqUtil.getConnection().createChannel();
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body, "UTF-8");
System.out.println(" 接收消息1:'" + message + "'");
}
};
channel.basicConsume(queueName, true, defaultConsumer);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
public void consume2() {
Channel channel = null;
try {
channel = RabbitmqUtil.getConnection().createChannel();
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body, "UTF-8");
System.out.println(" 接收消息2:'" + message + "'");
}
};
channel.basicConsume(queueName1, true, defaultConsumer);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
public static void main(String[] args) {
final Test4 test1 = new Test4();
new Thread() {
@Override
public void run() {
test1.produce();
}
}.start();
new Thread() {
@Override
public void run() {
test1.consume();
}
}.start();
new Thread() {
@Override
public void run() {
test1.consume2();
}
}.start();
}
}
rabbitmq利用exchange和queue搭配的灵活性已经exchange的类型,可以完成业务场景中各种各样的需求。