作者 | 乔宇
杏仁后端工程师,关注服务端技术。
背景知识
RabbitMQ
RabbitMQ 是基于 AMQP 协议实现的一个消息队列(Message Queue),Message Queue 是一个典型的生产者/消费者模式。生产者发布消息,消费者消费消息,生产者和消费者之间是解耦的,互相不知道对方的存在。
RPC
Remote Procedure Call:远程过程调用,一次远程过程调用的流程即客户端发送一个请求到服务端,服务端根据请求信息进行处理后返回响应信息,客户端收到响应信息后结束。
如何使用 RabbitMQ 实现 RPC?
使用 RabbitMQ 实现 RPC,相应的角色是由生产者来作为客户端,消费者作为服务端。
但 RPC 调用一般是同步的,客户端和服务器也是紧密耦合的。即客户端通过 IP/域名和端口链接到服务器,向服务器发送请求后等待服务器返回响应信息。
但 MQ 的生产者和消费者是完全解耦的,那么如何用 MQ 实现 RPC 呢?很明显就是把 MQ 当作中间件实现一次双向的消息传递:
客户端和服务端即是生产者也是消费者。客户端发布请求,消费响应;服务端消费请求,发布响应。
具体实现
MQ部分的定义
请求信息的队列
我们需要一个队列来存放请求信息,客户端向这个队列发布请求信息,服务端消费该队列处理请求。该队列不需要复杂的路由规则,直接使用 RabbitMQ 默认的 direct exchange 来路由消息即可。
响应信息的队列
存放响应信息的队列不应只有一个。如果存在多个客户端,不能保证响应信息被发布请求的那个客户端消费到。所以应为每一个客户端创建一个响应队列,这个队列应该由客户端来创建且只能由这个客户端使用并在使用完毕后删除,这里可以使用 RabbitMQ 提供的排他队列(Exclusive Queue):
channel.queueDeclare(queue:"", durable:false, exclusive:true, autoDelete:false, new HashMap<>())
并且要保证队列名唯一,声明队列时名称设为空 RabbitMQ 会生成一个唯一的队列名。
exclusive
设为true
表示声明一个排他队列,排他队列的特点是只能被当前的连接使用,并且在连接关闭后被删除。
一个简单的 demo(使用 pull 机制)
我们使用一个简单的 demo 来了解客户端和服务端的处理流程。
发布请求
编写代码前的一个小问题
我们在声明队列时为每一个客户端声明了独有的响应队列,那服务器在发布响应时如何知道发布到哪个队列呢?其实就是客户端需要告诉服务端将响应发布到哪个队列,RabbitMQ 提供了这个支持,消息体的
Properties
中有一个属性reply_to
就是用来标记回调队列的名称,服务器需要将响应发布到reply_to
指定的回调队列中。
解决了这个问题之后我们就可以编写客户端发布请求的代码了:
// 定义响应回调队列
String replyQueueName = channel.queueDeclare("", false, true, false, new HashMap<>()).getQueue();
// 设置回调队列到 Properties
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() .replyTo(replyQueueName) .build();
String request = "request";
// 发布请求
channel.basicPublish("",