原文:RabbitMQ(四):RPC的实现

一、RPC

  RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。有很多方式可以实现,譬如UNIX RPC、REST API、WCF和SOAP。这些传统的RPC实现方法有共同之处:那就是客户端和服务器端紧密相连,客户端直接连接上服务器,发送一个请求,然后就停下来等待服务器的应答。

  这种点对点的性质模式有很多好处,它使得在小范围内的拓扑变得简单。但是当有众多服务器的时候,客户端如何发现在那台服务器上可以找到其他想要的服务就变的麻烦,SOAP和大多数的企业RPC已经采用复杂的补充协议和服务目录,但也带来了额外的复杂度和众多故障点。

  但是,用RabbitMQ来实现RPC可以无需关心由那台服务器来处理,也不必担心服务器奔溃,只需要简单的发送消息,然后等待响应即可。一般接触RabbitMQ的都是用发后即忘模型,用于发送邮件等通知或者处理其他并行处理事件,也就是AMQP的消息是单向的。如何才能让服务器将处理结果返回给原始的客户端呢?

二、消息应答和私有队列

  RabbitMQ有一个优雅的解决方案:使用消息来发回应答。在每个AMQP消息头里有个字段reply_to.消息的生产者可以通过该字段来确定队列的名称,并监听应答队列等待应答。然后接收消息的RPC服务器能偶检查reply_to字段,并创建包含应答内容的新的消息,并以队列名称为路由键,通过应答队列将处理结果发回给生产者。这里我们不需要创建应答队列的名字也不需要将应答队列绑定到交换器上,这是因为没有声明队列的名称RabbitMQ会自动申明,消息发布到RabbitMQ在没有指名交换器的时候,RabbitMQ就会让位目的地是应答队列,而路由键就是应答队列名称。

  所以RabbitMQ实现RPC需要比一般的消息通信多以下几个步骤:

  1. 生产者创建一个应答队列,并监听该队列。
  2. 生产者为消息头中的Reply_to和CorrelationId字段赋值。reply_to是应答队列的名称,CorrelationId是相关标识由消费者返回后对比确认是返回我们的结果。
  3. 消费者返回生产者发送的消息头,并且不需要绑定交换器,并将Reply_to参数作为路由键发送消息到应答队列。

三、自己实现简单的RPC

  其实简单的讲就是生产者在发送消息后接收消息,消费者在接受消息后发送消息,生产者多了一步接收处理消息,消费者多了一步发送消息。我这里简化了一些操作,争取用最少的代码实现,具体代码如下:

  生产者:

private static void MySelfRPCProducer()
{
var conn_factory = new ConnectionFactory(){HostName = "localhost",UserName = "guest",Password = "guest",Port = };
using (var conn = conn_factory.CreateConnection())
{
using (var channel = conn.CreateModel())
{
IBasicProperties pro = channel.CreateBasicProperties();
pro.ReplyTo = channel.QueueDeclare().QueueName;//创建应答队列并返回队列名称,这个方法创建的队列exclusive和auto_delete都是true,这样可以确保没有人能窃取信息
pro.ContentType = "text/plain";
string corrId = Guid.NewGuid().ToString();
pro.CorrelationId = corrId; channel.BasicPublish("", "rpc_queue", pro, Encoding.UTF8.GetBytes("小黄"));
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (ea, ch) =>
{
//比较CorrelationId确认是返回的我们的消息
if (ch.BasicProperties.CorrelationId == corrId)
{
//处理返回结果
string msg = Encoding.UTF8.GetString(ch.Body);
Console.WriteLine(msg);
}
};
string consumer_tag = channel.BasicConsume(pro.ReplyTo, true, consumer);//监听应答队列
channel.BasicCancel(consumer_tag);
}
}
Console.ReadLine();
}

  消费者:

