RabbitmqRPC

Rabbitmq - RPC

RabbitMQ 实现 RPC

RPC(Remote Procedure Call Protocol 远程过程调用协议)RabbitMQ 也给我们提供了 RPC 功能,并且使用起来很简单。

6e9955b502cce3b20dcd8e5802194540.png

图:

  1. 首先 Client 发送一条消息,和普通的消息相比,这条消息多了两个关键内容:一个是 correlation_id,这个表示这条消息的唯一 id,还有一个内容是 reply_to,这个表示消息回复队列的名字。
  2. Server 从消息发送队列获取消息并处理相应的业务逻辑,处理完成后,将处理结果发送到 reply_to 指定的回调队列中。
  3. Client 从回调队列中读取消息,就可以知道消息的执行情况是什么样子了。

这种情况其实非常适合处理异步调用。

image-20220210165327688

配置消息确认方式,我们通过 correlated 来确认,只有开启了这个配置,将来的消息中才会带 correlation_id,只有通过 correlation_id 我们才能将发送的消息和返回值之间关联起来。最后一行配置则是开启发送失败退回。

Clinet 端

@Configuration
public class QueueConfig {

    public static final String MSG_QUEUE = "q1";
    public static final String REPLAY_QUEUE = "q2";

    public static final String RPC_EXCHANGE = "rpcExchange";

    /**
     * 消息发送队列
     * @return
     */
    @Bean
    public Queue msgQueue() {
        return new Queue(MSG_QUEUE);
    }

    /**
     * 设置消息返回队列
     * @return
     */
    @Bean
    public Queue replyQueue() {
        return new Queue(REPLAY_QUEUE);
    }

    /**
     * 设置 rpc 交换机
     * @return
     */
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(RPC_EXCHANGE);
    }

    /**
     * 设置请求队列与交换机绑定
     * @return
     */
    @Bean
    public Binding msgBinding() {
        return BindingBuilder.bind(msgQueue()).to(topicExchange()).with(MSG_QUEUE);
    }

    /**
     * 设置返回队列与交换机绑定
     * @return
     */
    @Bean
    public Binding replyBinding() {
        return BindingBuilder.bind(replyQueue()).to(topicExchange()).with(REPLAY_QUEUE);
    }

    /**
     * 设置 RabbitTemplate 发送和接收消息以及回调队列地址
     * @param connectionFactory
     * @return
     */
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {// 这个 connection 是spring的
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setReplyAddress(REPLAY_QUEUE);
        // 等待响应的超时时间
        rabbitTemplate.setReplyTimeout(6000);
        return rabbitTemplate;
    }

    /**
     * 给返回队列设置监听器
     * @param connectionFactory
     * @return
     */
    @Bean
    public SimpleMessageListenerContainer replyContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames(REPLAY_QUEUE);
        container.setMessageListener(rabbitTemplate(connectionFactory));
        return container;
    }
}

在 Spring Boot 中我们负责消息发送的工具是 RabbitTemplate,默认情况下,系统自动提供了该工具,但是这里我们需要对该工具重新进行定制,主要是添加消息发送的返回队列,最后我们还需要给返回队列设置一个监听器。

@Slf4j
@RestController
public class RpcController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/send/{message}")
    public String send(@PathVariable("message") String message) {
        // 1.构建消息对象
        Message newMsg = MessageBuilder.withBody(message.getBytes()).build();
        // 2.客户端发送消息 参数 1 指定交换机 2 RoutingKey 3 发送消息
        Message result = rabbitTemplate.sendAndReceive(QueueConfig.RPC_EXCHANGE, QueueConfig.MSG_QUEUE, newMsg);
        String response = "";
        if (result != null) {
            // 获取已发消息的correlationId
            String correlationId = newMsg.getMessageProperties().getCorrelationId();
            log.info("correlationId : "+correlationId);
            Map<String, Object> headers = result.getMessageProperties().getHeaders();
            // 获取 server 返回的消息 id
            String msgId = (String) headers.get("spring_returned_message_correlation");
            if (msgId.equals(correlationId)) {
                // 获取值
                response = new String(result.getBody());
            }
        }
        return response;
    }
}
  1. 消息发送调用 sendAndReceive 方法,该方法自带返回值,返回值就是服务端返回的消息。
  2. 服务端返回的消息中,头信息中包含了 spring_returned_message_correlation 字段,这个就是消息发送时候的 correlation_id,通过消息发送时候的 correlation_id 以及返回消息头中的 spring_returned_message_correlation 字段值,我们就可以将返回的消息内容和发送的消息绑定到一起,确认出这个返回的内容就是针对这个发送的消息的。

这就是整个客户端的开发,其实最最核心的就是 sendAndReceive 方法的调用。调用虽然简单,但是准备工作还是要做足够。例如如果我们没有在 application.yml 中配置 correlated,发送的消息中就没有 correlation_id,这样就无法将返回的消息内容和发送的消息内容关联起来。

Server 端

application.properties 配置文件,该文件的配置也和客户端中的配置一致

@Configuration
public class QueueConfig {

    public static final String MSG_QUEUE = "q1";
    public static final String REPLAY_QUEUE = "q2";

    public static final String RPC_EXCHANGE = "RPC";

    /**
     * 消息发送队列
     * @return
     */
    @Bean
    public Queue msgQueue() {
        return new Queue(MSG_QUEUE);
    }

    /**
     * 设置消息返回队列
     * @return
     */
    @Bean
    public Queue replyQueue() {
        return new Queue(REPLAY_QUEUE);
    }

    /**
     * 设置 rpc 交换机
     * @return
     */
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(RPC_EXCHANGE);
    }

    /**
     * 设置请求队列与交换器绑定
     * @return
     */
    @Bean
    public Binding msgBinding() {
        return BindingBuilder.bind(msgQueue()).to(topicExchange()).with(MSG_QUEUE);
    }

    /**
     * 设置返回队列与交换器绑定
     * @return
     */
    @Bean
    public Binding replyBinding() {
        return BindingBuilder.bind(replyQueue()).to(topicExchange()).with(REPLAY_QUEUE);
    }
}
@Slf4j
@RestController
public class RpcServerController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RabbitListener(queues = QueueConfig.MSG_QUEUE)
    public void process(Message message) {
        log.info("server 1     "+message);
        log.info("server 2     "+new String(message.getBody()));
        Message response = MessageBuilder.withBody(("server 需要返回的消息 :  "+ new String(message.getBody())).getBytes()).build();
        CorrelationData correlationData = new CorrelationData(message.getMessageProperties().getCorrelationId());
        //  RPC的基本模式。 用特定的路由键向特定的交换机发送消息,并尝试接收响应。 实现通常会将reply-to报头设置为一个独占队列,并等待一段受超时限制的时间。
        rabbitTemplate.sendAndReceive(QueueConfig.RPC_EXCHANGE, QueueConfig.REPLAY_QUEUE, response, correlationData);
    }
}
  1. 服务端首先收到消息并打印出来。
  2. 服务端提取出原消息中的 correlation_id。
  3. 服务端调用 sendAndReceive 方法,将消息发送给 RPC_QUEUE2 队列,同时带上 correlation_id 参数。

服务端的消息发出后,客户端将收到服务端返回的结果。

posted @ 2022-03-05 20:51  zrzicu  阅读(49)  评论(0)    收藏  举报