ASP.NET Core 6使用RabbitMQ实现RPC远程通信

一、概念、原理及介绍

1、什么是RPC

        RPC(Remote Procedure Call,远程过程调用)是一种通信协议和编程模型,用于实现分布式系统中的远程调用。它允许一个计算机程序通过网络请求另一个计算机上的服务或方法,就像调用本地方法一样。

        在 RPC 中,客户端应用程序可以调用位于远程服务器上的方法,而无需了解底层网络细节。客户端将请求参数传递给服务器,并等待服务器返回结果。服务器执行相应的方法,并将结果返回给客户端。

2、RPC工作原理

        RPC 的工作原理如下:

        1. 定义接口:首先需要定义客户端和服务器之间的接口,包括可调用的方法、参数和返回值类型。

        2. 生成代理:客户端根据接口定义生成一个代理对象,该代理对象封装了与服务器通信的细节。

        3. 序列化参数:客户端将方法调用的参数序列化为字节流,以便在网络上传输。

        4. 发送请求:客户端通过网络将序列化后的请求发送给服务器。

        5. 接收请求并处理:服务器接收到请求后,根据请求中的方法名和参数执行相应的方法。

        6. 序列化结果:服务器将方法执行的结果序列化为字节流。

        7. 发送响应:服务器将序列化后的结果发送给客户端。

        8. 接收响应:客户端接收到响应后,将其反序列化为方法的返回值。

        通过 RPC,可以将分布式系统中的不同部分连接起来,使得它们可以像本地方法一样进行交互。RPC 提供了一种简单、方便和高效的方式来实现跨网络的方法调用,使得分布式系统的开发和维护更加容易。

二、RabbitMQ实现RPC

1、实现原理概念

        当使用 RabbitMQ 实现 RPC(Remote Procedure Call)通信时,客户端可以向服务器发送请求,并等待服务器返回结果。这种通信模式允许客户端和服务器之间进行远程调用,就像本地方法调用一样。

2、实现步骤

在 .NET 6 中使用 RabbitMQ 实现 RPC 通信的步骤如下:

        1. 安装 RabbitMQ:首先需要安装 RabbitMQ 服务器,并确保它正在运行。

        2. 引用 RabbitMQ.Client 库:在 .NET 6 项目中,需要引用 RabbitMQ.Client 库来与 RabbitMQ 进行通信。

        3. 创建连接和通道:使用 RabbitMQ.Client 库创建与 RabbitMQ 服务器的连接,并在该连接上创建一个通道(Channel)。通道是进行消息传递的主要工具。

        4. 声明队列:在通道上声明一个请求队列和一个响应队列。请求队列用于接收客户端发送的请求,而响应队列用于接收服务器发送的响应。

        5. 发送请求:客户端通过将请求消息发布到请求队列中,向服务器发送请求。请求消息可以包含方法名、参数等信息。

        6. 接收请求并处理:服务器通过订阅请求队列,接收到客户端发送的请求消息。服务器根据请求消息中的信息执行相应的方法,并得到结果。

        7. 发送响应:服务器将执行结果封装成响应消息,并发布到响应队列中。

        8. 接收响应:客户端通过订阅响应队列,接收到服务器发送的响应消息。客户端可以从响应消息中获取到服务器执行方法的结果。

        通过以上步骤,你可以使用 RabbitMQ 在 .NET 6 中实现 RPC 通信,实现客户端和服务器之间的远程调用。这种通信模式可以帮助你构建分布式系统,提高系统的可扩展性和灵活性。

三、代码实现

1、创建RPC客户端程式

        创建一个web api 项目,用于演示实现客户端消息的生成发送和消息远程响应反馈结果的处理,基于RabbirMQ的发布订阅模式实现。

2、整合RabbitMQ

        1、项目引入RabbitMQ的Nuget包

        2、新增RabbitMQConfigs抽象基类,编写以下代码

