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

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

配置消息确认方式,我们通过 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;
}
}
- 消息发送调用 sendAndReceive 方法,该方法自带返回值,返回值就是服务端返回的消息。
- 服务端返回的消息中,头信息中包含了 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);
}
}
- 服务端首先收到消息并打印出来。
- 服务端提取出原消息中的 correlation_id。
- 服务端调用 sendAndReceive 方法,将消息发送给 RPC_QUEUE2 队列,同时带上 correlation_id 参数。
服务端的消息发出后,客户端将收到服务端返回的结果。

浙公网安备 33010602011771号