You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js与Express中无额外框架实现服务发现?

用Node.js + Express实现无额外框架的服务发现

服务发现的核心是注册中心管理服务实例的地址与状态,服务启动时注册、定期发心跳保活,客户端从注册中心查询可用服务。以下是具体实现步骤和代码示例:

实现步骤

  • 搭建注册中心:用Express实现HTTP服务,维护服务注册表,提供注册、心跳、查询接口
  • 服务注册逻辑:每个服务启动时向注册中心提交自身信息,定期发送心跳更新状态
  • 服务发现逻辑:客户端向注册中心查询指定服务的可用实例,获取地址后发起调用
  • 过期清理:注册中心定期移除超时未发心跳的服务实例

代码示例

1. 注册中心服务

const express = require('express');
const app = express();
app.use(express.json());

// 服务注册表:key为服务名,value为实例数组
const serviceRegistry = new Map();

// 注册服务接口
app.post('/register', (req, res) => {
  const { serviceName, serviceAddress } = req.body;
  if (!serviceName || !serviceAddress) {
    return res.status(400).json({ error: '缺少serviceName或serviceAddress参数' });
  }

  // 生成唯一实例ID
  const serviceId = `${serviceName}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  const serviceInstance = {
    id: serviceId,
    address: serviceAddress,
    lastHeartbeat: Date.now()
  };

  serviceRegistry.has(serviceName) 
    ? serviceRegistry.get(serviceName).push(serviceInstance)
    : serviceRegistry.set(serviceName, [serviceInstance]);

  res.status(200).json({ serviceId });
});

// 心跳更新接口
app.post('/heartbeat', (req, res) => {
  const { serviceId } = req.body;
  if (!serviceId) return res.status(400).json({ error: '缺少serviceId参数' });

  let updated = false;
  for (const instances of serviceRegistry.values()) {
    const instance = instances.find(inst => inst.id === serviceId);
    if (instance) {
      instance.lastHeartbeat = Date.now();
      updated = true;
      break;
    }
  }

  updated 
    ? res.status(200).json({ message: '心跳更新成功' })
    : res.status(404).json({ error: '服务实例未找到' });
});

// 查询可用服务实例接口
app.get('/discover/:serviceName', (req, res) => {
  const { serviceName } = req.params;
  if (!serviceRegistry.has(serviceName)) {
    return res.status(404).json({ error: '该服务无可用实例' });
  }

  // 过滤超时实例(30秒未发心跳视为不可用)
  const validInstances = serviceRegistry.get(serviceName).filter(inst => {
    return Date.now() - inst.lastHeartbeat < 30000;
  });

  if (validInstances.length === 0) {
    return res.status(404).json({ error: '该服务无可用实例' });
  }

  // 随机选择一个实例(简单负载均衡)
  const randomInstance = validInstances[Math.floor(Math.random() * validInstances.length)];
  res.status(200).json(randomInstance);
});

// 定期清理过期服务(每10秒执行一次)
setInterval(() => {
  const now = Date.now();
  for (const [serviceName, instances] of serviceRegistry.entries()) {
    const validInstances = instances.filter(inst => now - inst.lastHeartbeat < 30000);
    if (validInstances.length === 0) {
      serviceRegistry.delete(serviceName);
    } else {
      serviceRegistry.set(serviceName, validInstances);
    }
  }
}, 10000);

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`注册中心运行在 http://localhost:${PORT}`);
});

2. 服务实例(以用户服务为例)

const express = require('express');
const http = require('http'); // 用原生http模块替代第三方请求库
const app = express();

// 服务配置
const SERVICE_NAME = 'user-service';
const SERVICE_PORT = 3001;
const REGISTRY_URL = 'http://localhost:3000';
let serviceId = null;

// 业务接口
app.get('/api/users/:id', (req, res) => {
  res.json({ id: req.params.id, name: '张三', email: 'zhangsan@example.com' });
});

// 发送HTTP请求的工具函数(原生实现)
function sendHttpRequest(options, data = null) {
  return new Promise((resolve, reject) => {
    const req = http.request(options, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => {
        try {
          const response = JSON.parse(body);
          resolve({ data: response, status: res.statusCode });
        } catch (err) {
          reject(err);
        }
      });
    });
    req.on('error', reject);
    if (data) {
      req.write(JSON.stringify(data));
    }
    req.end();
  });
}

// 注册服务到注册中心
async function registerService() {
  try {
    const options = {
      hostname: 'localhost',
      port: 3000,
      path: '/register',
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }
    };
    const response = await sendHttpRequest(options, {
      serviceName: SERVICE_NAME,
      serviceAddress: `http://localhost:${SERVICE_PORT}`
    });
    serviceId = response.data.serviceId;
    console.log(`服务注册成功,ID: ${serviceId}`);
    // 每15秒发送一次心跳
    setInterval(sendHeartbeat, 15000);
  } catch (error) {
    console.error('服务注册失败:', error.message);
    // 注册失败5秒后重试
    setTimeout(registerService, 5000);
  }
}

// 发送心跳
async function sendHeartbeat() {
  try {
    const options = {
      hostname: 'localhost',
      port: 3000,
      path: '/heartbeat',
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }
    };
    await sendHttpRequest(options, { serviceId });
  } catch (error) {
    console.error('心跳发送失败:', error.message);
  }
}

// 启动服务并注册
app.listen(SERVICE_PORT, () => {
  console.log(`用户服务运行在 http://localhost:${SERVICE_PORT}`);
  registerService();
});

3. 客户端/调用方(以订单服务为例)

const express = require('express');
const http = require('http');
const app = express();

const REGISTRY_URL = 'http://localhost:3000';

// 原生HTTP请求工具函数
function sendHttpRequest(options, data = null) {
  return new Promise((resolve, reject) => {
    const req = http.request(options, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => {
        try {
          const response = JSON.parse(body);
          resolve({ data: response, status: res.statusCode });
        } catch (err) {
          reject(err);
        }
      });
    });
    req.on('error', reject);
    if (data) {
      req.write(JSON.stringify(data));
    }
    req.end();
  });
}

// 订单业务接口,调用用户服务
app.get('/api/orders/:orderId', async (req, res) => {
  try {
    // 从注册中心获取用户服务实例
    const discoverOptions = {
      hostname: 'localhost',
      port: 3000,
      path: '/discover/user-service',
      method: 'GET'
    };
    const serviceRes = await sendHttpRequest(discoverOptions);
    const userServiceAddr = serviceRes.data.address;

    // 解析用户服务地址,拆分hostname和port
    const urlParts = new URL(userServiceAddr);
    const userServiceOptions = {
      hostname: urlParts.hostname,
      port: urlParts.port,
      path: '/api/users/1',
      method: 'GET'
    };
    const userRes = await sendHttpRequest(userServiceOptions);

    res.json({
      orderId: req.params.orderId,
      product: '智能手机',
      userInfo: userRes.data
    });
  } catch (error) {
    res.status(500).json({ error: '服务调用失败', details: error.message });
  }
});

const PORT = 3002;
app.listen(PORT, () => {
  console.log(`订单服务运行在 http://localhost:${PORT}`);
});

扩展优化建议

  • 持久化注册表:将服务信息存储到文件或数据库,避免注册中心重启后数据丢失
  • 服务注销:添加注销接口,服务优雅关闭时主动通知注册中心移除自身实例
  • 负载均衡策略:扩展轮询、权重分配等更复杂的实例选择逻辑
  • 健康检查:除心跳外,可添加服务健康状态上报,更精准判断服务可用性

内容的提问来源于stack exchange,提问作者Jon Sud

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 19:48:25