简介
远程过程调用(RPC): 客户端发送一个请求到远程服务器上,远程服务器接收请求并处理结果,将结果响应给客户端,这个过程被称为远程过程调用。
RPC涉及到的基本知识:
-
关于队列:整个过程会设计到两个队列一个是专门保存请求的队列,一般名字被称为rpc_queue,另一个队列被称为响应队列,专门用于保存服务器处理的响应结果,这个队列的名字是随机生成的字符串。
-
关于消息的基本属性BasicProperties:回复(replyTo):是响应队列的名字,当服务器接收请求并处理好结果,服务器需要知道将响应的信息发送到哪个队列中;关联id(correlationId):是一个UUID值,发消息的时候会带上这个值,该值在客户端接收响应时用于判断接收到的响应消息是否是自己发出请求对应的响应; 客户端在发送请求时需要带上replyTo和correlationId两个属性。
-
其他属性:contentType:内容类型,用来描述编码的MIME类型。例如,经常使用JSON编码是将此属性设置为一个很好的做法:application/json,在rpc中该属性不是必须的
RPC的过程描述:
-
客户端首先将请求消息发送到请求队列,在发送请求时需要指定replyTo和correlationId两个值;
-
服务端需要预先订阅请求队列(rpc_queue),以便服务器端能随时接受到请求消息,当服务端接收到请求消息时对请求进行处理,将处理结果发送到响应队列(随机队列)中
-
客户端还要预先订阅响应队列(随机队列),以便当服务器发送响应消息到响应队列中,客户端能及时收到响应结果,服务器在将响应发送到响应队列中还要指定correlationId值(类似于唯一标记当前的请求),这样当客户端从响应队列中接收到消息时就可以通过correlationId的值是否和发送请求的关联id值是否相同,如果相同就证明这个响应结果就是这个请求对应的响应结果。注意这个预先订阅响应队列的步骤需要在客户端中完成,最好在客户端发送请求消息前就完成。
客户端Client
/**
* 客户端
* @throws IOException
* @throws TimeoutException
* @throws InterruptedException
*/
@Test
public void client() throws IOException, TimeoutException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(AMQP.PROTOCOL.PORT);
factory.setUsername("guest");
factory.setPassword("guest");
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
// 预先定义响应结果,即:预先订阅响应队列名和关联id,以接收服务端发送的响应结果
// 声明要关注的响应队列
String reply_to_queue = channel.queueDeclare().getQueue();
// 声明关联id
final String correlationId = UUID.randomUUID().toString();
//传入一个通道,告诉服务器我们需要哪个通道的消息,如果通道中有消息,就会执行回调函数handleDelivery
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
// 判断响应中的关联id和定义的关联id是否一致
if (correlationId.equals(properties.getCorrelationId())) {
// 响应消息
String message = new String(body, "UTF-8");
System.out.println("客户端:收到服务器的响应结果:" + message);
}
}
};
// 自动回复队列应答 -- RabbitMQ中的消息确认机制
channel.basicConsume(reply_to_queue, true, consumer);
// 请求队列
String rpc_queue = "rpc_queue";
// 请求消息
String message = "Hello RabbitMQ !";
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().correlationId(correlationId).replyTo(reply_to_queue).build();
// 将请求消息发送到请求队列
channel.basicPublish("", rpc_queue, properties, message.getBytes());
System.out.println("客户端:发出请求消息:" + message + ",订阅响应队列(" + reply_to_queue + "),等待响应结果");
Thread.sleep(100000);
}
服务端Server
/**
* 服务端
* @throws IOException
* @throws TimeoutException
* @throws InterruptedException
*/
@Test
public void server() throws IOException, TimeoutException, InterruptedException {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置RabbitMQ相关信息
factory.setHost("localhost");
factory.setPort(AMQP.PROTOCOL.PORT);
factory.setUsername("guest");
factory.setPassword("guest");
//创建一个新的连接
Connection conn = factory.newConnection();
//创建一个通道
final Channel channel = conn.createChannel();
// 声明一个响应队列
String rpc_queue = "rpc_queue";
channel.queueDeclare(rpc_queue, false, false, false, null);
// 一次只获取一个通道
channel.basicQos(1);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
// 请求消息
String message = new String(body, "UTF-8");
System.out.println("服务端:收到请求消息:" + message);
// 响应消息,JSON字符串格式
String response = "{'code': 200, 'data': '" + message + "'}";
AMQP.BasicProperties replyProperties = new AMQP.BasicProperties().builder().correlationId(properties.getCorrelationId()).build();
// 获取响应队列名字:replyTo
String replyTo = properties.getReplyTo();
// 发送消息到响应队列
channel.basicPublish("", replyTo, replyProperties, response.getBytes());
System.out.println("服务端:请求已处理完毕,响应结果" + response + ",已发送到响应队列"+ replyTo +"中");
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
// 手动回复队列应答
channel.basicConsume(rpc_queue, false, consumer);
System.out.println("服务端:已订阅请求队列(rpc_queue), 等待接收客户端请求消息...");
Thread.sleep(100000);
}
运行效果
先运行服务端,再运行客户端