Routing
路由模式
交换机类型为direct。在这种模式下,发送消息需要带上路由键,比如上图的orange、black就是路由键。
这种模式适用收集日志,类似于下图。不同的日志交由不同的队列处理。也可以作为消息通知,成功了通知一个队列,失败了也通知一条队列。
下面用代码模拟一下日志传递,声明两个队列,一个绑定info,一个绑定error。生产者指定消息投递的路由键
生产者
public class EmitLogDirect {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String infoMessage = "正常日志";
String errorMessage = "错误日志";
String infoRoutingKey = "info";
String errorRoutingKey = "error";
channel.basicPublish(EXCHANGE_NAME, infoRoutingKey, null, infoMessage.getBytes("UTF-8"));
System.out.println(" [info] Sent '" + infoRoutingKey + "':'" + infoMessage + "'");
channel.basicPublish(EXCHANGE_NAME, errorRoutingKey, null, errorMessage.getBytes("UTF-8"));
System.out.println(" [error] Sent '" + errorRoutingKey + "':'" + errorMessage + "'");
}
}
接收者
public class ReceiveLogsDirect {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String infoQueue = "infoQueue";
String errorQueue = "errorQueue";
String infoRoutingKey = "info";
String errorRoutingKey= "error";
//声明info队列
channel.queueDeclare(infoQueue,false,false,true,null);
//绑定路由
channel.queueBind(infoQueue,EXCHANGE_NAME,infoRoutingKey);
//声明error队列
channel.queueDeclare(errorQueue,false,false,true,null);
//绑定路由
channel.queueBind(errorQueue,EXCHANGE_NAME,errorRoutingKey);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback infoDeliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [info] Received '" +
delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
};
channel.basicConsume(infoQueue, true, infoDeliverCallback, consumerTag -> { });
DeliverCallback errorDeliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [error] Received '" +
delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
};
channel.basicConsume(errorQueue, true, errorDeliverCallback, consumerTag -> { });
}
}
Topic
主题模式
交换机声明成topic。路由键可以进行匹配,其中 * 可以匹配任意一个单词,# 可以匹配0个或多个任意单词。注意:单词的区分是以 . 隔开的。
ConsumerTopic01
//声明交换机
public class ConsumerTopic01 {
//声明交换机
public static final String EXCHANGE_NAME = "topic_logs";
public static final String QUEUE_NAME = "q1";
public static final String TOPIC_1 = "*.orange.*";
public static void main(String[] args) throws IOException, TimeoutException {
//获取channel
Channel channel = RabbitMqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列和交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,TOPIC_1);
//消息接收成功
DeliverCallback deliverCallback = (consumerTag,message)->{
System.out.println(new String(message.getBody()));
};
//消息被取消
CancelCallback cancelCallback = consumerTag ->{
};
//绑定回调
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
ConsumerTopic02
public class ConsumerTopic02 {
//声明交换机
public static final String EXCHANGE_NAME = "topic_logs";
public static final String QUEUE_NAME = "q2";
public static final String TOPIC_2 = "*.*.rabbit";
public static final String TOPIC_3 = "lazy.#";
public static void main(String[] args) throws IOException, TimeoutException {
//获取channel
Channel channel = RabbitMqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列和交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,TOPIC_2);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,TOPIC_3);
//消息接收成功
DeliverCallback deliverCallback = (consumerTag,message)->{
System.out.println(new String(message.getBody()));
};
//消息被取消
CancelCallback cancelCallback = consumerTag ->{
};
//绑定回调
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
ProducerTopic
public class ProducerTopic {
//交换机名
public static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.nextLine();
String[] s = message.split(" ");
System.out.println(message);
System.out.println("发送 "+ s[0] + " 消息到 " + s[1]);
channel.basicPublish(EXCHANGE_NAME,s[1],null,s[0].getBytes());
}
}
}
生产者发送消息
(绿色字体是自己输入的)
ConsumerTopic01 --> .orange. 与 q1绑定,因此收到第一条消息
ConsumerTopic02绑定了*.*.rabbit 和 lazy.# 与 q2绑定 ,因此收到二、三、四条消息
RPC
- RPC(远程过程调用)是一种技术,他允许在本地调用远程的方法就像调用本地函数一样,使用RPC可以实现服务间的内部通信。若要使用RPC,可以使用专门的RPC框架例如Dubbo、gRPC等。不必借助消息队列模拟。
消息队列的一些核心特性
消息过期机制
可以给每条消息指定有效期,一段时间内未被消费者处理就过期
方式一:给队列中的所有消息指定一个统一的过期时间
也即是说,无论何时进入这个队列的消息,在特定的时间点都会过期失效。这种方式是针对整个队列
方式二:给某条具体的消息指定过期时间
场景:订单未在指定时间内支付关闭订单 给整个队列添加过期时间
Map<String, Object> args = new HashMap();
args.put("x-message-ttl",60000);
channel.queueDeclare("myqueue",false,false,false,args);
给某条消息指定过期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
// 设置消息过期时间为1000毫秒
.expiration("1000")
.build();
//自行设置交换机、路由键、消息
channel.basicPublish("exchange","routing-key",properties,"message".getBytes());
消息确认机制
为了保证消息成功被消费,rabbitMQ提供消息确认机制,当消费者接收到消息后要给一个反馈
- ack: 消费成功
- nack: 消费失败
- rejecte:拒绝
我们需要关闭之前一直开启的自动确认机制,修改成手动确认
channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
消息处理成功
multiple:批量确认(一次性确认历史消息直到当前消息)
消息处理失败,nack拒绝
requeue:消息确认失败后重新放入队列
这样我们可以在消息成功被处理完成之后再回馈给消息队列,消息队列才能放心的删除这条消息