JavaScript 微服务:从单体到分布式系统
从单体应用过渡到微服务架构,不仅仅是技术上的重构,更是开发理念的转变。在使用 Node.js 进行高级开发时,我们面临的不仅仅是拆分代码为模块,而是要构建一个稳定、可扩展且容错的分布式系统。在本文中,我们将深入探讨关键的架构决策和实用模式,帮助您使用 JavaScript 构建生产就绪的微服务。
开发人员面临的主要挑战包括:服务间的数据同步、维护事务完整性、服务发现以及高效通信。设计阶段的错误可能导致生产环境中的混乱。让我们看看如何避免这些问题。
1. 服务间通信:RabbitMQ 和 gRPC
在微服务世界中,选择通信协议至关重要。对于 Node.js,最流行的两种方法是:异步消息队列(RabbitMQ)和同步高性能 RPC(gRPC)。
异步队列(RabbitMQ)
用于事件驱动架构。生产者服务不等待响应,而是发布事件。这降低了耦合度。我们来看一个将订单发送到配送服务的示例。
// "订单"服务 — 生产者const amqp = require('amqplib');
async function publishOrderCreated(order) { const connection = await amqp.connect('amqp://rabbitmq:5672'); const channel = await connection.createChannel(); const exchange = 'order.events'; await channel.assertExchange(exchange, 'topic', { durable: true });
channel.publish(exchange, 'order.created', Buffer.from(JSON.stringify(order)), { persistent: true });
console.log(`[x] Sent order: ${order.id}`); await channel.close(); await connection.close();}
// 创建订单时调用publishOrderCreated({ id: '123', userId: '456', amount: 1500 });消费者服务(配送)订阅队列:
// "配送"服务 — 消费者const amqp = require('amqplib');
async function consumeOrders() { const connection = await amqp.connect('amqp://rabbitmq:5672'); const channel = await connection.createChannel(); const exchange = 'order.events'; await channel.assertExchange(exchange, 'topic', { durable: true });
const q = await channel.assertQueue('', { exclusive: true }); await channel.bindQueue(q.queue, exchange, 'order.created');
channel.consume(q.queue, (msg) => { if (msg.content) { const order = JSON.parse(msg.content.toString()); console.log(`[x] Received order: ${order.id}`); // 创建配送的逻辑 channel.ack(msg); } });}
consumeOrders();同步 gRPC
对于需要即时响应的场景(例如,检查用户余额),gRPC 通过 HTTP/2 提供强类型和高速度。定义 proto 文件:
// user.protoservice UserService { rpc GetUserBalance (UserRequest) returns (UserResponse);}
message UserRequest { string user_id = 1;}
message UserResponse { double balance = 1;}在 Node.js 上实现服务器:
const grpc = require('@grpc/grpc-js');const protoLoader = require('@grpc/proto-loader');const PROTO_PATH = './user.proto';
const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true});const userProto = grpc.loadPackageDefinition(packageDefinition).userservice;
function getBalance(call, callback) { const userId = call.request.user_id; // 模拟数据库查询 const balance = userId === '456' ? 5000 : 0; callback(null, { balance });}
const server = new grpc.Server();server.addSe