资讯专栏INFORMATION COLUMN

RabbitMQ+PHP 教程六(RPC)

anquan / 2703人阅读

摘要:有助于将响应与请求关联起来。如果发生这种情况,重新启动的服务器将再次处理请求。又名服务器正在等待该队列上的请求。当消息出现时,它检查属性。然后,我们进入循环,在其中等待请求消息,完成工作并发送响应。

(using php-amqplib)

前提必读

本教程假设RabbitMQ是安装在标准端口上运行(5672)。如果您使用不同的主机、端口或凭据,则连接设置需要调整。

如果您在本教程中遇到困难,可以通过邮件列表与我们联系。

开始

在第二个教程中,我们学习了如何使用工作队列在多个工人之间分配耗时的任务。

但是如果我们需要在远程计算机上运行一个函数并等待结果呢?嗯,那是另一回事了。这种模式通常称为远程过程调用或RPC。

在本教程中我们将使用RabbitMQ搭建一个RPC系统:一个客户端和一个可扩展的RPC服务器。由于我们没有任何值得分配的耗时的任务,所以我们将创建一个返回Fibonacci数的模拟一个RPC服务。

Client interface

为了说明如何使用RPC服务,我们将创建一个简单的客户类。它将公开一个名为调用的方法,该方法发送一个RPC请求并阻塞直到接收到结果为止:

$fibonacci_rpc = new FibonacciRpcClient();
$response = $fibonacci_rpc->call(30);
echo " [.] Got ", $response, "
";
关于RPC的一些建议

虽然RPC是计算中非常常见的模式,但它经常遭到批评。当程序员不知道函数调用是本地的,或者它是一个缓慢的RPC时,问题就出现了。这样的混乱导致了不可预知的系统,并给调试增加了不必要的复杂性。而简化软件,滥用会导致难以维护的RPC代码。

考虑到这一点,请考虑以下建议:

确保很明显哪个函数调用是本地调用,并且它是远程的。
记录系统。使组件之间的依赖关系清晰。
处理错误案例。RPC服务器长时间处于下行状态时,客户端应如何响应?
有疑问时避免RPC。如果可以,则应该使用异步管道,而不是像阻塞这样的RPC,结果被异步推送到下一个计算阶段。

回调队列(Callback queue)

一般在RabbitMQ做RPC是容易的。客户端发送一条请求消息和一个响应消息的服务器回复。为了接收响应,我们需要向请求发送一个“回调”队列地址。我们可以使用默认队列。让我们试试看:

list($queue_name, ,) = $channel->queue_declare("", false, false, true, false);

$msg = new AMQPMessage(
    $payload,
    array("reply_to" => $queue_name));

$channel->basic_publish($msg, "", "rpc_queue");
# ... then code to read a response message from the callback_queue ...
消息属性
AMQP协议(0-9-1 protocol)预定义了一套14个属性,去一个消息。大多数属性很少使用,除了以下内容:

delivery_mode: 将消息标记为持久性。 (with a value of 2) or transient (1). 您可能会从第二个教程中记住这个属性。
content_type:用来描述编码的MIME类型。例如,对于常用的JSON编码,将此属性设置为应用程序/ JSON是一个很好的做法。
reply_to:常用的名字一个回调队列。
correlation_id:有助于将RPC响应与请求关联起来。

Correlation Id

在上面介绍的方法中,我们建议为每个RPC请求创建一个回调队列。这是非常低效的,但幸运的是有一个更好的方法——让我们为每个客户机创建一个回调队列。

这引发了一个新问题,在队列中收到了响应,不清楚响应的请求属于哪个。那时候correlation_id属性用于。我们将把它设置为每个请求的唯一值。稍后,当我们在回调队列中接收消息时,我们将查看这个属性,并在此基础上,我们将能够将响应与请求匹配。如果我们看到一个未知的correlation_id值,我们可以安全地忽略信息-它不属于我们的请求。

您可能会问,为什么我们应该忽略回调队列中的未知消息,而不是失败出错呢?这是由于服务器端可能出现竞争情况。虽然不太可能,RPC服务器可能在发送完答案后死亡,但在发出请求的确认消息之前。如果发生这种情况,重新启动的RPC服务器将再次处理请求。这就是为什么在客户机上我们必须优雅地处理重复响应,而RPC应该理想地是幂等的。

