阶段四:gRPC
gRPC是Google开源的高性能RPC框架,基于HTTP/2和Protocol Buffers,天生适合微服务间通信。与REST的文本JSON不同,gRPC使用二进制协议,性能提升10倍以上。本课从gRPC的架构、核心概念到第一个gRPC服务的实现。
| 组件 | 说明 |
|---|---|
| Protocol Buffers | 接口定义语言(IDL)和序列化格式 |
| HTTP/2 | 传输层协议,支持多路复用和流控 |
| gRPC核心 | 通道、存根、服务端框架 |
| 代码生成器 | 根据.proto自动生成客户端/服务端代码 |
| 维度 | REST | GraphQL | gRPC |
|---|---|---|---|
| 协议 | HTTP/1.1 | HTTP/1.1+ | HTTP/2 |
| 数据格式 | JSON(文本) | JSON(文本) | Protobuf(二进制) |
| 序列化速度 | 慢 | 慢 | 快10x+ |
| 代码生成 | OpenAPI(可选) | 代码优先 | ✅ 内置 |
| 流式支持 | ❌ | Subscription | ✅ 四种模式 |
| 浏览器支持 | ✅ | ✅ | ⚠️ 需gRPC-Web |
| 人类可读 | ✅ | ✅ | ❌ 二进制 |
| 适用场景 | 外部API | 复杂前端 | 微服务间通信 |
使用Node.js实现一个商品管理的gRPC服务。
// 文件1: product.proto - 接口定义
syntax = "proto3";
package product;
// 商品服务
service ProductService {
// 一元RPC:获取单个商品
rpc GetProduct(GetProductRequest) returns (Product);
// 一元RPC:列出商品
rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
// 一元RPC:创建商品
rpc CreateProduct(CreateProductRequest) returns (Product);
// 服务端流:批量导出
rpc ExportProducts(ExportRequest) returns (stream Product);
// 客户端流:批量导入
rpc ImportProducts(stream Product) returns (ImportResponse);
// 双向流:实时聊天
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
// 消息定义
message Product {
int32 id = 1;
string name = 2;
string category = 3;
double price = 4;
int32 stock = 5;
string status = 6;
}
message GetProductRequest {
int32 id = 1;
}
message ListProductsRequest {
string category = 1;
int32 page = 2;
int32 page_size = 3;
}
message ListProductsResponse {
repeated Product products = 1;
int32 total = 2;
}
message CreateProductRequest {
string name = 1;
string category = 2;
double price = 3;
int32 stock = 4;
}
message ExportRequest {
string category = 1;
}
message ImportResponse {
int32 success_count = 1;
int32 fail_count = 2;
repeated string errors = 3;
}
message ChatMessage {
string user = 1;
string message = 2;
}
// 文件2: grpc-server.js - gRPC服务端
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// 加载proto定义
const packageDef = protoLoader.loadSync('product.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const productProto = grpc.loadPackageDefinition(packageDef).product;
// 数据
const products = [
{ id: 1, name: '机械键盘', category: '外设', price: 599.0, stock: 120, status: 'active' },
{ id: 2, name: '无线鼠标', category: '外设', price: 199.0, stock: 250, status: 'active' },
{ id: 3, name: '4K显示器', category: '显示器', price: 2999.0, stock: 45, status: 'active' },
{ id: 4, name: 'USB扩展坞', category: '配件', price: 399.0, stock: 0, status: 'inactive' },
{ id: 5, name: '降噪耳机', category: '音频', price: 1299.0, stock: 60, status: 'active' },
];
let nextId = 6;
// ===== 服务实现 =====
// 获取单个商品
function GetProduct(call, callback) {
const product = products.find(p => p.id === call.request.id);
if (!product) {
return callback({ code: grpc.status.NOT_FOUND, message: `商品 ${call.request.id} 不存在` });
}
callback(null, product);
}
// 列出商品
function ListProducts(call, callback) {
const { category, page, pageSize } = call.request;
let result = [...products];
if (category) result = result.filter(p => p.category === category);
const p = page || 1;
const ps = pageSize || 20;
const total = result.length;
result = result.slice((p - 1) * ps, p * ps);
callback(null, { products: result, total });
}
// 创建商品
function CreateProduct(call, callback) {
const { name, category, price, stock } = call.request;
if (!name) {
return callback({ code: grpc.status.INVALID_ARGUMENT, message: 'name不能为空' });
}
const product = { id: nextId++, name, category: category || '未分类', price: price || 0, stock: stock || 0, status: 'active' };
products.push(product);
callback(null, product);
}
// 服务端流:批量导出
function ExportProducts(call) {
const { category } = call.request;
const filtered = category ? products.filter(p => p.category === category) : products;
filtered.forEach((product, i) => {
// 模拟逐条发送
setTimeout(() => {
call.write(product);
if (i === filtered.length - 1) call.end();
}, i * 100);
});
if (filtered.length === 0) call.end();
}
// 客户端流:批量导入
function ImportProducts(call, callback) {
let successCount = 0;
let failCount = 0;
const errors = [];
call.on('data', (product) => {
try {
if (!product.name) {
failCount++;
errors.push(`商品名称为空`);
return;
}
products.push({ ...product, id: nextId++, status: 'active' });
successCount++;
} catch (e) {
failCount++;
errors.push(e.message);
}
});
call.on('end', () => {
callback(null, { successCount, failCount, errors });
});
}
// 双向流:聊天
function Chat(call) {
call.on('data', (msg) => {
console.log(`💬 ${msg.user}: ${msg.message}`);
// 回显+服务端消息
call.write({ user: 'Server', message: `收到 "${msg.message}",已处理` });
});
call.on('end', () => call.end());
}
// ===== 启动服务 =====
const server = new grpc.Server();
server.addService(productProto.ProductService.service, {
GetProduct,
ListProducts,
CreateProduct,
ExportProducts,
ImportProducts,
Chat,
});
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), (err, port) => {
if (err) { console.error(err); return; }
console.log(`🚀 gRPC服务运行在端口 ${port}`);
server.start();
});
// 文件3: grpc-client.js - gRPC客户端
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDef = protoLoader.loadSync('product.proto', {});
const productProto = grpc.loadPackageDefinition(packageDef).product;
const client = new productProto.ProductService(
'localhost:50051',
grpc.credentials.createInsecure()
);
// 一元RPC调用
console.log('=== 获取商品 ===');
client.GetProduct({ id: 1 }, (err, product) => {
if (err) return console.error('错误:', err.message);
console.log('商品:', product);
});
// 列表
client.ListProducts({ category: '外设', page: 1, pageSize: 10 }, (err, res) => {
if (err) return console.error('错误:', err.message);
console.log(`\n=== 外设商品(${res.total}条) ===`);
res.products.forEach(p => console.log(` ${p.name} ¥${p.price}`));
});
// 创建
client.CreateProduct({ name: '蓝牙音箱', category: '音频', price: 299.0, stock: 50 }, (err, product) => {
if (err) return console.error('错误:', err.message);
console.log('\n=== 创建商品 ===');
console.log('新商品:', product);
});
// 服务端流
console.log('\n=== 导出商品 ===');
const exportStream = client.ExportProducts({ category: '外设' });
exportStream.on('data', (product) => console.log(` 导出: ${product.name}`));
exportStream.on('end', () => console.log('导出完成'));
// 双向流
console.log('\n=== 聊天 ===');
const chat = client.Chat();
chat.on('data', (msg) => console.log(`${msg.user}: ${msg.message}`));
chat.write({ user: '客户端', message: '你好!' });
setTimeout(() => { chat.write({ user: '客户端', message: '查询商品' }); }, 500);
setTimeout(() => chat.end(), 1500);
# 安装依赖
npm install @grpc/grpc-js @grpc/proto-loader
# 启动服务端
node grpc-server.js
# 另一终端运行客户端
node grpc-client.js
=== 获取商品 ===
商品: { id: 1, name: '机械键盘', category: '外设', price: 599, stock: 120, status: 'active' }
=== 外设商品(2条) ===
机械键盘 ¥599
无线鼠标 ¥199
=== 创建商品 ===
新商品: { id: 6, name: '蓝牙音箱', category: '音频', price: 299, stock: 50, status: 'active' }
=== 导出商品 ===
导出: 机械键盘
导出: 无线鼠标
导出完成
| 模式 | 请求 | 响应 | 类比 | 场景 |
|---|---|---|---|---|
| 一元RPC | 1个 | 1个 | HTTP请求 | CRUD操作 |
| 服务端流 | 1个 | 流(N个) | 订阅 | 导出、日志 |
| 客户端流 | 流(N个) | 1个 | 上传 | 批量导入 |
| 双向流 | 流 | 流 | WebSocket | 聊天、实时 |
| 指标 | JSON | Protobuf | 差距 |
|---|---|---|---|
| 序列化大小 | 100% | ~30% | 小3-5倍 |
| 序列化速度 | 基准 | 5-10x | 快5-10倍 |
| 反序列化速度 | 基准 | 5-10x | 快5-10倍 |
| 网络传输 | 文本 | 二进制 | 带宽省60%+ |
| 类型安全 | 运行时 | 编译时 | 更早发现错误 |
// Node.js性能对比测试
const protobuf = require('protobufjs');
// JSON序列化
const jsonObj = { id: 1, name: '机械键盘', category: '外设', price: 599.0, stock: 120 };
const jsonStart = process.hrtime.bigint();
for (let i = 0; i < 100000; i++) {
const serialized = JSON.stringify(jsonObj);
JSON.parse(serialized);
}
const jsonDuration = Number(process.hrtime.bigint() - jsonStart) / 1e6;
// Protobuf序列化
const root = protobuf.loadSync('product.proto');
const Product = root.lookupType('product.Product');
const msg = Product.create(jsonObj);
const pbStart = process.hrtime.bigint();
for (let i = 0; i < 100000; i++) {
const serialized = Product.encode(msg).finish();
Product.decode(serialized);
}
const pbDuration = Number(process.hrtime.bigint() - pbStart) / 1e6;
console.log(`JSON: ${jsonDuration.toFixed(0)}ms`);
console.log(`Protobuf: ${pbDuration.toFixed(0)}ms`);
console.log(`差距: ${(jsonDuration / pbDuration).toFixed(1)}x`);
// 典型结果:
// JSON: 450ms
// Protobuf: 85ms
// 差距: 5.3x
定义user.proto,实现GetUser、ListUsers、CreateUser、UpdateUser、DeleteUser五个一元RPC方法。
安装grpcurl工具,用它测试本课的gRPC服务。提示:grpcurl -plaintext localhost:50051 list