Node.js 使用 gRPC:从定义到实现

news/2024/7/8 2:31:27 标签: node.js, javascript, rpc, 开发语言, java, 分布式, 架构

1. 概述:

gRPC(gRPC Remote Procedure Calls)是一个高性能、开源的远程过程调用(RPC)框架,由 Google 开发。它支持多种编程语言,旨在简化和优化分布式系统中的服务通信。

2. gRPC的优势:

高性能:使用 HTTP/2 和 protobuf 使得 gRPC 在性能和效率方面表现出色。二进制协议和 HTTP/2 的多路复用特性使其通信开销低,速度快。

简化开发:自动代码生成和多语言支持简化了微服务的开发和维护。通过 .proto 文件定义接口后,gRPC 工具会生成相应的客户端和服务器代码,大大减少了手动编码的工作量。

强类型:使用 protobuf 定义服务接口和消息类型,确保强类型检查和错误检测。开发人员可以在编译时捕捉到许多错误,提高代码的可靠性和可维护性。

灵活的流处理:支持多种通信模式(单次请求-响应、服务端流、客户端流、双向流),适应不同的使用场景。例如,可以用服务端流实现数据的实时推送,用双向流实现实时聊天功能。

高效的序列化:Protocol Buffers 是一种高效的二进制序列化格式,序列化和反序列化速度快,生成的数据体积小,适合高性能场景。

3. 实现逻辑:

  1. 定义一个服务,指定被调用的方法(包含参数和返回类型)。
  2. 运行 gRPC 服务器来处理客户端的调用。
  3. 在客户端拥有一个存根,能够像服务端一样的方法。

4. Node.js:

Node.js 库从运行时加载的 .proto 文件动态生成服务描述和客户端存根的定义,所以使用此语言时没必要生成任何特殊代码。而是在例子客户端和服务端里,我们 require gRPC 库,然后用它的 load() 方法,就可以去加载.proto文件。

5. 为什么 nodejs 推荐动态加载.proto文件?

使用 @grpc/proto-loader 库在运行时动态加载 .proto 文件。这是官方推荐的方法

优点:

  1. 开发便捷:不需要在每次修改 .proto 文件后重新生成代码,开发过程更加便捷
  2. 灵活性:适合快速迭代和频繁修改 .proto 文件的项目
  3. 减少依赖:不需要安装 protoc 编译器

缺点:性能:由于在运行时解析 .proto 文件,可能会有一些性能开销,但通常可以忽略不计

6. RPC 生命周期:

定义服务的四类方法:
  1. 单项 RPC:

    proto
    复制代码
    rpc SayHello(HelloRequest) returns (HelloResponse) {}
    
  2. 服务端流式 RPC:

    proto
    复制代码
    rpc SayHello(HelloRequest) returns (stream HelloResponse) {}
    
  3. 客户端流式 RPC:

    proto
    复制代码
    rpc SayHello(stream HelloRequest) returns (HelloResponse) {}
    
  4. 双向流式 RPC:

    proto
    复制代码
    rpc SayHello(stream HelloRequest) returns (stream HelloResponse) {}
    

截止时间:客户端可以设置响应的过期时间

RPC 终止

取消 RPC:同步调用不能被取消

元数据集:特殊 RPC 调用对应的信息(键值对形式)

流控制

配置

频道

同步、异步

7. 定义.proto 文件:

关于 Protocol Buffers 的语法教程请看主页对应文章

// 使用 proto3 语法,不指定的话默认 proto2
syntax = "proto3";

// 是否需要生成的类拆分为多个
option java_multiple_files = true;
// 生成的类所属的层级
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

// 定义报名,并于避免命名冲突
package helloworld;

// 定义服务
service Greeter {
  // 定义:
  //   1. 参数
  //   2. 返回类型
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
}

// 结构化数据:使用 message 定义。
message HelloRequest {
  string name = 1; // 字段类型 字段名 = 字段编号;
}

message HelloReply {
  string message = 1;
}

8. 创建 nodejsgRPC 服务端:

// Protocol Buffers 文件
let PROTO_PATH = __dirname + './helloworld.proto';

// 用户实现 gRPC 服务和客户端的核心库
let grpc = require('@grpc/grpc-js');
// 加载 .proto 文件
let protoLoader = require('@grpc/proto-loader');
/**
 * protoLoader.loadSync(PROTO_PATH, { ... }) 方法
 * 从指定的 .proto 文件加载定义,并根据选项配置进行解析。
 * 使用到 protoLoader 一个 Node.js 模块
 */
let packageDefinition = protoLoader.loadSync(
  PROTO_PATH,
  {
    keepCase: true, // 保持字段名称的大小写
    longs: String, // 将 Protocol Buffers 中的 long 类型字段解析为 JavaScript 字符串
    enums: String, // 将枚举类型转换为字符串
    defaults: true, // 为所有字段设置默认值
    oneofs: true // 支持 oneof 字段,这是一种在 Protocol Buffers 中定义的互斥字段
  });
/**
 * grpc.loadPackageDefinition(packageDefinition) 方法
 * 将 @grpc/proto-loader 生成的描述加载到 gRPC 库中,
 * 将加载的 Protocol Buffers 描述转换为 gRPC 服务端可以使用的 JavaScript 对象。
 * 以创建客户端和服务端。
 */
let hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;

/**
 * 定义 RPC 的方法
 */
function sayHello(call, callback) {
  callback(null, { message: 'Hello ' + call.request.name });
}

