1、RPC简述
RPC,Remote Procedure Call 远程过程调用。通俗讲,两段程序不在同一个内存空间,无法直接通过方法名调用,就需要通过网络通信方式调用。对于RabbitMQ,本身就是用于消息通信。简单的RabbitMQ是,生产端发送消息,经由交换器,到达队列。消费端不需要知道生产端,消费端订阅队列,消费队列中的消息。而对于RPC,希望消费端消费消息后,返回一个结果,结果经由网络,再返回给生产端。
不考虑RabbitMQ针对RPC的特有设计。最简单的设计是,生产端和消费端共同约定消费队列和回复队列,同时生产端每次发送消息时指定一个唯一ID。生产端将消息和唯一ID发送给消费队列,消费者从消费队列获取消息。处理后,将结果和生产端发送过来的唯一ID,发送给回复队列。生产端从回复队列获取消息和ID,判断ID是否匹配,匹配,则此消息为回复消息。
以上实现的RPC存在问题:生产端和消费端需要约定回复队列,这就要求生产端和消费端互相知道,这无法实现解耦。解决方案:生产端在发送消息时,也将回复队列名称随消息一起发送给队列。
2、举例说明RabbitMQ中的RPC实现
相关要点,见源码注释
pom依赖
<dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>4.6.0</version> </dependency> </dependencies>
生产端,也就是客户端代码
1 package test; 2 3 import java.io.IOException; 4 import java.util.UUID; 5 6 import com.rabbitmq.client.AMQP; 7 import com.rabbitmq.client.AMQP.BasicProperties; 8 import com.rabbitmq.client.Channel; 9 import com.rabbitmq.client.DefaultConsumer; 10 import com.rabbitmq.client.Envelope; 11 12 import utils.ChannelUtils; 13 14 public class RPCClient { 15 16 public static void main(String[] args) throws IOException { 17 RPCClient rpcClient = new RPCClient(); 18 rpcClient.client(); 19 } 20 21 public void client() throws IOException { 22 //此方法封装了如何连接RabbitMQ和创建connection,channel.源码见附录 23 Channel channel = ChannelUtils.getChannelInstance("client"); 24 channel.exchangeDelete("exchange_rpc"); 25 channel.exchangeDeclare("exchange_rpc", "direct", false, false, null); 26 27 channel.queueDelete("queue_rpc"); 28 channel.queueDeclare("queue_rpc", false, false, false, null); 29 30 channel.queueBind("queue_rpc", "exchange_rpc", "rpc"); 31 32 //此处注意:我们声明了要回复的队列。队列名称由RabbitMQ自动创建。 33 //这样做的好处是:每个客户端有属于自己的唯一回复队列,生命周期同客户端 34 String replyQueue = channel.queueDeclare().getQueue(); 35 final String corrID = UUID.randomUUID().toString(); 36 37 //这里我们设计三类消息。 38 //消息1:指定回复队列和ID 39 //消息2:仅指定回复队列 40 //消息3:不指定回复队列和ID 41 AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); 42 // 指定回复队列和回复correlateId 43 builder.replyTo(replyQueue).correlationId(corrID); 44 AMQP.BasicProperties properties = builder.build(); 45 for (int i = 0; i < 2; i++) { 46 channel.basicPublish("exchange_rpc", "rpc", properties, 47 (System.currentTimeMillis() + "-rpc发送消息1").getBytes()); 48 } 49 50 AMQP.BasicProperties.Builder builder2 = new AMQP.BasicProperties.Builder(); 51 // 指定回复队列,未指定回复correlateId 52 builder2.replyTo(replyQueue); 53 AMQP.BasicProperties properties2 = builder2.build(); 54 for (int i = 0; i < 2; i++) { 55 channel.basicPublish("exchange_rpc", "rpc", properties2, 56 (System.currentTimeMillis() + "-rpc发送消息2").getBytes()); 57 } 58 59 for (int i = 0; i < 2; i++) { 60 // 未指定回复队列和correlateId 61 channel.basicPublish("exchange_rpc", "rpc", null, (System.currentTimeMillis() + "-rpc发送消息3").getBytes()); 62 } 63 64 DefaultConsumer c = new DefaultConsumer(channel) { 65 //这是一个回调函数,客户端获取消息,就调用此方法,处理消息 66 @Override 67 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) 68 throws IOException { 69 if (corrID.equals(properties.getCorrelationId())) { 70 System.out.println("correlationID对应上的消息:" + new String(body)); 71 } else { 72 System.out.println("correlationID未对应上的消息:" + new String(body)); 73 } 74 } 75 }; 76 channel.basicConsume(replyQueue, true, c); 77 } 78 79 }
消费端,也就是服务器端代码
1 package test; 2 3 import java.io.IOException; 4 5 import com.rabbitmq.client.AMQP; 6 import com.rabbitmq.client.Channel; 7 import com.rabbitmq.client.DefaultConsumer; 8 import com.rabbitmq.client.Envelope; 9 import com.rabbitmq.client.AMQP.BasicProperties; 10 11 import utils.ChannelUtils; 12 13 public class RPCServer { 14 15 public static void main(String[] args) throws IOException { 16 RPCServer rpcServer = new RPCServer(); 17 rpcServer.Server(); 18 } 19 20 public void Server() throws IOException { 21 final Channel channel = ChannelUtils.getChannelInstance("server"); 22 23 DefaultConsumer c = new DefaultConsumer(channel) { 24 25 //这是一个回到函数,服务器端获取到消息,就会调用此方法处理消息 26 @Override 27 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) 28 throws IOException { 29 System.out.println(new String(body)); 30 31 AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); 32 //我们在将要回复的消息属性中,放入从客户端传递过来的correlateId 33 builder.correlationId(properties.getCorrelationId()); 34 AMQP.BasicProperties prop = builder.build(); 35 36 //发送给回复队列的消息,exchange="",routingKey=回复队列名称 37 //因为RabbitMQ对于队列,始终存在一个默认exchange="",routingKey=队列名称的绑定关系 38 channel.basicPublish("", properties.getReplyTo(), prop, (new String(body) + "-回复").getBytes()); 39 40 } 41 }; 42 channel.basicConsume("queue_rpc", true, c); 43 } 44 45 }
附录:ChannelUtils 的源码
package utils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class ChannelUtils { // AMQP的连接其实是对Socket做的封装, 注意以下AMQP协议的版本号,不同版本的协议用法可能不同。 public static Channel getChannelInstance(String ConnectionDescription) { try { ConnectionFactory connectionFactory = getConnectionFactory(); Connection connection = connectionFactory.newConnection(ConnectionDescription); return connection.createChannel(); } catch (Exception e) { throw new RuntimeException("获取Channel连接失败"); } } public static ConnectionFactory getConnectionFactory() { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.1.111"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("drs"); connectionFactory.setPassword("123456"); return connectionFactory; } }