/sofa-rpc-node

SOFARPC Node is a high-performance, high-extensibility, production-level Nodejs RPC framework.

Primary LanguageJavaScriptMIT LicenseMIT

sofa-rpc-node

SOFARPC Nodejs 实现版本

NPM version Node.js CI Known Vulnerabilities npm download

一、SOFARPC Node 简介

简单说它是 SOFARPC 的 Nodejs 版实现,但本质上它是一个通用的 Nodejs RPC 解决方案。Nodejs RPC 在阿里和蚂蚁内部已经发展了四五年时间,如今广泛应用于各类业务场景,并经历了多次双 11 大促的考验。功能方面从基本的服务发布、寻址、点对点远程调用能力;到各种路由、负载均衡策略;再到故障隔离、熔断等高级功能,已逐渐发展成一个高可扩展性、高性能、生产级的 RPC 框架。

二、模块划分

SOFARPC Node 主要包含了四个子模块,分别是:

  • client: RPC 的客户端实现
  • server: RPC 的服务端实现
  • registry: 服务注册中心抽象及实现(目前提供 zookeeper 实现)
  • test: RPC 测试工具类
.
└── lib
    ├── client
    ├── registry
    ├── server
    └── test

三、快速上手

安装

npm install sofa-rpc-node --save

安装并启动 zookeeper

sofa-rpc-node 默认的注册中心实现基于 zookeeper,所以需要先启动一个 zookeeper 实例

从 Homebrew 安装(macOs)

brew install zookeeper

启动 zk server(默认端口为 2181)

zkServer start

# ZooKeeper JMX enabled by default
# Using config: /usr/local/etc/zookeeper/zoo.cfg
# Starting zookeeper ... STARTED

代码示例

  • 暴露一个 RPC 服务,并发布到注册中心
const { RpcServer } = require('sofa-rpc-node').server;
const { ZookeeperRegistry } = require('sofa-rpc-node').registry;
const logger = console;

// 1. 创建 zk 注册中心客户端
const registry = new ZookeeperRegistry({
  logger,
  address: '127.0.0.1:2181', // 需要本地启动一个 zkServer
});

// 2. 创建 RPC Server 实例
const server = new RpcServer({
  logger,
  registry, // 传入注册中心客户端
  port: 12200,
});

// 3. 添加服务
server.addService({
  interfaceName: 'com.nodejs.test.TestService',
}, {
  async plus(a, b) {
    return a + b;
  },
});

// 4. 启动 Server 并发布服务
server.start()
  .then(() => {
    server.publish();
  });
  • 调用 RPC 服务(从注册中心获取服务列表)
const { RpcClient } = require('sofa-rpc-node').client;
const { ZookeeperRegistry } = require('sofa-rpc-node').registry;
const logger = console;

// 1. 创建 zk 注册中心客户端
const registry = new ZookeeperRegistry({
  logger,
  address: '127.0.0.1:2181',
});

async function invoke() {
  // 2. 创建 RPC Client 实例
  const client = new RpcClient({
    logger,
    registry,
  });
  // 3. 创建服务的 consumer
  const consumer = client.createConsumer({
    interfaceName: 'com.nodejs.test.TestService',
  });
  // 4. 等待 consumer ready(从注册中心订阅服务列表...)
  await consumer.ready();

  // 5. 执行泛化调用
  const result = await consumer.invoke('plus', [ 1, 2 ], { responseTimeout: 3000 });
  console.log('1 + 2 = ' + result);
}

invoke().catch(console.error);
  • 调用 RPC 服务(直连模式)
const { RpcClient } = require('sofa-rpc-node').client;
const logger = console;

async function invoke() {
  // 不需要传入 registry 实例了
  const client = new RpcClient({
    logger,
  });
  const consumer = client.createConsumer({
    interfaceName: 'com.nodejs.test.TestService',
    serverHost: '127.0.0.1:12200', // 直接指定服务地址
  });
  await consumer.ready();

  const result = await consumer.invoke('plus', [ 1, 2 ], { responseTimeout: 3000 });
  console.log('1 + 2 = ' + result);
}

