目录
rabbitmq有六种队列
简单队列、work模式、发布订阅模式、routing模式、topics模式、RPC模式。
简单队列
一对一的队列。生产者P生产消息放入队列(这里不是简单地直接放入队列中),消费者C消费消息,消费者和生产者是一种一对一的关系。
直接上代码首先封装连接工具类
public class AMQPUtil {
private static final String HOST = "127.0.0.1";
private static final int PORT = 5672;
private static final String USERNAME = "admin";
private static final String PASSWORD = "admin";
public static Connection connection(){
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
try {
return factory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return null;
}
}
消费者:
public class ConsumerSimple {
/**
* 路由键名称
*/
private static final String QUEUE_NAME = "simpleQueue";
//自定义消息消费者
static class MyConsumer extends DefaultConsumer {
public MyConsumer(Channel channel){
super(channel);
}
//重写handleDelivery方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者消费" + new String(body, "UTF-8"));
this.getChannel().basicAck(envelope.getDeliveryTag(), false);
}
}
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
try {
Channel channel = connection.createChannel();
//创建队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//创建消费者对象
MyConsumer consumer = new MyConsumer(channel);
channel.basicConsume(QUEUE_NAME, false, consumer);
System.out.println("消费者监听队列。。。。");
} catch (IOException e) {
e.printStackTrace();
}
}
}
生产者:
public class ProductorSimple {
/**
* 队列的名称
*/
private static final String QUEUE_NAME = "simpleQueue";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
if(connection !=null){
Channel channel = null;
try {
//建立信道
channel = connection.createChannel();
String msg = "hello world";
//参数一:交换器 参数二:路由键 参数三:附加值 参数四:消息
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
System.out.println("生产者发送:"+msg);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
}
这里很可能产生疑惑为啥QUEUE_NAME明明是队列名称,但是向队列中发送消息的时候为啥将队列名称当成路由键传入到方法的参数中去? 解惑:打开我们的web管理界面选择Exchange,将会进入以下页面。。
点击默认的Exchange交换器 ,在绑定那里可以看到一句话。这里的信息非常多。
翻译:默认交换隐式绑定到每个队列,并且路由键等于队列名称。不能显式绑定到默认交换或与之解除绑定。也无法删除。
这里就得解释一下文章开始说的,生产者将消息放入队列,这里的放入的意思并不是直接的放入,而是需要经过一个叫做交换器的东西,消息在投递时(这里的投递就是交换器将消息放入队列)会按照路由键(一种匹配规则,消息在发送时要设置路由键伴随消息一块发送)进行投递到匹配(这里的匹配是指消息设置的路由键 与 交换器和队列绑定的绑定建相同时就是匹配成功)的队列。
简单队列我们虽然没有设置队列和交换器的绑定,但是默认的交换器会将所有没有设置绑定键的队列隐式绑定,绑定键就是队列的名字,这里上面的疑惑就迎刃而解,我们在发送消息时将队列名传入参数其实就是传入的路由键,默认的交换器解析出路由键然后将消息投递到与其隐式绑定的绑定建为路由键的队列中。
work模式
一对多的队列,一个生产者P,多个消费者C1、C2等,多个消费者监听同一个消息队列,当有消息被放入队列时,消息默认情况下会被通过轮询的方式被消费。
上代码
消费者1:
public class ConsumerWork1 {
/**
* 队列名称
*/
private static final String QUEUE_NAME = "workQueue";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
try {
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//设置消费者最多允许多少个消息不被ack
//channel.basicQos(1);
//创建消费者对象
MyConsumer consumer = new MyConsumer(channel);
channel.basicConsume(QUEUE_NAME, true, consumer);
//channel.basicConsume(QUEUE_NAME, false, consumer);
System.out.println("消费者1监听队列。。。。");
} catch (IOException e) {
e.printStackTrace();
}
}
//自定义消息消费者
static class MyConsumer extends DefaultConsumer {
public MyConsumer(Channel channel){
super(channel);
}
//重写handleDelivery方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1消费" + new String(body, "UTF-8"));
//this.getChannel().basicAck(envelope.getDeliveryTag(), false);
}
}
}
消费者2:
public class ConsumerWork2 {
/**
* 队列名称
*/
private static final String QUEUE_NAME = "workQueue";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
try {
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//创建消费者对象
MyConsumer consumer = new MyConsumer(channel);
channel.basicConsume(QUEUE_NAME, true, consumer);
//channel.basicConsume(QUEUE_NAME, false, consumer);
System.out.println("消费者2监听队列。。。。");
} catch (IOException e) {
e.printStackTrace();
}
}
//自定义消息消费者
static class MyConsumer extends DefaultConsumer {
public MyConsumer(Channel channel){
super(channel);
}
//重写handleDelivery方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2消费" + new String(body, "UTF-8"));
//this.getChannel().basicAck(envelope.getDeliveryTag(), false);
}
}
}
生产者:
public class ProductorWork {
/**
* 队列的名称
*/
private static final String QUEUE_NAME = "workQueue";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
if(connection !=null){
Channel channel = null;
try {
//建立信道
channel = connection.createChannel();
for(int i=0; i<10; i++){
String msg = "第"+ (i+1) +"条消息";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes("UTF-8"));
System.out.println("生产者发送:"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
}
打印结果:
生产者:
生产者发送:第1条消息 生产者发送:第2条消息 生产者发送:第3条消息 生产者发送:第4条消息 生产者发送:第5条消息
生产者发送:第6条消息 生产者发送:第7条消息 生产者发送:第8条消息 生产者发送:第9条消息 生产者发送:第10条消息
消费者1:
消费者1监听队列。。。。
消费者1消费第2条消息 消费者1消费第4条消息 消费者1消费第6条消息
消费者1消费第8条消息 消费者1消费第10条消息
消费者2:
消费者2监听队列。。。。
消费者2消费第1条消息 消费者2消费第3条消息 消费者2消费第5条消息
消费者2消费第7条消息 消费者2消费第9条消息
由结果可以看出奇数条消息被消费者1消费,偶数条消息被消费者2消费,每个消息只能被一个消费者消费,这就是轮询分发。
轮询分发 :使用任务队列的优点之一就是可以轻易的并行工作。如果我们积压了好多工作,我们可以通过增加工作者(消费者)来解决这一问题,使得系统的伸缩性更加容易。在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者。平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。虽然这种分配法方式也还行,但是有个问题就是:比如:现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。而RabbitMQ则是不了解这些的。这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将消息发给轮询指定的消费者。
显而易见轮询的方式会出现诸多的问题,可能会导致一直处于忙碌状态的服务器宕机等。因此就需要进行 "公平分发",此公平是以能力大小为度量的公平,四个字能者多劳。
将消费者的注释掉的放开,这里的第二个参数 当为true时代表自动ack确认消息,当消息一旦被发送给消费者无论消费者能不能真正的将消息消费,都会立即发送ack确认消息已经被消费,队列会将该消息删除,这种方式消费者会造成消息的丢失。当为false时,将手动ack,这时需要程序开发者在消息被消费后进行手动确认消息已被消费,队列收到消费者的消息确认,然后将消息从队列中删除(这里开发者一定要记得手动ack,否则会造成消息被重复消费)。
开启手动确认消息。
channel.basicConsume(QUEUE_NAME, false, consumer);
手动发送确认消息 。
this.getChannel().basicAck(envelope.getDeliveryTag(), false);
限制chanel中未被确认的消息的数量(一个channel信道对应一个消费者),超过此数量队列将不再为改消费者分发消息。这里将prefetchCount 设置为1,意味着该channel的消费者每次只被分发到一个消息,当该消息未被确认之前将会不在被分发消息,默认不设置是为0,表示没有限制。
channel.basicQos(1);
可以看出开启手动确认和设置prefetchCount配合使用才会起到限流的作用。
发布订阅模式
一个生产者,多个队列,生产者生产的消息可以被投放在所有的绑定的队列中。交换器为fanout类型。
消费者1
public class ConsumerSubAndPub2 {
private static final String EXCHANGE_NAME = "SubAndPubExchange";
private static final String ROUTING_KEY = "";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
//声明交换器 并指定类型为fanout(广播)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
String queue = channel.queueDeclare().getQueue();
channel.queueBind(queue, EXCHANGE_NAME, ROUTING_KEY);
System.out.println("消费者2监听"+queue+"中。。。");
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println(msg);
}
};
channel.basicConsume(queue, true, callback, consumerTag->{});
} catch (IOException e) {
e.printStackTrace();
}
}
}
消费者2
public class ConsumerSubAndPub1 {
private static final String EXCHANGE_NAME = "SubAndPubExchange";
private static final String ROUTING_KEY = "";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
//声明交换器 并指定类型为fanout(广播)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
String queue = channel.queueDeclare().getQueue();
channel.queueBind(queue, EXCHANGE_NAME, ROUTING_KEY);
System.out.println("消费者1监听"+queue+"中。。。");
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println(msg);
}
};
channel.basicConsume(queue, true, callback, consumerTag->{});
} catch (IOException e) {
e.printStackTrace();
}
}
}
生产者:
public class ProductorSubAndPub {
private static final String EXCHANGE_NAME = "SubAndPubExchange";
private static final String ROUTING_KEY = "";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
String msg = "hello world";
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, msg.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
打印结果:
消费者1:
消费者1监听amq.gen-skZrjqDTzMyJZVTVxDSIGg中。。。
hello world
消费者2:
消费者2监听amq.gen-hXKydkmlByUF4349WI7Y7A中。。。
hello world
路由模式(routing模式)
将交换器与队列使用特定的路由键绑定,发送消息时,消息要带上路由键,消息会被交换器根据路由键匹配投放。
此时的交换器的类型为 direct。
消费者error:
public class ProductorRouting {
private static final String EXCHANGE_NAME = "exchange_routing";
private static final String ROUTINGKEY_ERROR = "errorKey";
private static final String ROUTINGKEY_RIGHT = "rightKey";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
channel.basicPublish(EXCHANGE_NAME, ROUTINGKEY_ERROR, null, "hello world error".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, ROUTINGKEY_RIGHT, null, "hello world right".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
消费者right:
public class ConsumerRoutingRight {
private static final String EXCHANGE_NAME = "exchange_routing";
private static final String QUEUE_RIGHT = "rightQueue";
private static final String ROUTINGKEY_RIGHT = "rightKey";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
channel.queueDeclare(QUEUE_RIGHT, false, false, true, null);
//将交换器与队列通过路由键绑定
channel.queueBind(QUEUE_RIGHT, EXCHANGE_NAME, ROUTINGKEY_RIGHT);
DeliverCallback callback = (String consumerTag, Delivery message)->{
String msg = new String(message.getBody(), "UTF-8");
System.out.println(msg);
};
System.out.println("消费者Right正在监听。。。。");
channel.basicConsume(QUEUE_RIGHT, true, callback, (consumerTag)->{});
}catch (IOException e){
e.printStackTrace();
}
}
}
生产者:
public class ProductorRouting {
private static final String EXCHANGE_NAME = "exchange_routing";
private static final String ROUTINGKEY_ERROR = "errorKey";
private static final String ROUTINGKEY_RIGHT = "rightKey";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
channel.basicPublish(EXCHANGE_NAME, ROUTINGKEY_ERROR, null, "hello world error".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, ROUTINGKEY_RIGHT, null, "hello world right".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
打印结果
消费者error:
消费者Error正在监听。。。。
hello world error
消费者right:
消费者Right正在监听。。。。
hello world right
topic模式
使用表达式匹配的模式。
- * 可以代表一个单词。
- # 可以代表零个或者多个单词。
消费者goodAdd:
public class ConsumerAdd {
private static final String EXCHANGE_NAME = "exchange_topic";
private static final String ROUTINGKEY = "*.good.add.*";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
channel.queueDeclare("addQueue", false, false, true, null);
channel.queueBind("addQueue", EXCHANGE_NAME, ROUTINGKEY);
DeliverCallback callback = (String consumerTag, Delivery message)->{
String msg = new String(message.getBody(), "UTF-8");
System.out.println(msg);
};
System.out.println("*.good.add.* 正在监听。。。。");
channel.basicConsume("addQueue", true, callback, consumerTag -> {});
} catch (IOException e) {
e.printStackTrace();
}
}
}
消费者goodDel:
public class ConsumerDel {
private static final String EXCHANGE_NAME = "exchange_topic";
private static final String ROUTINGKEY = "*.good.del.*";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
channel.queueDeclare("delQueue", false, false, true, null);
channel.queueBind("delQueue", EXCHANGE_NAME, ROUTINGKEY);
DeliverCallback callback = (String consumerTag, Delivery message)->{
String msg = new String(message.getBody(), "UTF-8");
System.out.println(msg);
};
System.out.println("*.good.del.* 正在监听。。。。");
channel.basicConsume("delQueue", true, callback, consumerTag -> {});
} catch (IOException e) {
e.printStackTrace();
}
}
}
消费者good:
public class ConsumerGood {
private static final String EXCHANGE_NAME = "exchange_topic";
private static final String ROUTINGKEY = "#.good.#";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
channel.queueDeclare("goodQueue", false, false, true, null);
channel.queueBind("goodQueue", EXCHANGE_NAME, ROUTINGKEY);
DeliverCallback callback = (String consumerTag, Delivery message)->{
String msg = new String(message.getBody(), "UTF-8");
System.out.println(msg);
};
System.out.println("#.good.# 正在监听。。。。");
channel.basicConsume("goodQueue", true, callback, consumerTag -> {});
} catch (IOException e) {
e.printStackTrace();
}
}
}
生产者:
public class ProductorTopic {
private static final String EXCHANGE_NAME = "exchange_topic";
public static void main(String[] args) {
Connection connection = AMQPUtil.connection();
Channel channel = null;
try {
channel = connection.createChannel();
channel.basicPublish(EXCHANGE_NAME, "aaa.good.add.aaa",null, "aaa.good.add.aaa".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "bbb.good.del.bbb",null, "bbb.good.add.bbb".getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "ccc.good.all.ccc",null, "ccc.good.all.ccc".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
打印结果
goodAdd:
*.good.add.* 正在监听。。。。
aaa.good.add.aaa
goodDel:
*.good.del.* 正在监听。。。。
bbb.good.add.bbb
good:
#.good.# 正在监听。。。。
aaa.good.add.aaa
bbb.good.add.bbb
ccc.good.all.ccc
这里注意:
当队列用 " # " 绑定键绑定时,它将接收所有消息,而与路由键无关,就像在发布/订阅交换中一样。
当在绑定中不使用特殊字符(* 和 #)时,主题交换的行为和路由模式一样。