使用RabbitMQ的RPC
笔者不才,根据小弟的经验觉得使用rabbitMQ进行RPC调研不太妥当,需要他能够实现跨语言,但是对于整体来说使用消息队列服务进行RPC调用,通过RabbitMQ的事务来确定消息已经成功处理完毕,然后通过消息队列服务的reply队列返回处理结果。总觉得差点什么,或者你跟我一样发现了一些问题。第一如何处理分布式事务,这个的确有点费解,这个后面在spring和JPA的时候再去说吧。第二个问题也是我还没有弄懂的一个问题,就是如何做到多线程并发处理。
为什么我会提出这个问题,因为根据rabbitMQ的消息队列处理机制当中,消息队列在消息没有ack之前他是不会继续分发消息的,所以同一时间内你只能处理一条消息,也代表你处理的消息是线性的。再看WEB程序,大家都清楚在Servlet 被调用的时候会新开一条线程,然后在独立的线程去调用serivce 或 dao 等等的业务,也证明了WEB服务是多线程去处理的,而非线性的,总不能一个人在调用这个链接之后,下一个用户也调用这个链接就卡住那里等着吧?所以对于消息队列这种线性的处理方式,我们应该怎么去做到并发呢?方法有能多种,设置成no_ack 然后每次接收到消息之后开启性的线程去处理。第二种方法也是比较简单的开多几个channel,这样就形成多个消费者了。不过以上两个想法,只是我想象而已,也没有实践过,不过今天在写这个东西的时候,我打算实践一下。而且我会在这个笔记当中记录RabbitMQ 的RPC 调用方式。
本人参考的文档为官网关于RPC的介绍:
http://www.rabbitmq.com/tutorials/tutorial-six-java.html
本次我开发所使用的语言为JAVA,在动手写代码之前先说明几个重点问题。
在RabbitMQ 实现RPC的原理
在rabbitMQ上实现RPC,与之前的订阅消息和生产消息非常相似,唯一不一样的是RPC需要消息处理的放回结果,当然有时你不需要结果,你只在乎是否执行成功而已,但是这里所涉及的问题是我们怎样能做到当消息发送到队列之后,开始等待直到消息处理完成后返回处理结果后再进行执行其他处理。
引用一下官方的图,真心画的不错。
在到这个图基本上都清晰了。
1. client发起消息,然后带上一个唯一的correlation_id,同时在reply_to中提供一个独立的队列。官方一般建议使用默认独立。后面会有代码~
2. 携带好correlation_id 和 reply_to 之后将消息发布。
3. 然后server 获得消息,并处理消息。
4. 得出的处理结果通过 reply_to 中的队列 返回消息 并携带上 消息中携带的的correlation_id。
5. client 监听 reply_to 队列,当获得消息后,判断消息中correlation_id 是否与请求时发出的一致,如果一致就证明该消息是这个业务的处理结果。如果不一致就证明消息不是你的啦~ 别碰人家的东西!
贴出官方的summary:
- When the Client starts up, it creates an anonymous exclusive callback queue.【当Client运行时,创建一个匿名的 独立的 回调队列】
- For an RPC request, the Client sends a message with two properties: replyTo, which is set to the callback queue and correlationId, which is set to a unique value for every request. 【client 发送的消息 会携带两个参数是为了 RPC调用的,replyTo 是设置回调的队列,correlation 是 唯一的数值,不同的请求就不同的correlationID】
- The request is sent to an rpc_queue queue. [发送消息到队列当中]
- The RPC worker (aka: server) is waiting for requests on that queue. When a request appears, it does the job and sends a message with the result back to the Client, using the queue from the replyTo field.【worker 即是服务器 等待队列中的消息,当请求出现时,开始干活同时通过replyTo字段中的队列发送处理结果】
- The client waits for data on the callback queue. When a message appears, it checks the correlationId property. If it matches the value from the request it returns the response to the application.【client 等待数据返回的队列,当有消息出现时,检查corrleationID 参数,如果和之前发出的correlationid吻合,就将结果返回到应用程序】
实现代码如下
Client 如下:
package com.maxfunner.rpc;
import com.rabbitmq.client.*;
import org.apache.commons.lang.SerializationUtils;
import java.io.IOException;
import java.util.UUID;
/**
* Created by Tony on 2016/11/3.
*/
public class Client {
public String sayHelloToServer(String username) throws IOException, InterruptedException {
String exchangeName = "rpc_exchange"; //交换器名称
String queueName = "rpc_queue"; //队列名称
String routingKey = "rpc_key"; //路由键
ConnectionFactory factory = new ConnectionFactory();
factory.setVirtualHost("test");
factory.setHost("192.168.0.21");
factory.setPort(5673);
factory.setUsername("tony");
factory.setPassword("tonypwd"); //创建链接
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchangeName, "direct", false, false, null); //定义交换器
channel.queueDeclare(queueName, false, false, false, null); //定义队列
channel.queueBind(queueName, exchangeName, routingKey, null); //绑定队列
String callbackQueue = channel.queueDeclare().getQueue(); //获得匿名的 独立的 默认队列
String correlationId = UUID.randomUUID().toString(); //产生一个 关联ID correlationID
QueueingConsumer consumer = new QueueingConsumer(channel); // 创建一个消费者对象
channel.basicConsume(callbackQueue,true,consumer); //消费消息
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder()