谈谈京东的微服务框架

最近由于在实习期间接触到了京东的自研服务框架 JSF,目前我写的一些新功能里面调用的下游接口就是 JSF 提供的。现有有很多高效的服务框架,如阿里巴巴的 Dubbo 配合 Apache 的 ZooKeeper,那么为什么京东却自研了 JSF 服务框架呢?于是看了看京东的 JSF 的演化历史,不得不感叹好的架构果然不是一朝一夕就能实现的,都是逐步演变而来的。

一、Dubbo 与 Zookeeper 的组合拳

Dubbo 是阿里巴巴开源的一个高性能的服务框架,Dubbo 使得应用之间可通过高性能的 RPC 实现服务的输出和输入功能,而且可以和 Spring 框架无缝集成。我们可以看看 Dubbo 架构路线图:从单一应用架构 → 垂直应用架构 → 分布式服务架构 → 流动计算架构 ,也可以看我的另一篇文章 《服务拆分方法论》:

从上图可以看出互联网的架构演变经历了单一应用架构、垂直应用架构、分布式服务架构和流动计算架构。服务的粒度越来越细化,多个 Tomcat 的 Servlet 之间相互调用,服务治理的尤为重要,所以一个优秀的架构来替我们解决服务之间的调用问题,而且还要保证高可用,甚至是实现负载均衡。于是 Dubbo 框架就出现了,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册与发现。

下面是 Dubbo 框架的架构图:

mark

节点 说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心
Container 服务运行容器

先用容器 Container,通过 Docker 来启动我们的服务 Provider。Dubbo 本身没有使用任何的注册中心,而是用一种直连的方式进行的。但是,实际上很多时候都是使用 Dubbo + Zookeeper 的方式,使用 zookeeper 作为注册中心,服务调用方 Comsumer 就会去注册中心去订阅 Subscribe 相关服务。注册中心会异步的方式将消费者需要的服务的地址列表推送 Notify 给你。消费者就会将整个地址列表缓存在本地,当业务需求到来时,就会使用地址列表里的地址去请求相关的服务程序。至于 Monitor 顾名思义就是监视器的含义,消费者和提供者会定时将服务的调用次数和被调用的次数发送给监视器。关于 Dubbo 和 Zookeeper 的使用案例在此就不介绍了,可以参考 《Dubbo 一篇文章就够了:从入门到实战》

二、Zookeeper 适合作为注册中心吗

当我们提出 “Zookeeper 作为注册中心合适吗” 这个问题的时候,首先考虑的是具体使用场景,我想说的是,在京东,Zookeeper 真的不合适!

关于服务注册中心重要性不必多说,注册中心相当于是服务提供者和服务调用者之间的引路人,在服务治理中的作用极为重要,注册中心必须必须必须是高可用的,拥有极强的稳定性。那么如何选择服务器注册中心呢?下面列出一些作为注册中心基本要考量点:

  • 服务注册:接收注册信息的方式
  • 服务订阅:返回订阅信息的方式,推还是拉
  • 状态检测:检测服务端存活状态

状态检测尤其重要,因为这个要是检测不准确会误判,导致严重后果,例如 Zookeeper 是强一致性的,Zookeeper 根据服务端注册的临时节点进行状态检测,如果服务端和 Zookeeper 之间的网络闪断,导致 Zookeeper 认为服务端已经死了,从而摘掉这个节点;但是其实客户端和服务端直接的网络是好的,这样就有可能把节点全部摘掉,导致无可用节点。

如果是从开源框架里面选择,那么还需要考量:

  • 成熟度:包括学习成本,社区热度,文档数(盲目追求的不一定是最适合)
  • 维护成本:注册中心维护
  • 数据结构:是否能快速定位结果,是否能遍历
  • 性能和稳定性
  • CAP 原则:CP(关注一致性)还是 AP(关注可用性)

下面比较一下我知道的两个注册中心

ZooKeeper Eureka
一致性 强一致性 弱一致性
数据结构 Tree K/V
通讯协议 TCP HTTP
客户端 ZKClient Eureka-client
CAP 原则 CP AP