invoke().catch(console.error);
  • 测试 RPC Server 的方法(用于单元测试)
const request = require('sofa-rpc-node').test;
const { RpcServer } = require('sofa-rpc-node').server;
const logger = console;

describe('test/server.test.js', () => {
  let server;
  before(async function() {
    server = new RpcServer({
      logger,
      port: 12200,
    });
    server.addService({
      interfaceName: 'com.nodejs.test.TestService',
    }, {
      async plus(a, b) {
        return a + b;
      },
    });
    await server.start();
  });
  after(async function() {
    await server.close();
  });

  it('should call plus ok', async function() {
    await request(server)
      .service('com.nodejs.test.TestService')
      .invoke('plus')
      .send([ 1, 2 ])
      .expect(3);
  });
});
  • 暴露和调用 protobuf 接口

通过 *.proto 来定义接口

syntax = "proto3";

package com.alipay.sofa.rpc.test;

// 可选
option java_multiple_files = false;

service ProtoService {
  rpc echoObj (EchoRequest) returns (EchoResponse) {}
}

message EchoRequest {
  string name = 1;
  Group group = 2;
}

message EchoResponse {
  int32 code = 1;
  string message = 2;
}

enum Group {
  A = 0;
  B = 1;
}

服务端代码

const antpb = require('antpb');
const protocol = require('sofa-bolt-node');
const { RpcServer } = require('sofa-rpc-node').server;
const { ZookeeperRegistry } = require('sofa-rpc-node').registry;
const logger = console;

// 传入 *.proto 文件存放的目录,加载接口定义
const proto = antpb.loadAll('/path/proto');
// 将 proto 设置到协议中
protocol.setOptions({ proto });

const registry = new ZookeeperRegistry({
  logger,
  address: '127.0.0.1:2181',
});

const server = new RpcServer({
  logger,
  protocol, // 覆盖协议
  registry,
  codecType: 'protobuf', // 设置默认的序列化方式为 protobuf
  port: 12200,
});

server.addService({
  interfaceName: 'com.alipay.sofa.rpc.test.ProtoService',
}, {
  async echoObj(req) {
    req = req.toObject({ enums: String });
    return {
      code: 200,
      message: 'hello ' + req.name + ', you are in ' + req.group,
    };
  },
});
server.start()
  .then(() => {
    server.publish();
  });

客户端代码

const antpb = require('antpb');
const protocol = require('sofa-bolt-node');
const { RpcClient } = require('sofa-rpc-node').client;
const { ZookeeperRegistry } = require('sofa-rpc-node').registry;
const logger = console;

// 传入 *.proto 文件存放的目录,加载接口定义
const proto = antpb.loadAll('/path/proto');
// 将 proto 设置到协议中
protocol.setOptions({ proto });

const registry = new ZookeeperRegistry({
  logger,
  address: '127.0.0.1:2181',
});

async function invoke() {
  const client = new RpcClient({
    logger,
    protocol,
    registry,
  });
  const consumer = client.createConsumer({
    interfaceName: 'com.alipay.sofa.rpc.test.ProtoService',
  });
  await consumer.ready();

  const result = await consumer.invoke('echoObj', [{
    name: 'gxcsoccer',
    group: 'B',
  }], { responseTimeout: 3000 });
  console.log(result);
}

invoke().catch(console.error);

最佳实践

虽然上面我们提供了示例代码,但是我们并不推荐您直接使用该模块,因为它的定位是 RPC 基础模块,只提供基本的 API,对于业务开发者可能并不是非常友好。我们的最佳实践是通过插件将 RPC 能力集成到 Egg.js 框架里,提供更加直观友好的用户接口,让您就像使用本地方法一样使用 RPC。这块也会在近期开放,敬请期待!

三、相关文档

四、如何贡献

请告知我们可以为你做些什么,不过在此之前,请检查一下是否有已经存在的Bug或者意见

如果你是一个代码贡献者,请参考代码贡献规范

五、开源协议

MIT