function main() {
  // 1. 创建 gRPC 服务器实例
  let server = new grpc.Server();
  // 2. 将 Greeter 服务和实现方法添加到 gRPC 服务器中
  server.addService(hello_proto.Greeter.service, { sayHello: sayHello });
  /**
   * 3. 绑定服务器到指定的地址和端口,并使用不安全的凭据(没有 SSL/TLS)
   *    grpc.ServerCredentials.createInsecure(): 创建不安全的服务器凭据
   */
  server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), (err, port) => {
    if (err != null) {
      return console.error(err);
    }
    console.log(`gRPC listening on ${port}`)
  });
}

main();

9. 创建 nodejsgRPC 客户端:

let PROTO_PATH = __dirname + './helloworld.proto';

// 用户实现 gRPC 服务和客户端的核心库
let grpc = require('@grpc/grpc-js');
// 加载 .proto 文件
let protoLoader = require('@grpc/proto-loader');
// 加载 .proto 文件,并且根据配置项进行解析
let packageDefinition = protoLoader.loadSync(
  PROTO_PATH,
  {
    keepCase: true, // 保持字段名称的大小写
    longs: String, // 将 Protocol Buffers 中的 long 类型字段解析为 JS 字符串
    enums: String, // 将枚举类型转换为字符串
    defaults: true, // 为所有字段设置默认值
    oneofs: true // 支持 oneof 字段
  });
// 将生成的描述添加到 gRPC 库中,并输入可以使用的 JS 对象,来创建服务端和客户端
let hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;

function main() {
  // 定义 gRPC 服务器的地址和端口
  let target = 'localhost:50051';
  // 创建一个客户端存根,存根(抽象层):hello_proto.Greeter,来调用远程服务
  let client = new hello_proto.Greeter(target,
    grpc.credentials.createInsecure());
  let user = 'world';
  // 调用远程服务方法
  client.sayHello({ name: user }, function (err, response) {
    console.log('Greeting:', response.message);
  });
}

main();

10. 核心特点

  1. 多语言支持:gRPC 支持多种编程语言,包括 C++, Java, Python, Go, Ruby, Node.js 等,使得跨语言的微服务之间可以无缝通信。
  2. 基于 HTTP/2:gRPC 使用 HTTP/2 协议,这带来了许多优点,如多路复用、流量控制、头部压缩和双向流。
  3. 使用 Protocol Buffers:gRPC 使用 Protocol Buffers(protobuf)作为接口定义语言(IDL)和消息交换格式。这种二进制格式既高效又便于跨语言。
  4. 自动生成代码:通过使用 .proto 文件定义服务和消息类型,gRPC 工具链可以自动生成客户端和服务器端的代码,大大简化了开发工作。
  5. 全双工流式处理:gRPC 支持双向流式处理,这意味着客户端和服务器可以在单个连接上独立地发送和接收多个消息。

11. 典型应用场景

  1. 微服务通信:gRPC 非常适合在微服务架构中用来实现高效的服务间通信。
  2. 实时通信:通过双向流处理,gRPC 可以用于实时聊天、数据流和其他需要低延迟的应用。
  3. 跨语言通信:gRPC 的多语言支持使其成为异构系统中不同语言组件之间通信的理想选择。

http://www.niftyadmin.cn/n/5536230.html

相关文章

C语言实现的冒泡排序算法的示例程序

冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小&…

Kotlin_作用域函数let/also/with/run/apply

文章目录 1.let2.also3.with4.run5.apply6.总结对比 1.let 仅当调用对象不为 null 时执行 name?.let {println("name: $it")it.fun1() // 不需要: 判空 或 ?.it.fun2()it.fun3() } // 最后一行为返回值2.also 跟 let 类似,但返回的是传入对象本身 v…

windows USB 设备驱动开发- 选择备用设备

可以选择接口请求以激活 USB 接口中的备用设置。 客户端驱动程序必须在选择 USB 配置后发出此请求。 默认情况下,选择配置还会激活该配置中每个接口中的第一个备用设置。 每个 USB 配置必须支持一个或多个多个 USB 接口。 每个接口都会公开一个或多个终结点&#x…

暄桐好作业之《临吴昌硕〈萧斋清供图〉》

暄桐是一间传统美学教育教室,创办于2011年,林曦是创办人和授课老师,教授以书法为主的传统文化和技艺,皆在以书法为起点,亲近中国传统之美,以实践和所得,滋养当下生活。      其中“暄桐好作…

简历–求职信–通用

每个毕业生的简历首页大概都会是一封求职信。如果说对求职者的简历正文我们只是浮光掠影看上几眼的话,那么对求职信,简直连浮光掠影都称不上。说实话,我在看求职者简历的时候一般会把这一页翻过去,很少去看。为什么呢?…

探索视频合成新境界:加快加长视频生成,PAB加速与ExVideo延展技术介绍

一、摘要 随着人工智能技术的不断进步,视频合成领域正迎来前所未有的发展机遇。本文介绍近期两项视频生成方向的创新技术:PAB(Pyramid Attention Broadcast)和ExVideo。这两篇文章合在一起主要介绍如何提升视频生成的速度与长度&a…

opencv实现人脸检测功能----20240704

opencv实现人脸检测 早在 2017 年 8 月,OpenCV 3.3 正式发布,带来了高度改进的“深度神经网络”(dnn)模块。 该模块支持多种深度学习框架,包括 Caffe、TensorFlow 和 Torch/PyTorch。OpenCV 的官方版本中包含了一个更准确、基于深度学习的人脸检测器, 链接:基于深度学习…

基于单片机的出租车计价器实验教学案例设计

摘 要 为了让学生加深单片机的理解,加强学生的单片机技术应用、实践动手、创新能力的培养,根据单片机课程设计教学情况精心设计了基于单片机的出租车计价器实验教学案例。本教学案例的基本原理是速度的检测,里程、价格的计算和显示。学生通…