总结

我们的RPC会像这样工作:

当客户端启动时,它创建一个匿名的独占回调队列。

一个RPC请求,客户端发送消息,两个属性:reply_to,设置回调队列和correlation_id,它被设置为每个请求的唯一值。

请求被发送到一个rpc_queue队列。

RPC worker(又名:服务器)正在等待该队列上的请求。当一个请求时,它的工作和发送消息的结果返回给客户端,使用从reply_to队列。

客户机等待回调队列上的数据。当消息出现时,它检查correlation_id属性。如果它与请求的值匹配,则返回对应用程序的响应。

汇总

Fibonacci 递归源码:

function fib($n) {
    if ($n == 0)
        return 0;
    if ($n == 1)
        return 1;
    return fib($n-1) + fib($n-2);
}
``
我们声明fibonacci(斐波那契)函数。它只假设有效的正整数输入。(不要指望这一个能为大数字工作,而且这可能是最慢的递归实现)。

我们的RPC服务器rpc_server.php代码看起来像这样:

require_once DIR . "/vendor/autoload.php";
use PhpAmqpLibConnectionAMQPStreamConnection;
use PhpAmqpLibMessageAMQPMessage;

$connection = new AMQPStreamConnection("localhost", 5672, "guest", "guest");
$channel = $connection->channel();

$channel->queue_declare("rpc_queue", false, false, false, false);

function fib($n) {

if ($n == 0)
    return 0;
if ($n == 1)
    return 1;
return fib($n-1) + fib($n-2);

}

echo " [x] Awaiting RPC requestsn";
$callback = function($req) {

$n = intval($req->body);
echo " [.] fib(", $n, ")
";

$msg = new AMQPMessage(
    (string) fib($n),
    array("correlation_id" => $req->get("correlation_id"))
    );

$req->delivery_info["channel"]->basic_publish(
    $msg, "", $req->get("reply_to"));
$req->delivery_info["channel"]->basic_ack(
    $req->delivery_info["delivery_tag"]);

};

$channel->basic_qos(null, 1, null);
$channel->basic_consume("rpc_queue", "", false, false, false, false, $callback);

while(count($channel->callbacks)) {

$channel->wait();

}

$channel->close();
$connection->close();

?>

服务器代码相当简单:

像往常一样,我们从建立连接、通道和声明队列开始。

我们可能需要运行多个服务器进程。为了分散负载同样多的服务器需要设置`prefetch_count`, 设置`$channel.basic_qos`美元。

我们用`basic_consume`访问队列。然后,我们进入while循环,在其中等待请求消息,完成工作并发送响应。

我们rpc_client.php RPC客户端代码:

require_once DIR . "/vendor/autoload.php";
use PhpAmqpLibConnectionAMQPStreamConnection;
use PhpAmqpLibMessageAMQPMessage;

class FibonacciRpcClient {

private $connection;
private $channel;
private $callback_queue;
private $response;
private $corr_id;

public function __construct() {
    $this->connection = new AMQPStreamConnection(
        "localhost", 5672, "guest", "guest");
    $this->channel = $this->connection->channel();
    list($this->callback_queue, ,) = $this->channel->queue_declare(
        "", false, false, true, false);
    $this->channel->basic_consume(
        $this->callback_queue, "", false, false, false, false,
        array($this, "on_response"));
}
public function on_response($rep) {
    if($rep->get("correlation_id") == $this->corr_id) {
        $this->response = $rep->body;
    }
}

public function call($n) {
    $this->response = null;
    $this->corr_id = uniqid();

    $msg = new AMQPMessage(
        (string) $n,
        array("correlation_id" => $this->corr_id,
              "reply_to" => $this->callback_queue)
        );
    $this->channel->basic_publish($msg, "", "rpc_queue");
    while(!$this->response) {
        $this->channel->wait();
    }
    return intval($this->response);
}

};

$fibonacci_rpc = new FibonacciRpcClient();
$response = $fibonacci_rpc->call(30);
echo " [.] Got ", $response, "n";

