【问题标题】:Why does my Node.js gRPC client take 3 seconds to send a request to my Python gRPC server?为什么我的 Node.js gRPC 客户端需要 3 秒才能向我的 Python gRPC 服务器发送请求?
【发布时间】:2020-08-17 07:49:24
【问题描述】:

让我们首先承认我是一个 gRPC 菜鸟。如果我问了一个愚蠢的问题,请继续告诉我,但只有在解释为什么它是愚蠢的之后。谢谢! :)

概述

我正在开发一个处理图像的应用程序。影响处理结果的变量可由用户更改。我想在保持跨平台兼容性的同时提供一个好看的 GUI。因此,我决定在 Python 中实现我的图像处理,并使用 Node.js 和 Electron 作为我的 GUI。

需要一种方法来向我的 Python 后端发送和接收数据,我决定使用 gRPC。因此,我有一个 Node.js gRPC 客户端 与一个 Python gRPC 服务器

在阅读了许多教程并了解了一些协议缓冲区之后,我能够成功地在两个程序之间传输数据。

设置

Node.js Electron 应用程序接收用户的输入,并向 Python 后端发送请求。请求的大小是一个小对象:

// From My Protocol Buffer
message DetectionSettings {
    float lowerThreshold = 1;
    float upperThreshold = 2;
    float smallestObject = 3;
    float largestObject  = 4;
    float blurAmount     = 5;

    int64 frameNumber    = 6;
    string streamSource  = 7;
}

收到此请求后,Python 应用程序会从指定的streamSource 中读取一个帧并进行处理。然后将此处理后的帧转换为 JPEG 格式并通过 gRPC 作为 Image 返回到 Node.js 应用程序:

// From My Protocol Buffer
message Image {
    bytes image_data = 1;
    int32 height = 2;
    int32 width = 3;
    int64 frame = 4;    
}

问题

我注意到在发出请求和接收到图像之间存在可变延迟。这个时间从几毫秒到近 3 秒不等!在分析 Python 代码后,我确定处理时间可以忽略不计。此外,通过 gRPC 返回Image 所需的时间也可以忽略不计。

因此,在 RPC 执行和 Python 应用程序接收调用之间存在一个有趣的延迟。以下是一些带有时间的日志,可以更好地解释正在发生的事情(括号中的数字以秒为单位):

/* Node.js */
[160408.072] "Sending Request..."
[160408.072] "Executing RPC"
[160408.072] "RPC Executed"

/* Python */
[160411.032] [ py-backend ] Getting frame

/* Node.js */
[160411.617] "Got Data"

您可以看到,在这种情况下,从执行 Node.js RPC 到调用 Python 方法的时间约为 3 秒,而从执行 Python 方法到 Node.js 应用程序接收到Image 的时间小于 1 秒 0.o

代码

Python gRPC 服务器

# Create the server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))

# Add our service to the server
processor_pb2_grpc.add_ProcessorServicer_to_server(
    ProcessorServicer(), server
)

server.add_insecure_port('[::1]:50053')
server.start()
server.wait_for_termination()

Node.js 客户端:

const grpc = require('grpc')
const PROTO_PATH = '../processor.proto'

const serviceConfig = {
    "loadBalancingConfig": [ {"round_robin": {} } ]
}
const options = {
    'grpc.service_config': JSON.stringify(serviceConfig)
}

const ProcessorService = grpc.load(PROTO_PATH).Processor
const client = new ProcessorService ('[::1]:50053',
    grpc.credentials.createInsecure(), options);

module.exports = client

协议缓冲区:

syntax = "proto3";

message Number {
    float value = 1;
}

message Image {
    bytes image_data = 1;
    int32 height = 2;
    int32 width = 3;
    int64 frame = 4;    
}

message DetectionSettings {
    float lowerThreshold = 1;
    float upperThreshold = 2;
    float smallestObject = 3;
    float largestObject  = 4;
    float blurAmount     = 5;

    int64 frameNumber    = 6;
    string streamSource  = 7;
}

service Processor{
    rpc GetFrame(DetectionSettings) returns (Image) {};
}

Node.js gRPC 调用:

function GetFrameBytes(detectionSettings, streamSource, frameNumber, callback)
{
    detectionSettings["streamSource"] = streamSource;
    detectionSettings["frameNumber"] = frameNumber;

    client.GetFrame(detectionSettings, (error, response) =>
    {
        if(!error){
            callback(response)
        }
        else
        {
            console.error(error);
        }
    });
}

tl;博士

到底为什么我的 Node.js 客户端请求需要这么长时间才能触发我的 Python 代码?

【问题讨论】:

    标签: python node.js electron grpc-python grpc-node


    【解决方案1】:

    有几个因素可能会影响您观看的时间。

    首先,客户端发出的第一个请求总是需要更长的时间,因为它必须进行一些连接设置工作。一般的预期是单个客户端会发出许多请求,并且设置时间将分摊到所有这些请求中。

    其次,您提到您正在使用 Electron。有一些关于 gRPC for Node 在 Electron 渲染过程中使用时性能不佳的报告。在主进程或常规 Node 进程中运行代码时,您可能会看到不同的结果。

    如果您尝试使用 @grpc/grpc-js 包,您可能还会有不同的运气,它是对 Node gRPC 库的完整重新实现。您当前用于加载 .proto 包的 API 在该库中不存在,但使用 @grpc/proto-loader 的替代方法适用于这两种实现,并且其他 API 相同。

    【讨论】:

    • 感谢您富有洞察力的回答!我通过不断建立连接来测试“第一个请求需要更长的时间”理论。这导致了巨大的加速。虽然我还是有点困惑......超时后连接是否被“丢弃”?或者为什么在请求之间的时间更短的时间内运行我的代码会使响应更快?
    • 我只是想澄清一下,当您向客户端client.methodName(...) 发出请求时,您通常不会建立新连接,而是使用客户端内部管理的现有连接。这就是为什么第一个请求之后的请求更快的原因。
    • 如果您在请求之间等待的时间足够长,则底层连接可能会因不活动而关闭,因此在您发出下一个请求时需要重新创建它。
    • 啊,我明白了。我在请求之间有约 1 秒的延迟。只有当我将延迟减少到 0(收到响应后立即发送另一个请求)时,我才注意到改进。
    • 客户端有一个方法waitForReady,它将指示它开始连接,并在建立连接时调用回调。这将与您提到的“ping/pong”方法具有相同的目的:在您需要使用它之前设置连接。我确实想确保清楚的是,这不会改变建立连接所需的时间,只是在它发生时。如果你想在构建客户端后立即发出请求,你不妨直接发出请求。
    猜你喜欢
    • 1970-01-01
    • 2020-09-09
    • 2019-11-12
    • 2021-10-19
    • 2021-11-29
    • 2021-04-10
    • 1970-01-01
    • 1970-01-01
    • 2017-04-21
    相关资源
    最近更新 更多