private static void MySelfRPCCousmer()
{
var conn_factory = new ConnectionFactory(){HostName = "localhost",UserName = "guest",Password = "guest",Port = };
using (var conn = conn_factory.CreateConnection())
{
using (var channel = conn.CreateModel())
{
channel.QueueDeclare("rpc_queue", false, false, false, null);
var consumer = new EventingBasicConsumer(channel);
channel.BasicQos(, , false);
consumer.Received += (ea, ch) =>
{
string msg = Encoding.UTF8.GetString(ch.Body);
Console.WriteLine("接收到消息:" + msg);
//发送处理结果
channel.BasicPublish("", ch.BasicProperties.ReplyTo, ch.BasicProperties, Encoding.UTF8.GetBytes(msg + "给我回电话了"));
channel.BasicAck(ch.DeliveryTag, false);
};
string consumer_tag = channel.BasicConsume("rpc_queue", false, consumer);
Console.ReadLine();//这里先停止运行下面的代码,因为需要持续监听,信道断开就监听不了了
channel.BasicCancel(consumer_tag);
}
}
}

四、RabbitMQ封装好的RPC

  其实RabbitMQ已经封装好了RPC相应的对象,分别是SimpleRpcClient和SimpleRpcServer。客户端在初始化SimpleRpcClient后主要可以通过Call方法发送消息并返回服务端处理结果。服务端的SimpleRpcServer内部定义了很多虚方法,具体的消息处理是我们自己决定的,所以需要继承SimpleRpcServer后实现相应方法,通过实现重写HandleSimpleCall方法可以返回给客户端数据。具体代码如下所示:

  客户端:

private static void RabbitMQRPCProducer()
{
var conn_factory = new ConnectionFactory() { HostName = "localhost", UserName = "guest", Password = "guest", Port = };
using (var conn = conn_factory.CreateConnection())
{
using (var channel = conn.CreateModel())
{
//创建client的rpc
SimpleRpcClient client = new SimpleRpcClient(channel, new PublicationAddress(exchangeType: ExchangeType.Direct, exchangeName: string.Empty, routingKey: "rpc_queue"));
bool flag = true;
var sendmsg = "";
while (flag)
{
Console.WriteLine("请输入要发送的消息");
sendmsg = Console.ReadLine();
if (string.IsNullOrWhiteSpace(sendmsg))
{
Console.Write("请输入消息");
continue;
}
var msg = client.Call(Encoding.UTF8.GetBytes(sendmsg));
Console.WriteLine(Encoding.UTF8.GetString(msg));
}
Console.ReadKey();
}
}
}

  服务端:

private static void RabbitMQRPCCousmer()
{ var conn_factory = new ConnectionFactory() { HostName = "localhost", UserName = "guest", Password = "guest", Port = };
using (var conn = conn_factory.CreateConnection())
{
//创建返回一个新的频道
using (var channel = conn.CreateModel())
{
channel.QueueDeclare("rpc_queue", false, false, false, null);//创建一个rpc queue
SimpleRpcServer rpc = new MySimpleRpcServer(new Subscription(channel, "rpc_queue"));
Console.WriteLine("服务端启动成功");
rpc.MainLoop(); Console.ReadKey();
}
}
}

  继承实现方法:

class MySimpleRpcServer : SimpleRpcServer
{
public MySimpleRpcServer(Subscription subscription) : base(subscription)
{
}
/// <summary>
/// 执行完成后进行回调
/// </summary>
public override byte[] HandleSimpleCall(bool isRedelivered, IBasicProperties requestProperties, byte[] body, out IBasicProperties replyProperties)
{
replyProperties = null;
return Encoding.UTF8.GetBytes($"给{Encoding.UTF8.GetString(body)}发送短信成功");
}
}

五、小结

  以上就是RabbitMQ对于RPC的最简单的实现,与大家共勉。

