1. RPC
1.1 RPC
Remote Procedure Call:远程过程调用,一次远程过程调用的流程即客户端发送一个请求到服务端,服务端根据请求信息进行处理后返回响应信息,客户端收到响应信息后结束
1.2 Client interface客户端接口
为了说明如何使用RPC服务,我们将把“发送方”和“接收方”更改为“客户端”和“服务器”。当我们调用服务时,我们将得到我们对应的斐波那契值
Integer response = (Integer) template.convertSendAndReceive
(exchange.getName(), "rpc", 5);
System.out.println(" [.] Got '" + response + "'");
1.3 Callback queue回调队列
一般来说,通过RabbitMQ执行RPC很容易。客户端发送请求消息,服务器进行回复。为了接收响应,我们需要随请求一起发送“回调”队列地址。当我们使用上述convertSendAndReceive()方法时,Spring AMQP的RabbitTemplate为我们处理回调队列。使用RabbitTemplate时,无需执行任何其他设置
Message properties消息属性
- deliveryMode:将消息标记为持久(值为2)或瞬态(任何其他值)
- contentType: 用于描述编码的mime-type。例如,对于经常使用的JSON编码,将此属性设置为:application/JSON是一种很好的做法
- replyTo:常用于命名回调队列
- correlationId: 用于将RPC响应与请求关联
1.4 Correlation Id关联ID
Spring AMQP允许您关注正在使用的消息方式,并隐藏支持此方式所需的消息管道的详细信息。例如,本机客户端通常会为每个RPC请求创建一个回调队列。这效率很低,所以另一种方法是为每个客户端创建一个回调队列
这引发了一个新问题,在该队列中收到响应后,不清楚响应属于哪个请求。此时将使用correlationId属性。Spring AMQP自动为每个请求设置唯一值。此外,它还处理拥有正确correlationID的响应
Spring AMQP使RPC样式更容易的一个原因是,有时您可能希望忽略回调队列中的未知消息,而不是由于错误而失败。这是由于服务器端可能存在竞争条件。虽然不太可能,但RPC服务器可能会在向我们发送应答之后,但在发送请求的确认消息之前死亡。如果发生这种情况,重新启动的RPC服务器将再次处理该请求。Spring AMQP客户端优雅地处理重复的响应,理想情况下RPC应该是幂等的
1.5. 总结
RPC工作流程如下:
- RpcConfig新建一个直连交换器rpc并绑定队列
- 客户端调用convertSendAndReceive方法,设置exchange、routing key、message
- 请求并发送至队列rpc.requests
- 服务端监听队列中的客户端请求,请求一旦进入队列,服务端开始工作并使用队列的replyTo字段进行响应,
- 客户端等待回调队列中消息,当消息到达,其核查correlationId字段,如果匹配,则返回响应消息给应用。这在RabbitTemplate自动实现
1.6. 完整代码
@Configuration
public class RpcConfig {
@Bean
public DirectExchange rpc() {
return new DirectExchange("rpc");
}
private static class ServerConfig {
@Bean
public Queue rpcQueue() {
return new Queue("rpc.requests");
}
@Bean
public Binding binding(DirectExchange rpc,
Queue rpcQueue) {
return BindingBuilder.bind(rpcQueue)
.to(rpc)
.with("rpc");
}
}
}
@Component
public class RpcClient {
private RabbitTemplate rabbitTemplate;
public RpcClient(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void send() {
System.out.println(" [客户端] 发送请求10");
Integer response = (Integer) rabbitTemplate.convertSendAndReceive("rpc", "rpc", 10);
System.out.println(" [客户端] 收到响应 " + response + "");
}
}
@Component
public class RpcServer {
@RabbitListener(queues = "rpc.requests")
public int fibonacci(int n) {
System.out.println(" [服务端] 收到请求" + n);
int result = fib(n);
System.out.println(" [服务端] 响应请求 " + result);
return result;
}
public int fib(int n) {
return n == 0 ? 0 : n == 1 ? 1 : (fib(n - 1) + fib(n - 2));
}
}
@SpringBootTest
public class RabbitTest {
@Autowired
private RpcClient rpcClient;
@Test
public void testRpc() {
rpcClient.send();
}
2. 消息发布确认
Publisher Confirms发布确认是用于实现可靠发布的RabbitMQ扩展。
我们将使用发布确认来确保已发布的消息已安全到达代理。我们将介绍几种使用publisher确认的策略,并解释其优缺点
首先检查application.yml文件
spring:
rabbitmq:
host: 127.0.0.1
# 之前博客未加端口,此处新增
port: 5672
username: guest
password: guest
virtualHost: /
2.1 单独发布消息
- 新增配置文件PublishConfirmConfig.java
@Configuration
public class PublishConfirmConfig {
@Bean("myRabbitConnectionFactory")
public ConnectionFactory myRabbitConnectionFactory(RabbitProperties rabbitProperties){
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
cachingConnectionFactory.setHost(rabbitProperties.getHost());
cachingConnectionFactory.setPort(rabbitProperties.getPort());
cachingConnectionFactory.setUsername(rabbitProperties.getUsername());
cachingConnectionFactory.setPassword(rabbitProperties.getPassword());
cachingConnectionFactory.setVirtualHost("/");
return cachingConnectionFactory;
}
@Bean
public RabbitTemplate simpleRabbitTemplate(ConnectionFactory myRabbitConnectionFactory) {
CachingConnectionFactory connectionFactory = (CachingConnectionFactory) myRabbitConnectionFactory;
connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.SIMPLE);
connectionFactory.setPublisherReturns(true);
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
return rabbitTemplate;
}
@Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter(){
return new Jackson2JsonMessageConverter();
}
}
- 新增发送文件PublishConfirmSender.java
@Component
public class PublishConfirmSender {
private RabbitTemplate simpleRabbitTemplate;
public PublishConfirmSender(RabbitTemplate simpleRabbitTemplate) {
this.simpleRabbitTemplate = simpleRabbitTemplate;
}
public void oneSender() {
boolean sendFlag = simpleRabbitTemplate.invoke(operations -> {
simpleRabbitTemplate.convertAndSend("direct", "orange", "orange msg");
return simpleRabbitTemplate.waitForConfirms(5000);
});
if (sendFlag) {
System.out.println("消息已成功发送");
}
}
}
- 测试发送
@SpringBootTest
public class RabbitTest {
@Autowired
private PublishConfirmSender publishConfirmSender;
@Test
public void testOneSender() {
publishConfirmSender.oneSender();
}
}
2.2 批量消息发布确认
@Component
public class PublishConfirmSender {
............
public void batchSender() {
boolean sendFlag = simpleRabbitTemplate.invoke(operations -> {
for (int i = 0; i < 50; i++) {
simpleRabbitTemplate.convertAndSend("direct", "orange", "orange " + i + "msg");
if (i % 10 == 0) {
if (simpleRabbitTemplate.waitForConfirms(5000)) {
System.out.println(i / 10 + "批次消息已全部成功发送");
}
}
}
return simpleRabbitTemplate.waitForConfirms(5000);
});
if (sendFlag) {
System.out.println("消息已全部成功发送");
}
}
}
2.3 发布服务器异步确认
@Component
public class PublishConfirmSender {
......
@Bean
@Primary
public RabbitTemplate asyncRabbitTemplate(ConnectionFactory myRabbitConnectionFactory) {
CachingConnectionFactory connectionFactory = (CachingConnectionFactory) myRabbitConnectionFactory;
connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
connectionFactory.setPublisherReturns(true);
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
}
@Component
public class PublishConfirmSender {
public void asyncSender() {
asyncRabbitTemplate.invoke(operations -> {
for (int i = 0; i < 50; i++) {
String body = "orange " + i + "msg";
simpleRabbitTemplate.convertAndSend("direct", "orange", body);
}
return null;
}, (deliveryTag, multiple) -> {
System.out.format("消息已确认. Sequence number: %d, multiple: %b%n", deliveryTag, multiple);
}, (deliveryTag, multiple) -> {
System.err.format("消息未确认. Sequence number: %d, multiple: %b%n",deliveryTag, multiple);
});
}
}
2.4 总结
在某些应用程序中,确保将发布的消息发送给代理是至关重要的。Publisher确认有助于满足此要求的RabbitMQ功能。Publisher确认本质上是异步的,但也可以同步处理它们。没有明确的方法来实现publisher确认,这通常归结为应用程序和整个系统中的约束。典型技术包括
- 单独发布消息,同步等待确认:简单,但吞吐量非常有限。
- 批量发布消息,同步等待批处理的确认:简单、合理的吞吐量,但很难判断何时出错。
- 异步处理:最佳性能和资源利用率,在发生错误时进行良好控制,但需要正确处理。
欢迎关注公众号算法小生或沈健的技术博客shenjian.online
欢迎关注公众号算法小生或沈健的技术博客shenjian.online