注册中心的选型总结:

  • 规模小选择 CP,RPC 框架可以直接接入数据源
  • 规模大选择 AP, RPC 框架不可以直接接入数据源
  • 存在跨机房,跨地域的尽量不要选有强一致性协议的注册中心
  • RPC 框架必须要有注册中心不可用的容灾策略
  • 服务状态检测十分重要

说完了注册中心选型和特点后,我们再来分析 Zookeeper 是否在适合在京东作为服务注册中心,答案是 不适合 。Zookeeper 在大流量场景下不适合作为注册中心,因为 Zookeeper 不是为高可用性设计的。

由于要跨机房容灾,很多系统实际上是需要跨机房部署的。出于性价比的考虑我们通常会让多个机房同时工作,而不会搭建 N 倍的冗余。也就是说单个机房肯定撑不住全流量。由于 Zookeeper 集群只能有一个 master 节点,因此一旦机房之间连接出现故障,Zookeeper master 就只能照顾一个机房,其他机房运行的业务模块由于没有 master 都只能停掉。于是所有流量集中到有 master 的那个机房,于是系统宕机。 这是就是京东在 2015 年的双十一注册中心挂掉的根本原因。后端容器服务重启以后之前缓存的服务地址列表丢失,服务无法调用。而且 Zookeeper 没有动态水平扩展的能力,Zookeeper 作为注册中心称为了双十一这种高并发场景下的瓶颈。

即使是在同一个机房里面,由于网段的不同,在调整机房交换机的时候偶尔也会发生网段隔离的情况。实际上机房每个月基本上都会发生短暂的网络隔离之类的子网段调整。在那个时刻 Zookeeper 将处于不可用状态。如果整个业务系统基于 Zookeeper(比如要求每个业务请求都先去 Zookeeper 获取业务系统的 master 地址),则系统的可用性将非常脆弱。由于 Zookeeper 对于网络隔离的极度敏感,导致 Zookeeper 对于网络的任何风吹草动都会做出激烈反应。这使得 Zookeeper 的服务失效时间比较多,Zookeeper 的不可用将导致整个系统的不可用。

三、理想的服务框架是什么样

mark

接口文档管理

提供一个接口文档管理以及接口查询的入口,可以是一个公共的 WIKI,也可以是独立的系统,等等。这里可以定义接口的文档,包括接口描述,方法定义,字段定义。可以定义接口的 SLA,包括支持的并发数,建议配置是什么。还有就是接口的负责人等一些查询的入口。

配置中心

提供一个配置管理的地方,这里说的配置主要指的是服务相关的一些配置。配置包括分组配置、路由策略、黑白名单、降级开关、限流信息、超时时间、重试次数等等,任何可以动态变更的所有数据。这样服务提供者和服务调用者可以不需要重启自己的应用,直接进行配置的变更。配置中心可以独立于注册中心,也可以和注册中心合并。

监控中心

监控服务关注接口维度,实例(例如所在 JVM 实例)维度的数据。RPC 框架可以定时上报调用次数,耗时,异常等信息。监控中心可以统计出服务质量信息,也可以进行监控报警。

分布式跟踪

区别于监控中心,以调用链的模式对服务进行。RPC 框架作为分布式跟踪系统的一个天然埋点,可以很好的进行一个数据输出。

服务治理

