聊聊 Node.js RPC(二)— 服务发现-阿里云开发者社区

开发者社区> 金融级分布式架构> 正文

聊聊 Node.js RPC(二)— 服务发现

简介: Nodejs 在蚂蚁和阿里已经发展了四、五年时间,从最开始「前端工程师的玩具」,到 Web、BFF 场景的破局,逐步走到线上甚至是一些核心业务,非常不容易。

前言

Nodejs 在蚂蚁和阿里已经发展了四、五年时间,从最开始「前端工程师的玩具」,到 Web、BFF 场景的破局,逐步走到线上甚至是一些核心业务,非常不容易。回头想想 Nodejs 为什么能活下来?依靠的绝不仅仅是:非阻塞I/O、事件驱动、轻量这些官方宣传的特性,我认为更重要一点是我们打通了和 Java 的桥梁,实现了互联互通,这才让它真正融入阿里的技术体系。

伴随蚂蚁 SOFA(Scalable Open Financial Architecture)技术栈的开源,我们也开源了两个 Nodejs RPC 相关模块,希望能填补 Nodejs 社区这块的空白,也将我们几年来在 Nodejs 基础技术的一些经验做个总结和分享。

sofa-bolt-node:蚂蚁通讯协议 Bolt 的 Nodejs 实现

https://github.com/alipay/sofa-bolt-node

sofa-rpc-node:一个通用的 Nodejs RPC 模块

https://github.com/alipay/sofa-rpc-node

上一篇我们介绍了 RPC 通讯协议,它是实现 RPC 的第一步,接下来我们要讨论一下 RPC 的服务发现(Service Discovery)

什么是服务发现?

概念上讲,服务发现就是通过服务唯一标识来获取服务地址的过程,它在 RPC 里扮演了重要角色。下面我用一个点外卖的例子来通俗解释服务发现到底做些什么?它为什么重要?

假设我是一家外卖店的老板,我要考虑的一个问题是:如何让客户能够找到我的店,并且点我的外卖呢?最先想到的是发小广告,客户通过广告里的订餐热线就可以找到我们,这个过程其实就是最简单的服务发现。

这个方案是有效的,但是营运了一段时间后,我发现一些问题:

小广告的传播力有限,投放的精准度也不够,很多人可能随手扔进垃圾桶

客户可能因为丢失卡片或忘记号码而无法下单

一旦留的电话停机了,整个服务不可用

缺货、或者停业还是会接到客户电话

我的生意越来越好,很快开了分店,但是老客户并不知道新店的热线

后面,我听说有一个叫饿了么的点餐平台,抱着试一试态度在上面注册了我的店。没想到这个平台给我带来了大量的订单,而我不再需要到处发小广告,只需要专心做好饭菜、提高服务质量、维护良好的口碑就可以得到稳定的客源。

其次,我也不用担心电话停机、缺货、停业、开新店等服务变更带来的麻烦,我只需要在平台上修改服务信息即可。对于消费者来说他们也不需要收集一大堆外卖卡片,只要安装一个 app,就可以找到丰富的美食、并且可以根据评分选择更加优质的服务。这已经是相当高级的服务发现实现,可以看出无论对于提供者还是消费者,服务发现都是至关重要的。

服务发现的分类

硬负载
硬负载顾名思义是依靠硬件设备做负载,在调用链路上加一个独立部署的硬件设备(一般就是我们所熟知的 F5/LVS/HAproxy 集群),通过它们对后端的服务进行发现,对流量进行负载均衡。

+----------+  invoke   +---------------+        |  Services  |-+
| Consumer | --------> | Load Balancer | -----> |  Providers | |-+
+----------+           +---------------+        +------------+ | |
                                                 |-------------+ |
                                                   +-------------+
  • 优点

存在一个统一的流量集中化节点,可以实现一些全局性的掌控,比如路由、鉴权、安全防控等等

  • 缺点

硬负载设备的成本高,不易维护

在调用主链路上有一定性能损耗

硬负载设备需要实现集群化部署的模式以解决单点故障的问题

软负载
同理,软负载是依靠软件方式进行服务发现和负载均衡,这种方式具有以下特点:

没有了中心化的硬负载设备,把 LB 的功能以 SDK 的模式集成到服务消费方进程里

引入了注册中心(Servcie Registry),用来动态管理所有的服务地址