public abstract class RabbitMQConfigs
{
    protected ConnectionFactory GreateConnectionFactory(IConfiguration configuration)
    {
        // 在这里设置ConnectionFactory的属性
        var factory = new ConnectionFactory
        {
            // 设置连接属性
            HostName = configuration["RabbitMQ:HostName"],
            Port = int.Parse(configuration["RabbitMQ:Port"]),
            UserName = configuration["RabbitMQ:UserName"],
            Password = configuration["RabbitMQ:Password"]
        };
        return factory;
    }
}

        3、appsetting.json文件配置本地RabbitMQ连接信息

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",

  "RabbitMQ": {
    "HostName": "localhost",
    "Port": "5672",
    "UserName": "",//账号
    "Password": "" //密码
  }
}

        4、新建IRabbitMQServices接口,声明RPC客户端通信方法rpcClient

public interface IRabbitMQServices
{
    
    /// <summary>
    /// RPC通信客户端请求
    /// </summary>
    void rpcClient(string message);
}

        5、新建RabbitMQServices,继承与RabbitMQConfigs和IRabbitMQServices,实现RPC客户端的请求方法,使用到任务Task()为了不阻塞主线程,由子线程去等待远程响应并处理。

public class RabbitMQServices : RabbitMQConfigs, IRabbitMQServices
{
    private readonly IConnection _connection;
    private readonly IModel _model;
    static ConcurrentDictionary<string, TaskCompletionSource<string>> pendingRequests = new();

    public RabbitMQServices(IConfiguration configuration)
    {
        var factory = GreateConnectionFactory(configuration);
        _connection = factory.CreateConnection();
        _model = _connection.CreateModel();
    }
    #region
    /// <summary>
    /// RabbitMQ实现RPC远程通信
    /// </summary>
    /// <param name="message"></param>
    public void rpcClient(string message)
    {
        // 声明交换机、 请求队列和响应队列、请求路由和响应路由
        string exchange = "rpc";
        
        var requestQueue = "rpc_request_queue";
        var responseQueue = "rpc_respone_queue";

        var requestkey = "rpc_request_key";
        var responsekey = "rpc_respone_key";
        //设置消息属性
        string corrId = Guid.NewGuid().ToString();
        var props = _model.CreateBasicProperties();
        props.ReplyTo = responseQueue; 
        props.CorrelationId = corrId;

        // 创建 TaskCompletionSource,并添加到字典中
        var tcs = new TaskCompletionSource<string>();
        pendingRequests.TryAdd(corrId, tcs);

        //绑定交换机、队列、路由
        _model.ExchangeDeclare(exchange, "direct");
        _model.QueueDeclare(requestQueue, false, false, false, null);
        _model.QueueBind(requestQueue, exchange, requestkey);

        _model.QueueDeclare(responseQueue, false, false, false, null);
        _model.QueueBind(responseQueue, exchange, responsekey);
        //发生RPC请求
        var body = Encoding.UTF8.GetBytes(message);
        _model.BasicPublish(exchange, requestkey, props, body);
        Console.WriteLine("向远程RPC发送消息:{0}{1}", corrId, message);


        // 创建消费者
        var consumer = new EventingBasicConsumer(_model);
        _model.BasicConsume(queue:responseQueue,autoAck:true,consumer:consumer);

        //监听处理RPC请求的响应
        consumer.Received += (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);
            var res_props = ea.BasicProperties;

            
            if (res_props.CorrelationId.Equals(corrId)) {
                Console.WriteLine("收到远程RPC响应消息:{0}", message);
            }
            // 获取响应的 CorrelationId
            var correlationId = ea.BasicProperties.CorrelationId;
            Console.WriteLine("收到远程RPC批量响应消息:{0} {1}", message,correlationId);

            // 根据 CorrelationId 查找对应的 Task
            if (pendingRequests.TryRemove(correlationId, out var tcs))
            {
                // 设置响应结果到 Task,并标记为已完成
                tcs.SetResult(message);
            }
        };

        Task.Run(() =>
        {
            // 等待响应完成
            var responseTask = tcs.Task;
            responseTask.Wait();

            // 输出响应结果
            var response = responseTask.Result;
            Console.WriteLine("线程:{0};收到响应:{1}", Thread.CurrentThread.ManagedThreadId,response);
        });
        Console.WriteLine("主线程结束:{0}", Thread.CurrentThread.ManagedThreadId);
    }
}

        6、通过依赖注入或者直接new()在Controller层实现RPC通信的请求发起

public class ApiController : ControllerBase
{
    private readonly IRabbitMQServices _rabbitMQServices;
    private readonly IOrderServices _orderService;