?>

现在是一个很好的时间来让我们完整的示例源代码rpc_client.php和rpc_server.php。

我们的RPC服务现在准备好了。我们可以启动服务器:

php rpc_server.php
# => [x] Awaiting RPC requests

请求斐波那契数运行客户机:

php rpc_client.php
# => [x] Requesting fib(30)
``
这里介绍的设计并不是RPC服务的唯一实现,但它有一些重要的要点:

如果RPC服务器太慢,您可以通过运行另一个服务器来扩展。试着在一个新的控制台再运行第一个:rpc_server.php。

在客户端,RPC只需要发送和接收一条消息。不喜欢queue_declare需要同步调用。因此,RPC客户机只需要一次RPC请求的一次网络往返。

我们的代码仍然非常简单,并没有试图解决更复杂(但重要)的问题,例如:

如果没有服务器运行,客户端应该如何反应?

客户端应该对RPC有某种超时吗?

如果服务器发生故障并引发异常,是否应该转发给客户端?

在处理前防止无效传入消息(如检查边界、类型)。

如果您想进行实验,您可能会发现management UI对于查看队列非常有用。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28314.html

相关文章

  • RabbitMQ+PHP 教程五(Topics)

    摘要:前提必读本教程假设是安装在标准端口上运行。这些词可以是任何东西,但通常它们指定连接到消息的某些特性。如果我们违背合同,用一个或四个词,如或那么,这些消息将不匹配任何绑定并将丢失。代码与前面的教程几乎相同。 (using php-amqplib) 前提必读 本教程假设RabbitMQ是安装在标准端口上运行(5672)。如果您使用不同的主机、端口或凭据,则连接设置需要调整。 在哪里得到帮助...

    nemo 评论0 收藏0
  • 【译】RabbitMQ系列()-RPC模式

    摘要:如果涉及返回值,就要用到本章提到的了。方法发送请求,并阻塞知道结果返回。当有消息时,进行计算并通过指定的发送给客户端。当接收到,则检查。如果和之前的匹配,则将消息返回给应用进行处理。 RPC模式 在第二章中我们学习了如何使用Work模式在多个worker之间派发时间敏感的任务。这种情况是不涉及到返回值的,worker执行任务就好。如果涉及返回值,就要用到本章提到的RPC(Remote ...

    894974231 评论0 收藏0
  • 白话RabbitMQ(): RPC

    摘要:因为消费消息是在另外一个进程中,我们需要阻塞我们的进程直到结果返回,使用阻塞队列是一种非常好的方式,这里我们使用了长度为的,的功能是检查消息的的是不是我们之前所发送的,如果是,将返回值返回到。 推广 RabbitMQ专题讲座 https://segmentfault.com/l/15... CoolMQ开源项目 我们利用消息队列实现了分布式事务的最终一致性解决方案,请大家围观。可以参考...

    KevinYan 评论0 收藏0
  • rabbitmq中文教程python版 - 远程过程调用

    摘要:通常用于命名回调队列。对每个响应执行的回调函数做了一个非常简单的工作,对于每个响应消息它检查是否是我们正在寻找的。在这个方法中,首先我们生成一个唯一的数并保存回调函数将使用这个值来捕获适当的响应。 源码:https://github.com/ltoddy/rabbitmq-tutorial 远程过程调用(RPC) (using the Pika Python client) 本章节教程...

    chuyao 评论0 收藏0
  • RabbitMQ+PHP 教程一(Hello World)

    摘要:在中间的框是一个队列的消息缓冲区,保持代表的消费。本教程介绍,这是一个开放的通用的协议消息。我们将在本教程中使用,解决依赖管理。发送者将连接到,发送一条消息,然后退出。注意,这与发送发布的队列匹配。 介绍 RabbitMQ是一个消息代理器:它接受和转发消息。你可以把它当作一个邮局:当你把邮件放在信箱里时,你可以肯定邮差先生最终会把邮件送到你的收件人那里。在这个比喻中,RabbitMQ就...

    silencezwm 评论0 收藏0

发表评论

0条评论

anquan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<