什么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. symfony小练习-表白墙

    过上一个博客系统以及对官方示例程序的基本学习,目前对symfony的各个组件有了一定的学习,学校布置了一个表白墙任务,这里就这个任务的完成进行记录 ...........2019.3.20.22.31 ...

  2. H5唤醒app,第三方开源库

    在微信浏览器内,安卓打开应用宝,ios跳进appstore,基本都可以成功在外部浏览器内,已安装可进入应用内,未安装进入应用宝提示下载,需客户端支持.<!DOCTYPE html> < ...

  3. Flink解析kafka canal未压平数据为message报错

    canal使用非flatmessage方式获取mysql bin log日志发至kafka比直接发送json效率要高很多,数据发到kafka后需要实时解析为json,这里可以使用strom或者flin ...

  4. Python科学计算库

    Python科学计算库 一.numpy库和matplotlib库的学习 (1)numpy库介绍:科学计算包,支持N维数组运算.处理大型矩阵.成熟的广播函数库.矢量运算.线性代数.傅里叶变换.随机数生成 ...

  5. 获取标准shell 命令的输出内容

    cmdline.h #include <iostream> #include <mutex> class Cmdline { private: Cmdline() = defa ...

  6. libevent入门介绍

    libevent是之前看到的一个别人推荐的清凉级网络库,我就想了解一下它.今天下载到了一个人写的剖析系列,从结构和源码方面进行了简要分析.只是这个分析文章是2010年的,有点过时了(跟现在的libev ...

  7. 查看 FormData 中已存在的值

    var formData = new FormData(); formData.append('name','bob'); formData.append('sex','male'); formDat ...

  8. Katalon Studio之接口测试中token处理

    前言 最近抽时间接触了一下Katalon Studio(后面简称KS),并且利用KS做了一些接口测试的试验,感觉还不错,不过其中接口授权中缺少通过token动态验证的方案,虽然KS支持Authoriz ...

  9. 适配iOS11

    总结在iOS11系统中出现的适配问题: 启动app发现上下有空隙,不能完全贴合屏幕----- 解决方案:添加一张尺寸为1125x2436的启动图. 隐藏导航栏的界面,会出现无法贴合屏幕顶部(一般来说, ...

  10. Oracle分析函数——函数列表

    --------------聚合函数 SUM :该函数计算组中表达式的累积和 MIN :在一个组中的数据窗口中查找表达式的最小值 MAX :在一个组中的数据窗口中查找表达式的最大值 AVG :用于计算 ...