注册中心不在调用的主链路上,它在旁路

                  +------------------+
                  | Service Registry |
                  +------------------+
                  /                 ^
                 /                   \
             Discover          Register & Keep Alive
               /                         \ 
              /                           \
             v                             \
+----------+                                +----------+
| Consumer | ---- Load Balance & Invoke --> | Provider |
+----------+                                +----------+

优点

Consumer 直接调用 Provider,不再有中间节点

不需独立的负载均衡设备,也就不存在成本和运维的问题

缺点

对 Consumer 端有侵入性,存在接入成本

去中心化,所以弱管控

虽然注册中心在旁路,但也是一个关键的基础设施,需要确保高可用

业界常见的服务发现解决方案
硬负载

阿里云的 SLB

AWS 的 ELB

软负载

Eureka

zookeeper/etcd/consul

阿里和蚂蚁的 ConfigServer

这些方案都各有场景,但在 RPC 里我们通常采用软负载来做服务发现

Node.js 如何做服务发现?

接口抽象
这里主要讨论 Node.js 接入软负载的一些经验和套路。在典型的软负载模式下包含三个角色:

服务提供者(Service Provider)

服务消费者(Service Consumer)

服务注册中心(Service Registry)

Node.js 主要承担前两种角色,所以我们要做的是开发服务注册中心的客户端 SDK。虽然注册中心有多种实现,但我们可以将其接口抽象为:

服务注册

服务注销

服务订阅

服务去订阅

健康检查(可选)

服务治理相关查询(可选)

由此我们可以创建一个 RegistryBase 基类,它的 API 定义如下:

interface RegistryBase {
  async register(config: any): void;

  async unRegister(config: any): void;

  subscribe(config: any, listener: function): void;

  unSubscribe(config: any, listener: function): void;

  async close(): void;
}

针对不同的服务端,会有其对应的实现,比如:ZookeeperRegistry、EurekaRegistry 等等。

实际例子可以参考:ZookeeperRegistry 的实现

https://github.com/alipay/sofa-rpc-node/blob/master/lib/registry/zk/data_client.js

服务发现自己的服务发现
调用注册中心接口本身也需要有一个服务发现的过程,这里感觉有点鸡蛋问题。一般来说这个服务发现我们需要依赖一个更加基础的地址服务(比如:DNS),然后通过轮训或其他策略来更新注册中心的地址列表,最后从中选择一台发起请求,完整的时序图如下:

+--------+               +-----------+                +--------------+
| Client |               |    DNS    |                |   Registry   |
+--------+               +-----------+                +--------------+
    |                          |                              |
    |  -- 1. 查询注册中心地址 --> |                              |
    |  <--- 返回注册中心地址 ---- |                              |
    |                          |                              |
    |                                                         |
    |  ---------------- 2. 注册消费者 / 发布者 ----------------> |   
    |  <-------------------- 注册结果反馈 --------------------- |
    |                                                         |
    |                                                         |
    |  ------------------ 3. 订阅服务发布者 ------------------> |   
    |  <-------------------- 订阅结果反馈 --------------------- |
    |                                                         |
    |                                                         |
    |  <----------------- 4. 推送服务地址 --------------------- |
    |  ----------------------- 反馈收到 ---------------------- |
    |                                                         |

关于健康检查
服务注册中心不同于一般的动态配置系统,因为服务是有状态的(至少包含可用和不可用两种状态)。在服务发布成功以后,还需要持续通过健康检查来确保服务是可用的。

健康检查的方式一般分两种:

1、通过心跳
服务提供方和注册中心通过定时发送心跳包来维护一个长连接,只要长连接不断,就代表服务可用。

优点

对业务透明,实现也比较简单

可以确保至少网络连接是通的

缺点

粒度较粗,无法检查实际业务是否健康

对于注册中心来说需要维护大量长连接

Zookeeper, 阿里的 ConfigServer 都采用这种方式来做健康检查

2、暴露接口用于定时检查
服务提供方单独暴露一个接口给注册中心来轮训,根据接口的返回状态来判断服务是否可用

优点

业务可以自定健康标准,做更精确的健康检查

不用维护长连接

缺点

对业务有一定侵入

K8s 里的 Health Checks 就是这种方式

版权声明:如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:developerteam@list.alibaba-inc.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

SOFAStack™(Scalable Open Financial Architecture Stack)是一套用于快速构建金融级分布式架构的中间件,也是在金融场景里锤炼出来的最佳实践。

官方博客
官网链接