RabbitMQ(四):RPC的实现的更多相关文章

  1. 【RabbitMQ学习之二】RabbitMQ四种交换机模式应用

    环境 win7 rabbitmq-server-3.7.17 Erlang 22.1 一.概念1.队列队列用于临时存储消息和转发消息.队列类型有两种,即时队列和延时队列. 即时队列:队列中的消息会被立 ...

  2. RabbitMQ 实现RPC

    实现RPC 首先要弄明白,RPC是个什么东西. (RPC) Remote Procedure Call Protocol 远程过程调用协议 在一个大型的公司,系统由大大小小的服务构成,不同的团队维护不 ...

  3. RabbitMQ中RPC的实现及其通信机制

    RabbitMQ中RPC的实现:客户端发送请求消息,服务端回复响应消息,为了接受响应response,客户端需要发送一个回调队列的地址来接受响应,每条消息在发送的时候会带上一个唯一的correlati ...

  4. 基于RabbitMQ的Rpc框架

    参考文档:https://www.cnblogs.com/ericli-ericli/p/5917018.html 参考文档:RabbitMQ 实现RPC MQ的使用场景大概包括解耦,提高峰值处理能力 ...

  5. RabbitMQ 笔记-RPC

    RabbitMQ中实现RPC的机制是: 客户端发送请求(消息)时,在消息的属性(MessageProperties,在AMQP协议中定义了14中properties,这些属性会随着消息一起发送)中设置 ...

  6. rabbitmq (五)RPC

    Remote Procedure Call or RPC(远程函数调用) 当我们需要在远程计算机上运行一个函数,并且等待结果的时候,我们用到RPC 在rabbitmq客户端使用call函数,发送RPC ...

  7. RabbitMQ四种交换机类型介绍

    RabbitMQ  原文地址: https://baijiahao.baidu.com/s?id=1577456875919174629&wfr=spider&for=pc 最新版本的 ...

  8. RabbitMQ 四种Exchange

    AMQP协议中的核心思想就是生产者和消费者隔离,生产者从不直接将消息发送给队列.生产者通常不知道是否一个消息会被发送到队列中,只是将消息发送到一个交换机.先由Exchange来接收,然后Exchang ...

  9. RabbitMQ、RPC、SaltStack "贡"具的使用

    消息队列 使用队列的场景 在程序系统中,例如外卖系统,订单系统,库存系统,优先级较高 发红包,发邮件,发短信,app消息推送等任务优先级很低,很适合交给消息队列去处理,以便于程序系统更快的处理其他请求 ...

随机推荐

  1. 浅谈Java——泛型DAO

    首先解释一下为什么要学习泛型DAO.平时在写DAO的时候是一个接口对应一个实现类,实现类里面要写很多的操作数据库的方法.当我们有很多的javaben的时候我们会写很多的接口和实现类,并且里面的代码都是 ...

  2. 如何将同一 VNET 下的虚拟机从经典部署模型迁移到 Azure Resource Manager

    本文内容 适用场景 解决方案 适用场景 用户拥有多个云服务但是在同一个 VNET 下,希望将这些虚拟机从经典部署模型(以下简称:ASM)迁移到 Azure Resource Manager(以下简称: ...

  3. [翻译] RAReorderableLayout

    RAReorderableLayout A UICollectionView layout which you can move items with drag and drop. 一种UIColle ...

  4. 搭建企业级NFS网络文件共享服务[二]

    1.1.8 NFS问题总结 1.问:使用showmount -e 127.0.0.1后报clnt_create: RPC: Program not registered错误 答:顺序不对,重启nfs服 ...

  5. 应用程序-特定 权限设置并未向在应用程序容器 不可用 SID (不可用)中运行的地址 LocalHost (使用 LRPC) 中的用户

    这是安装biztalk server 2013出现的问题,很多天了没解决,下边这个解决办法也搜到过类似的,但上次实验时出现设置组件权限时发现都是按钮都是灰的,无法操作. 这次设置好了.谢谢ibg. 文 ...

  6. 更改Request Parameters中的值

    1. 定义ParameterRequestWrapper 继承HttpServletRequestWrapper public class ParameterRequestWrapper extend ...

  7. ORACLE默认实例设置--linux

    数据库实例多了之后,每次export的时候,显示的ORACLE_SID总不是自己经常用的那个,要是能让默认的自定义就好了. 现在就解释一下在linux环境中如何定义: 1.su - oracle 2. ...

  8. 4种Java日志管理方法

    java开发中常见的几种日志管理方案有以下4种: 1. Commons-logging + log4j 2. log4j 3. slf4j + log4j + commmons-logging 4. ...

  9. Array.prototype.reduce 的理解与实现

    Array.prototype.reduce 是 JavaScript 中比较实用的一个函数,但是很多人都没有使用过它,因为 reduce 能做的事情其实 forEach 或者 map 函数也能做,而 ...

  10. python第一课——关于python的一些概念

    day01(上午): 1.学习方法(建议): 1).不要依赖于我的视频,绝对不要晚上将视频全部在过一遍 2).上课不要记笔记,而且不要用纸质的笔记本去整理笔记 3).不要只看不敲,代码方面我们需要做到 ...