RabbitMQ基础入门(下)

153 阅读4分钟

承接上文:RabbitMQ 基础入门(上)

Routing

路由模式

图片.png

交换机类型为direct。在这种模式下,发送消息需要带上路由键,比如上图的orange、black就是路由键。

这种模式适用收集日志,类似于下图。不同的日志交由不同的队列处理。也可以作为消息通知,成功了通知一个队列,失败了也通知一条队列。

图片.png

下面用代码模拟一下日志传递,声明两个队列,一个绑定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 -> { });  
     }  
}

图片.png

图片.png

Topic

主题模式 图片.png 交换机声明成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());  
        }  

    }  
}

生产者发送消息

图片.png

(绿色字体是自己输入的)

ConsumerTopic01 --> .orange. 与 q1绑定,因此收到第一条消息

图片.png

ConsumerTopic02绑定了*.*.rabbit 和 lazy.# 与 q2绑定 ,因此收到二、三、四条消息

图片.png

RPC

图片.png

  • 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);

消息处理成功 图片.png

multiple:批量确认(一次性确认历史消息直到当前消息)

消息处理失败,nack拒绝

图片.png requeue:消息确认失败后重新放入队列

这样我们可以在消息成功被处理完成之后再回馈给消息队列,消息队列才能放心的删除这条消息