什么RPC?

这一段是从度娘摘抄的。

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。

RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

Callback Queue

之前在做学习笔记(二)工作队列的例子的时候,我们只是发送了任务给消息消费者实例去完成,但是对于消息生产者,它并不知道任务是否完成,所以这里缺少一个回调方法。

既然我们使用RabbitMQ, 所以很容易想到的就是我们可以互换一下消息消费者和消息生产者的角色,当消费生产者完成任务之后,通过创建一个新的消息队列,将完成任务的消息,发送给消息生产者。

那么如何让消息消费者,知道消费生产者关注的消息队列呢?

在我们发布消息的时候,会调用channel对象的BasicPublish方法,这个方法中有一个IBasicProperties的参数basicProperties

在这对象中,有一个ReplyTo属性,我们可以将生产者监听的消息队列名称存放在里面。当消费者程序接收到这条消息的时候,就可以在Receive事件的ea对象中获取ReplyTo属性的值

var props = channel.CreateBasicProperties();

props.ReplyTo = replyQueueName;

 

var messageBytes = Encoding.UTF8.GetBytes(message);

channel.BasicPublish(exchange: "",

                     routingKey: "rpc_queue",

                     basicProperties: props,

                     body: messageBytes);

Correlation Id

那么当消息生产者接收到消息消费者任务完成的消息之后,该如何确定完的是哪一个任务呢?

在现实情况,消息生产者通常会发出多个任务,多个消息消费者分别进行不同的任务,这时候我们就需要知道是哪个消息消费者完成了任务。

当消息生产者调用channel对象的BasicPublish方法发送消息时,IBasicProperties对象除了可以帮助我们传递消息生产者监听的消息队列名,还可以帮我们传递一个CorrelationId(相关Id),当发送任务消息的时候,我们给每个任务消息定义一个唯一的相关Id, 并存储在IBasicProperties对象的CorrelationId属性中。

var properties = channel.CreateBasicProperties();

properties.ReplyTo = replyQueueName;

properties.CorrelationId = Guid.NewGuid().ToString();

这样消息消费者在接收到任务消息时,可以从Receive的ea参数中获取CorrelationId。当任务完成时,再将保存有这个CorrelationId的任务完成消息发送到消息生产者关注的消息队列中, 消息生产者就可以知道是哪个任务完成了

手动实现RabbitMQ的RPC

Send

static void Main(string[] args)

        {

            var factory = new ConnectionFactory()

            {

                HostName = "localhost"

            };

 

            using (var connection = factory.CreateConnection())

            {

                using (var channel = connection.CreateModel())

                {

                    var replyQueueName = channel.QueueDeclare().QueueName;

                    var consumer = new EventingBasicConsumer(channel);

                    channel.BasicConsume(queue: replyQueueName,

                                         autoAck: true,

                                         consumer: consumer);

 

                    channel.QueueDeclare(queue: "manual_rpc_queue",

                        durable: true,

                        exclusive: false,

                        autoDelete: false,

                        arguments: null);

 

                    string message = args[0];

                    var body = Encoding.UTF8.GetBytes(message);

 

                    var properties = channel.CreateBasicProperties();

                    properties.ReplyTo = replyQueueName;

                    properties.CorrelationId = Guid.NewGuid().ToString();

 

                    consumer.Received += (m, ea) =>

                    {

                        if (ea.BasicProperties.CorrelationId == properties.CorrelationId)

                        {

                            Console.WriteLine($"Task {properties.CorrelationId} completed.");

                            Console.WriteLine($"{Encoding.UTF8.GetString(ea.Body)}");

 

                            Environment.Exit(1);

                        }

 

                    };

 

                    channel.BasicPublish(exchange: "",

                        routingKey: "manual_rpc_queue",

                        basicProperties: properties,

                        body: body

                    );

 

                    Console.WriteLine("[x] Sent {0}", message);

                    Console.Read();

                }

            }

        }

Receive

