• RabbitMQ


    Remote Procedure Call  (RPC)

    用RabbitMQ实现RPC还是比较简单,一个客户端发送一个请求,服务端返回一个响应。为了实现响应我们需要发送一个"callback" 指定某个请求的队列,也就是下面主要介绍的Correlation Id

    Correlation Id

    实现RPC最简单的方式是为每个RPC请求创建一个callback回调函数,但这样是无效的或者说是冗余繁杂的。

    这也会产生一个问题,就是返回的response不能清晰的知道属于哪个request,这就会用到CorrelationId,我们将会为每次请求创建唯一的值,然后,我们收到这个callback response,查找这个值通过匹配的方式能够知道response属于哪个RPC request,如果我们收到一个未知的CorrelationId 值,我们完全可以忽略它,不属于我们的请求。

    这里可能会有所迷惑,为什么我们要忽略这个未知的值而不是直接报错,这个是由于RPC Server的竞争,虽然不太可能,这个可能是当RPC server发送一个reponse,但未发送确认(acknowledgment)消息给request之前宕机了,如果发生了,RPC Server重启之后会再次发送请求。这就是为什么在client端需要处理duplicate response。

    PRC工作原理

    • 当Clientk开始,将会创建匿名唯一的callback队列
    • 对于RPC请求,client会发送一个消息附带2个属性:ReplyTo通常是发送callback请求队列名,CorrelationId指定每次请求的唯一值。
    • 请求发送到队列
    • RPC Server等待请求对于指定的queue,当接收到请求,处理job并且会发送一个response消息给client,response的队列是ReplyTo request的队列。(就是说request和response的queue name和CorrelationId是一样的)
    • Client等待callback queue的数据,当收到请求消息,判断CorrelcationId是否匹配,如果匹配了,返回response给application 应用程序。

    下面以Fibonacci数列为例,实现简单的RPC Server / Client

    RPC Server的逻辑比较简单:

    • 建立连接connectionchannel,声明queue
    • 我们可能会运行多余一个的RPC Server,为了在多个Server之间实现负载均衡,需要设置prefetchCount channel.BasicQos settings。(解释一下:prefetchCount = 1是告诉RabbitMQ不要同时给一个worker分发多个消息,或者换句话说不要分发一个新消息给这个worker直到worker之前的消息已经处理并且被确认。而是分发新消息给下一个不繁忙的worker)
    • 用BasiConsume访问queue,然后注册一个delivery handler处理和发送response back。

    示例Code:

      public static void RunRPCDemo()
            {
                var factory = new ConnectionFactory() { HostName = "10.2.87.39" };
                using (var connection = factory.CreateConnection())
                {
                    using (var channel = connection.CreateModel())
                    {
                        channel.QueueDeclare(queue: "rpc_queue",
                            durable: false,
                            exclusive: false,
                            autoDelete: false,
                            arguments: null);
                        channel.BasicQos(0, 1, false);
                        var consumer = new EventingBasicConsumer(channel);
                        channel.BasicConsume(queue: "rpc_queue",
                            autoAck: false,
                            consumer: consumer);
                        Console.WriteLine("[x] waiting RPC request");
                        consumer.Received += (model, ea) =>
                        {
                            string response = null;
                            var body = ea.Body.ToArray();
                            var props = ea.BasicProperties;
                            var replyProps = channel.CreateBasicProperties();
                            replyProps.CorrelationId = props.CorrelationId;
    
                            try
                            {
                                var message = Encoding.UTF8.GetString(body);
                                int n = int.Parse(message);
                                Console.WriteLine($"[.] fib{message}");
                                response = fib(n).ToString();
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine($"[.] {e.Message}");
                            }
                            finally
                            {
                                var reponseBytes = Encoding.UTF8.GetBytes(response);
                                channel.BasicPublish(exchange: "",
                                    routingKey: props.ReplyTo,
                                    basicProperties: replyProps,
                                    body: reponseBytes);
                                channel.BasicAck(deliveryTag: ea.DeliveryTag,
                                    multiple: false);
    
                            }
                        };
                        Console.WriteLine("Press any key to exist");
                        Console.ReadLine();
                    }
                }
            }
    
            private static int fib(int n)
            {
                if (n == 0 || n == 1)
                {
                    return n;
                }
                return fib(n - 1) + fib(n - 2);
            }
    View Code

    RPC Client 逻辑稍微复杂一些:

    • 建立connection和channel,声明唯一的callback queue
    • 订阅callback queue,以至于可以收到RPC 响应
    • Call 方法是实际RPC 请求
    • 创建一个唯一的CorrelationId number
    • 发布request message附带ReplyTo和CorrelationId两个属性
    • 接下来等待RPC Server响应
    • 对于每个响应,判断是否是我们所期待的
    • 最终返回response给user

    示例Code

     public class RPCClient
        {
            private readonly IConnection connection;
            private readonly IModel channel;
            private readonly string replyQueueName;
            private readonly EventingBasicConsumer consumer;
            private readonly BlockingCollection<string> respQueue = new BlockingCollection<string>();
            private readonly IBasicProperties props;
    
            public RPCClient()
            {
                var factory = new ConnectionFactory() { HostName = "10.2.87.39" };
                connection = factory.CreateConnection();
                channel = connection.CreateModel();
    
                replyQueueName = channel.QueueDeclare().QueueName;
                consumer = new EventingBasicConsumer(channel);
    
                props = channel.CreateBasicProperties();
                var correlationId = $"{Guid.NewGuid()}";
                props.CorrelationId = correlationId;
                props.ReplyTo = replyQueueName;
    
                consumer.Received += (model, ea) =>
                {
                    var body = ea.Body.ToArray();
                    var response = Encoding.UTF8.GetString(body);
                    if (ea.BasicProperties.CorrelationId == correlationId)
                    {
                        respQueue.Add(response);
                    }
                };
            }
    
            public string Call(string message)
            {
                var messageBytes = Encoding.UTF8.GetBytes(message);
                channel.BasicPublish(exchange: "",
                    routingKey: "rpc_queue",
                    basicProperties: props,
                    body: messageBytes);
                channel.BasicConsume(consumer: consumer,
                    queue: replyQueueName,
                    autoAck: true);
                return respQueue.Take();
            }
    
            public void Close()
            {
                connection.Close();
            }
        }
    View Code

    客户端Code:

     public static void RunRPCDemo()
     {
                var rpcClient = new RPCClient();
                Console.WriteLine($"[x] requesting fib(30)");
                var response = rpcClient.Call("30");
                Console.WriteLine($"[.] get {response}");
                rpcClient.Close();
      }

    运行RPC Server和Client,结果如下:

    简单的RPC已经实现了。

    待解决问题:

    • 没有server运行时,Client如何处理
    • RPC Client长时间没有收到response,过期时间怎么处理
    • 如果server 异常了,是否需要向client发送response
    • 无效的incoming message,如何处理
  • 相关阅读:
    ZOJ 4097 Rescue the Princess
    最大值最小化 最小值最大化
    SD第九届省赛B题 Bullet
    Euler Circuit UVA
    bzoj 1878
    随笔
    BZOJ
    主席树模板
    AC自动机模板
    BZOJ
  • 原文地址:https://www.cnblogs.com/qindy/p/14576472.html
Copyright © 2020-2023  润新知