在RabbitMQ中实现RPC远程调用是比较容易的。常见的用法是监听客户端发起的请求在服务器处理之后响应回去结果信息.(客户端发送请求消息和服务器响应消息)。为了接收响应,我们需要发送请求的“回调”队列地址。我们可以使用默认的队列(这在java客户端专属)。客户端回调队列得到响应消息的写法如下:
public String call(String message) throws IOException, InterruptedException {
String corrId = UUID.randomUUID().toString();
AMQP.BasicProperties props = new AMQP.BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
//发送请求
channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
//获取响应消息
channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
if (properties.getCorrelationId().equals(corrId)) {
response.offer(new String(body, "UTF-8"));
}
}
});
return response.take();
}
AMQP 0-9-1协议预定义了一套消息的14个属性。除下列外,大多数属性很少使用.
deliveryMode:标志着一个消息为持久性状态(值为2)或瞬态(任何其他值)。
contentType:用来描述编码的MIME类型。比如application/json.
replyTo:回调队列的名字
correlationId:关于RPC请求响应的时候有用到,对于在创建一个单一回调队列来响应每个客户端的请求作为一个标识id.
为每个RPC请求都创建一个回调队列,这在性能方面讲是非常低效的,我们可以使用correlationId来标识每个客户端请求,使用单一的回调队列即可.
为每个RPC请求设置唯一的correlationId值,当我们在回调队列中接收消息时,我们将查看correlationId值,并基于此值,我们将能够与请求匹配响应。如果我们看到一个未知的correlationid值,可以安全地忽略信息-它不属于我们的请求。
看一下官方给的示意图:
JAVAAPI的完整代码如下:
package yzr.main;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RPCServer {
private static final String RPC_QUEUE_NAME = "rpc_queue";
//远程服务器的方法
private static int fib(int n) {
if (n ==0) return 0;
if (n == 1) return 1;
return fib(n-1) + fib(n-2);
}
public static void main(String[] argv) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = null;
try {
connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
//channel.basicQos(1);
System.out.println(" [x] Awaiting RPC requests");
//获取客户端请求
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
AMQP.BasicProperties replyProps = new AMQP.BasicProperties
.Builder()
.correlationId(properties.getCorrelationId())
.build();
String response = "";
try {
/*
* 模拟业务 逻辑
*/
String message = new String(body,"UTF-8");
int n = Integer.parseInt(message);
System.out.println(" [.] fib(" + message + ")");
response += fib(n);
}
catch (RuntimeException e){
System.out.println(" [.] " + e.toString());
}
finally {
//响应消息
channel.basicPublish( "", properties.getReplyTo(), replyProps, response.getBytes("UTF-8"));
//确定消费ack消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//获取来自客户端的消息
channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
//loop to prevent reaching finally block
//轮询客户端的请求
while(true) {
try {
Thread.sleep(100);
} catch (InterruptedException _ignore) {}
}
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
finally {
if (connection != null)
try {
connection.close();
} catch (IOException _ignore) {}
}
}
}
package yzr.main;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
public class RPCClient {
private Connection connection;
private Channel channel;
//发送请求的队列
private String requestQueueName = "rpc_queue";
private String replyQueueName;
public RPCClient() throws IOException, TimeoutException {
/*
* 初始化连接对象
*/
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
connection = factory.newConnection();
channel = connection.createChannel();
//创建获取响应消息的队列
replyQueueName = channel.queueDeclare().getQueue();
}
public String call(String message) throws IOException, InterruptedException {
String corrId = UUID.randomUUID().toString();
AMQP.BasicProperties props = new AMQP.BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
//发送请求
channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
//获取响应消息
channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
if (properties.getCorrelationId().equals(corrId)) {
response.offer(new String(body, "UTF-8"));
}
}
});
return response.take();
}
public void close() throws IOException {
connection.close();
}
public static void main(String[] argv) {
RPCClient fibonacciRpc = null;
String response = null;
try {
fibonacciRpc = new RPCClient();
System.out.println(" [x] Requesting fib(30)");
response = fibonacciRpc.call("30");
System.out.println(" [.] Got '" + response + "'");
}
catch (IOException | TimeoutException | InterruptedException e) {
e.printStackTrace();
}
finally {
if (fibonacciRpc!= null) {
try {
fibonacciRpc.close();
}
catch (IOException _ignore) {}
}
}
}
}
RabbitMq使用总结:
像RabbitMQ这样的消息代理可用来模拟不同的场景,例如点对点的消息分发或者订阅/推送。我们的程序足够简单,有两个基本的组件,一个生产者用于产生消息,还有一个消费者用来使用产生的消息.
不管是生产者还是消费者, 连接队列的代码都是一样的,使用一个抽象类EndPoint封装起来,这样可以通用一些,减少重复代码。
package yzr.base.rabbitmq;
import java.io.IOException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public abstract class EndPoint{
protected Channel channel;
protected Connection connection;
protected String endPointName;
public EndPoint(String endpointName) throws Exception{
this.endPointName = endpointName;
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(endpointName, false, false, false, null);
}
/**
* 关闭channel和connection。并非必须,因为隐含是自动调用的。
* @throws IOException
*/
public void close() throws Exception{
this.channel.close();
this.connection.close();
}
}
生产者类的任务是向队列里写一条消息。我们使用Apache Commons Lang把可序列化的Java对象转换成 byte 数组。
package yzr.base.rabbitmq;
import java.io.Serializable;
import org.apache.commons.lang.SerializationUtils;
/*
* 生产者
*/
public class Producer extends EndPoint{
public Producer(String endPointName) throws Exception{
super(endPointName);
}
public void sendMessage(Serializable object) throws Exception {
channel.basicPublish("",endPointName, null, SerializationUtils.serialize(object));
}
}
消费者可以以线程方式运行,对于不同的事件有不同的回调函数,其中最主要的是处理新消息到来的事件
package yzr.base.rabbitmq;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.SerializationUtils;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.ShutdownSignalException;
/**
* 读取队列的程序端,实现了Runnable接口。
*
*/
public class QueueConsumer extends EndPoint implements Runnable, Consumer{
public QueueConsumer(String endPointName) throws Exception{
super(endPointName);
}
public void run() {
try {
//start consuming messages. Auto acknowledge messages.
channel.basicConsume(endPointName, true,this);
} catch (Exception e) {
e.printStackTrace();
}
}
public void handleConsumeOk(String consumerTag) {
System.out.println("Consumer "+consumerTag +" registered");
}
public void handleDelivery(String consumerTag, Envelope env,
BasicProperties props, byte[] body) {
Map map = (HashMap)SerializationUtils.deserialize(body);
System.out.println("Message Number "+ map.get("message number") + " received.");
}
public void handleCancel(String consumerTag) {}
public void handleCancelOk(String consumerTag) {}
public void handleRecoverOk(String consumerTag) {}
public void handleShutdownSignal(String consumerTag, ShutdownSignalException arg1) {}
}
测试:
package yzr.unit.rabbitmq;
import java.util.HashMap;
import yzr.base.rabbitmq.Producer;
public class test {
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) {
Producer producer;
try {
producer = new Producer("queueName");
for (int i = 0; i < 10; i++) {
HashMap message = new HashMap();
message.put("message number", i);
producer.sendMessage(message);
System.out.println("Message Number "+ i +" sent.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package yzr.unit.rabbitmq;
import yzr.base.rabbitmq.QueueConsumer;
public class test {
public static void main(String[] args) {
QueueConsumer consumer;
try {
consumer = new QueueConsumer("queueName");
Thread consumerThread = new Thread(consumer);
consumerThread.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
参考链接: http://www.oschina.net/translate/getting-started-with-rabbitmq-in-java?cmp