static void Main(string[] args)

        {

            var factory = new ConnectionFactory() { HostName = "localhost" };

 

            using (var connection = factory.CreateConnection())

            {

                using (var channel = connection.CreateModel())

                {

                    channel.QueueDeclare(queue: "manual_rpc_queue",

                        durable: true,

                        exclusive: false,

                        autoDelete: false,

                        arguments: null);

 

                    channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);

 

                    var consumer = new EventingBasicConsumer(channel);

 

                    consumer.Received += (model, ea) =>

                    {

                        var body = ea.Body;

                        var message = Encoding.UTF8.GetString(body);

 

                        Thread.Sleep(4000);

                        Console.WriteLine("[x] Received {0}", message);

 

                        var prop = channel.CreateBasicProperties();

                        prop.CorrelationId = ea.BasicProperties.CorrelationId;

 

                        channel.BasicPublish(

                            exchange: "",

                            routingKey: ea.BasicProperties.ReplyTo,

                            mandatory: false,

                            basicProperties: prop,

                            body: Encoding.UTF8.GetBytes($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} Task {prop.CorrelationId} Completed."));

 

                        channel.BasicAck(ea.DeliveryTag, false);

                    };

 

                    channel.BasicConsume(queue: "manual_rpc_queue", autoAck: false, consumer: consumer);

 

                    Console.Read();

                }

            }

        }

最终效果

RabbitMQ提供的RPC实现

RabbitMQ提供了一个SimpleRpcClient类和SimpleRpcServer类,来简化我们的代码,代码不难理解

Send

static void Main(string[] args)

        {

            var factory = new ConnectionFactory()

            {

                HostName = "localhost"

            };

 

            using (var connection = factory.CreateConnection())

            {

                using (var channel = connection.CreateModel())

                {

                    SimpleRpcClient client = new SimpleRpcClient(channel, new PublicationAddress(exchangeType: ExchangeType.Direct, exchangeName: string.Empty, routingKey: "RpcQueue"));

 

                    var prop = channel.CreateBasicProperties();

                    prop.CorrelationId = Guid.NewGuid().ToString();

                    IBasicProperties outProp;

 

                    var msg = client.Call(prop, Encoding.UTF8.GetBytes(args[0]), out outProp);

 

                    if (prop.CorrelationId == outProp.CorrelationId)

                    {

                        Console.WriteLine($"Task {prop.CorrelationId} completed.");

                        Console.WriteLine(Encoding.UTF8.GetString(msg));

                    }

                }

            }

        }

Receive

  1. 创建MySimpleRpcServer类,继承自SimpleRpcServer类
  2. HandleSimpleCall方法里添加回调返回值
  3. ProcessRequest方法为任务处理方法

MySimpleRpcServer.cs

public class MySimpleRpcServer : SimpleRpcServer

    {

        public MySimpleRpcServer(Subscription subscription) : base(subscription)

        {

 

        }

 

        public override byte[] HandleSimpleCall(bool isRedelivered, IBasicProperties requestProperties, byte[] body, out IBasicProperties replyProperties)

        {

            replyProperties = null;

            return Encoding.UTF8.GetBytes($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} Task {requestProperties.CorrelationId} Completed.");

        }

 

        /// <summary>

        /// 进行处理

        /// </summary>

        /// <param name="evt"></param>

        public override void ProcessRequest(BasicDeliverEventArgs evt)

        {

            Console.WriteLine("[x] Received {0}", Encoding.UTF8.GetString(evt.Body));

            Thread.Sleep(4000);

            base.ProcessRequest(evt);

        }

    }

Program.cs

static void Main(string[] args)

        {

            var factory = new ConnectionFactory() { HostName = "localhost" };

 

            using (var connection = factory.CreateConnection())

            {

                using (var channel = connection.CreateModel())

                {

                    channel.QueueDeclare("RpcQueue", true, false, false, null);

 

                    SimpleRpcServer rpc = new MySimpleRpcServer(new Subscription(channel, "RpcQueue"));

 

                   

                    rpc.MainLoop();

                    Console.ReadKey();

                }

            }

        }