我这边列了常见的服务治理功能,例如:

  • 服务路由:
    • 权重:例如机器配置高的权重高,机器配置低的权重低;
    • IP 路由:例如某几台机器只能调某几台机器;
    • 分组路由:例如自动根据配置调某个分组;
    • 参数路由:例如根据方法名进行读写分类,或者根据参数走不同的节点;
    • 机房路由:例如只走同机房,或者同机房优先。
  • 调用授权:
    • 应用授权:只有授权后的应用才能调这组服务
    • token:只有 token 对的调这组服务
    • 黑白名单:只有名单允许的才能调这组服务
  • 动态分组:
    • 服务端切分组:可以根据分组的情况,对服务提供者进行一个动态的分组调度;
    • 客户端切分组:可以对调用者进行一个分组调度。
  • 调用限流:
    • 服务端限流:服务端基于令牌桶或者漏桶模型进行限流;
    • 客户端限流:根据客户端的标识,进行调用次数限流。
  • 灰度部署:
    • 灰度上线:先启动,验证后在提供服务;
    • 预发标识:表示该服务为预发布服务;
    • 接口测试:方便的提供接口自动化功能测试功能。
  • 服务降级:
    • Mock:出现异常或者测试情况下,返回 Mock 数据;
    • 熔断:客户端超时或者服务端超时;
    • 拒绝服务:服务端压力大时,自动拒绝服务,保护自己。

      网关

RPC 框架大部分场景都是自己调用的,什么时候会需要一个网关呢?

网关可以提供如下功能:

  • 统一的鉴权服务;
  • 限流服务;
  • 协议转换:外部协议转统一内部协议;
  • Mock:服务测试,降级等;
  • 其它一些统一处理逻辑(例如请求解析,响应包装)。

四、京东的 JSF 迭代

JSF 雏形 ——SAF

JSF 早期叫做 SAF,当时的选择如下:

  • RPC 框架:基于 dubbo2.3.2 做配置扩展,以及功能扩展包括:rest(resteasy)、webservice(cxf)、kryo/thrift 序列化、调用压缩等;
  • 注册中心:Zookeeper,RPC 框架直接接入数据源;
  • 监控中心:监控服务 + HBase;
  • 管理平台:读取 Zookeeper 做管理平台,提供基本的上下线、黑白名单等功能。

现在的 JSF 构成

目前的 JSF 几乎是全部自研

  • RPC 框架:轻量级,更佳的性能,兼容旧版本协议;
  • 注册中心:基于 DB 作为数据源,前置 Index 服务;支持十倍接入量;部分逻辑放在注册中心减少客户端负担;
  • 监控中心:监控 Proxy 服务 + InfluxDB(2015 后改为 ElasticSearch);
  • 管理端:基于 DB,功能更强大,提供完善的服务治理管理功能;打通京东应用管理平台,提供应用依赖关系梳理;
  • HTTP 网关:基于 Netty,支持跨语言调用。

目前的 JSF 是基于 DB 做的数据最终一致,也就是 AP 系统。注册中心主要实现的就是服务列表的注册订阅推送,服务配置的获取下发,服务状态的实时查看等功能。注册中心节点是无状态的,可水平扩展的。整个注册中心集群下的所有注册中心几点都是等价的。

JSF 优化与特点

  • 引入 Index 服务概念:该服务就是一个最简单 HTTP 的服务,用于找注册中心节点(同机房或者压力最小或者其它特定场景),可以认为是不会挂的服务,RPC 框架会优先连该服务拿注册中心地址,这样子的好处是注册中心地址变化后,RPC 框架不用修改任何设置;
  • 注册中心内存有服务列表全量缓存,连不上数据库也保证可读;
  • 数据库的数据结构更适合各种维度展示、过滤、分析等,例如根据分组 / IP / 应用 / 机房等不同维度;
  • 注册中心就是个 JSF 服务,监控到压力大即可进行动态水平扩展,不会出现 2015 年双十一那样的事故了;
  • 服务列表推送逻辑改进:例如原来 100 个 Provider,现在加 1 个节点,之前的 SAF 是需要下发 101 个节点,自己判断加了哪个节点,进行长链接建立;现在的改进是:修改为下发一个 add 事件,告知 RPC 框架加了 1 个节点,RPC 框架进行长链接建立;这样做大大减少了推送的数据量;
  • 注册中心与 RPC 框架可各种交互:注册中心和 RPC 框架是长链接,而且 JSF 是支持 Callback 的,注册中心可以调用 RPC 框架进行服务列表变化之外的操作;例如查看状态,查看配置,配置下发等。