    public ApiController(IRabbitMQServices rabbitMQServices, IOrderServices orderService,IEventBusServices eventBusServices)
    {
        _rabbitMQServices=rabbitMQServices;
        _orderService=orderService;
    }

    [Route("api/rpc_request")]
    [HttpPut]
    public void RPC(string key)
    {
        // 开始发送RPC通信消息
        _rabbitMQServices.rpcClient(key);

    }
}

 3、新建RPC服务端项目

        新建一个PRC服务端的控制台应用程序,引入RabbitMQ,实现RPC消息的接收处理并响应返回结果到客户端,假设该项目有一个方法,将客户端发送的消息经过调用该方法处理之后,响应返回客户端,就是实现了客户端远程调用服务端的方法。

        1、修改Program.cs,创建RabbitMQ连接

IConnection _connection = null;
IModel _model = null;

var factory = new ConnectionFactory
{
    // 设置连接属性
    HostName = "localhost",
    Port = 5672,
    UserName = "my",
    Password = "123456"
};

_connection = factory.CreateConnection();
_model = _connection.CreateModel();

//启用RPC服务
rpcServer();

Console.WriteLine("按任意键退出...");
Console.ReadKey();

        2、新增方法,这里面还模拟塞入了一个其他id的响应,用于验证客户端的消息的原子性,验证客户端是否只处理请求id和响应id一致的响应信息。

// <summary>
/// RPC服务端
/// </summary>
void rpcServer()
{
    // 声明交换机、 请求队列和响应队列、请求路由和响应路由
    string exchange = "rpc";

    var requestQueue = "rpc_request_queue";
    var requestkey = "rpc_request_key";

    //绑定交换机、队列、路由
    _model.ExchangeDeclare(exchange, "direct");
    //_model.QueueDeclare(requestQueue,false,false,false,null);
    _model.QueueBind(requestQueue,exchange, requestkey);

    // 创建消费者,绑定回调函数
    var consumer = new EventingBasicConsumer(_model);
    consumer.Received += (model, ea) =>
    {
        var body = ea.Body.ToArray();
        var message = Encoding.UTF8.GetString(body);
        var props = ea.BasicProperties;

        var requestid = props.CorrelationId;
        var responequeue = props.ReplyTo;
        Console.WriteLine("收到RPC请求消息:{0}{1}", requestid, message);
        string respone = "你好,我是RPC运程服务端。";
        Console.WriteLine("回复RPC请求消息:{0}", respone);
        Task.Run(() =>
        {
            //Thread.Sleep(3000); // 延时 3 秒:模拟复杂的处理耗时过程
            var props1 = _model.CreateBasicProperties();
            props1.CorrelationId = "67890";
            _model.BasicPublish("", responequeue, props1, Encoding.UTF8.GetBytes("这个是其他响应返回"));
            Console.WriteLine("其他结束");
        });
        
        Thread.Sleep(3000); // 延时 3 秒:模拟复杂的处理耗时过程
        //响应RPC请求
        _model.BasicPublish("",responequeue,props, Encoding.UTF8.GetBytes(respone));
        
    };

    // 启动消费者
    _model.BasicConsume(queue: requestQueue, autoAck: true, consumer: consumer);
}

四、运行调试

        1、分别启动运行RPC客户端和服务端项目

        2、调用api:api/rpc_request,请求RPC

        3、结果展示

五、总结 

        在这个过程中,RabbitMQ 扮演了消息中间件的角色,负责消息的传递和路由。客户端和服务器之间通过消息队列进行通信,解耦了彼此的直接依赖关系。

为了实现 RPC 通信,需要注意以下几点:

  • 1、队列声明:客户端和服务器都需要在 RabbitMQ 中声明自己的请求队列和响应队列。

  • 2、唯一标识:每个请求和响应都需要有唯一的标识符,以便客户端能够正确地匹配请求和响应。

  • 3、回调机制:客户端可以使用回调函数或者异步等待的方式来处理服务器的响应。

  • 4、超时处理:客户端可以设置超时时间,如果在指定时间内没有收到服务器的响应,则认为请求失败。

        通过 RabbitMQ 实现 RPC 通信可以帮助构建分布式系统,提供灵活、可扩展的远程调用能力。同时,RabbitMQ 提供了可靠的消息传递机制,确保消息的可靠性和顺序性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值