RabbitMQ学习笔记(六) RPC的更多相关文章

  1. rabbitMQ学习笔记(七) RPC 远程过程调用

    关于RPC的介绍请参考百度百科里的关于RPC的介绍:http://baike.baidu.com/view/32726.htm#sub32726 现在来看看Rabbitmq中RPC吧!RPC的工作示意 ...

  2. rabbitMQ学习笔记(六) topic类型消息。

    上一节中使用了消息路由,消费者可以选择性的接收消息. 但是这样还是不够灵活. 比如某个消费者要订阅娱乐新闻消息 . 包括新浪.网易.腾讯的娱乐新闻.那么消费者就需要绑定三次,分别绑定这三个网站的消息类 ...

  3. RabbitMQ学习笔记六:RabbitMQ之消息确认

    使用消息队列,必须要考虑的问题就是生产者消息发送失败和消费者消息处理失败,这两种情况怎么处理. 生产者发送消息,成功,则确认消息发送成功;失败,则返回消息发送失败信息,再做处理. 消费者处理消息,成功 ...

  4. 官网英文版学习——RabbitMQ学习笔记(一)认识RabbitMQ

    鉴于目前中文的RabbitMQ教程很缺,本博主虽然买了一本rabbitMQ的书,遗憾的是该书的代码用的不是java语言,看起来也有些不爽,且网友们不同人学习所写不同,本博主看的有些地方不太理想,为此本 ...

  5. # go微服务框架kratos学习笔记六(kratos 服务发现 discovery)

    目录 go微服务框架kratos学习笔记六(kratos 服务发现 discovery) http api register 服务注册 fetch 获取实例 fetchs 批量获取实例 polls 批 ...

  6. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  7. Learning ROS for Robotics Programming Second Edition学习笔记(六) indigo xtion pro live

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  8. RabbitMQ学习笔记(五) Topic

    更多的问题 Direct Exchange帮助我们解决了分类发布与订阅消息的问题,但是Direct Exchange的问题是,它所使用的routingKey是一个简单字符串,这决定了它只能按照一个条件 ...

  9. Typescript 学习笔记六:接口

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  10. RabbitMQ学习笔记1-hello world

    安装过程略过,一搜一大把. rabbitmq管理控制台:http://localhost:15672/   默认账户:guest/guest RabbitMQ默认监听端口:5672 JAVA API地 ...

随机推荐

  1. jedis keys和scan操作

    关于redis的keys命令的性能问题 KEYS pattern 查找所有符合给定模式 pattern 的 key . KEYS * 匹配数据库中所有 key . KEYS h?llo 匹配 hell ...

  2. 理解WindowManagerService

    --摘自<Android进阶解密> *WMS的职责* 1)窗口管理 WMS负责窗口的启动.添加和删除,另外窗口的大小和层级也是由WMS进行管理的 2)窗口动画 WMS的动画子系统Windo ...

  3. Interpreting NotifyCollectionChangedEventArgs zz

    If you’ve ever consumed INotifyCollectionChanged.CollectionChanged, then you’ve run into some inadeq ...

  4. 【省选十连测之一】【线段树】【最小生成树之Kruskal】公路建设

    目录 题意 输入格式 输出格式 数据范围 思路 代码 题意 有n个点,m条双向道路,其中第条公路的两个端点是u[i],v[i],费用是c[i]. 现在给出q个询问,每次给定一个L和一个R,要求你只能够 ...

  5. photoshop改变图片大小,不改变像素

    用画图修改了图片像素,360*440 但是图片30K,要求图片20K 打开photoshop,打开图片,点击文件--存储为web所用格式,调节品质大小到20K以下,保存即可

  6. WebBrowser加载一个URL被多次调用DocumentCompleted 的问题解决方案<转>

    关于DocumentCompleted事件,MSDN给出的解释是在文档加载完毕后执行,但是在我的程序中DocumentCompleted却被多次调用,查了一下资料,大概出现了以下几种情况. 1.Web ...

  7. mongodb 数据导出

    后台找我导数据 以此记录 在mongodb  bin目录下执行 ./mongoexport -d xxx(db name)-c xxx(Collection name)-u xxx(username) ...

  8. Could not load file or assembly 'System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'

    提示哪个引用修改哪个引用的属性: Could not load file or assembly 'System.Web.Http, Version=4.0.0.0, Culture=neutral, ...

  9. 快递100码json

    { "AOL澳通速递": "aolau", "A2U速递": "a2u", "AAE快递": &qu ...

  10. 编译部署 Mysql 5.7

    1.环境准备 RHEL7.4(最小化安装)  64bit   2G 内存 (1G 内存编译将近一个小时) 磁盘空间 15G 以上. 配置为本地yum 源 从MySQL5.7版本开始,安装MySQL需要 ...