..

Lion 设计与实现

本文是在美团内部面向其他团队的分享

1-介绍

1-1 功能介绍

Lion 是一个面向分布式系统的配置中心,采用中心化数据管理,通过 C/S 架构实现配置的统一管理和实时推送。

应用程序使用 Lion 可实现配置与代码分离、配置变更推送、历史版本管理、灰度发布等功能。通过集中化的在线配置管理,应用可以实现秒级配置变更,并且保证所有节点配置的最终一致性。除此之外,Lion 还提供操作日志查看、灰度发布、配置审核等高级功能。

1-2 SLA 指标

指标 目标 说明
推送成功率 99.9% 配置修改成功后(5s内)所有客户端成功获取到最新配置的比例
推送时效性 <5s 配置从修改成功到客户端获取到新配置的时间

当配置进行一次成功变更之后,Lion 只能保证最终一致性,即所有 Client 最终都能获取到最新配置,不能保证强一致性

1-2 服务端性能指标

指标 数据 说明
单机/集群写性能(8C8G) 700QPS/2500QPS HTTP Open API
单机读性能(8C8G, RT < 100ms) 7000QPS HTTP Open API
单机最优连接数(8C/16G) 8000 服务端长轮询连接
北上同步延迟(1700QPS) TP999<500ms 数据在一侧数据中心发生变更后,同步到另一侧的延迟

2-系统设计

2-1 整体架构

Lion 系统采用异地多活架构:北京 & 上海各部署一套数据中心,支持双写,通过自研同步组件(Consistency)保证两个数据中心间数据一致性。业务配置存储主要使用 MySQL, Redis 作为存储介质(ZK 即将下线)。

用户可以通过管理端(Console)与 HTTP 接口(API)进行配置变更与查询操作,同时 Lion 也支持多语言的 SDK(Java,Node,C++,Golang),便于业务服务进行配置获取与变更监听操作。

模块名称 主要功能 备注
lion-config 用于 client 获取配置与监听配置变更 client 与 config 使用长轮询通信; config 集群北上多机房部署,支持多集群
lion-consistency 不同数据中心间数据同步; 同一数据中心,不同存储介质数据同步 北上双集群部署
lion-meta 实现 config, consistency 服务发现功能 consistency 同步数据分区策略控制; 用于 client 获取相关元数据:config 节点列表,黑白名单项目等 弱依赖 mns 实现节点注册发现功能,在 mns 不可用时降级到自研健康检测策略
lion-client 与 config 服务交互实现配置获取/监听功能 定时从 meta 获取可用 config 节点列表,并通过长轮询访问 config 服务端; config 服务不可用时,从本地文件获取配置(可能是过期数据); 支持多语言 sdk: java, node, go, c++
lion-api 提供 http open api:用于业务方通过 http 实现配置的变更与查询  
lion-console 管理端交互,提供配置增删改查功能  

2-1 配置获取与推送

Client 在获取配置时,加载策略依次为:1. 内存缓存,2. Lion 远程服务端,3. 本地持久化文件。在从服务端某个节点加载失败时,会及时切换到其他可用节点,直到超时;之后会降级到本地文件(可能仍是上次获取到的旧值)。

Config 服务端在收到 Client 获取配置的请求后,依次从:1. 内存缓存,2. Redis,3. DB 中加载配置。配置加载成功之后,需要比较 Client 携带的配置版本号(c_version)与服务端加载到的配置版本号(s_version):

  • 如果 c_verison == 0,则说明 Client 首次获取该配置,此时直接返回服务端配置
  • 如果 c_version != 0 且 c_version < s_version,则说明该配置在 Client 上次加载之后又更新过,此时需要将最新配置返回
  • 如果 c_version != 0 且 c_version >= s_version,则说明 Client 本地配置已经是最新值,此时请求被 hang 住,直到该配置被更新或者请求超时(100s)

2-2 一致性保障

Lion 配置存储分为北京/上海两个数据中心,每个数据中心分别有 DB, Redis, ZK(即将下线) 。为了实现异地多活,需要保证北上数据的一致性。

Lion 采取的是最终一致性,一致性保障由 Consistency 模块维护,整体可以分为两个方面:

  • 不同数据中心间的一致性
  • 同一数据中心内不同存储介质的一致性

2-2-1 同步模型

为了实现数据最终一致性,lion 对每次配置变更都会记录一条 log,被称为同步日志(sync log)。该 log 模型基本如下:

key release_key local_db_status remote_db_status zk_status redis_status status
配置唯一标识 配置变更唯一标识 同侧 db 状态:0 未同步,1 同步成功 异地 db 状态:0 未同步,1 同步成功 同侧 zk 状态:0 未同步,1 同步成功 同侧 redis 状态:0 未同步,1 同步成功 本次变更同步状态: 0 未加载,1 已加载未同步,2 同步成功

status 字段用于标识同步流程是否全部成功,如果同步失败,则会重试直到同步成功。

不管是通过 http api 接口还是管理端变更配置,只会更新同侧的 db,并不会同步更新同侧数据中心内的 zk, redis(提高接口性能)。每次配置变更产生的 log 为:

local_db remote_db zk redis status
1 0 0 0 0

2-2-2 同步流程

同步流程步骤分为:

  • log 加载:将未同步完成的日志从 db 中加载到内存队列中
  • 异地同步:优先将 log 批量同步到异地;异地同步成功后将 log hash 到内存队列中
  • 本地同步:将 log 顺序同步到本地 db, redis, zk 中

结合 log 中各个同步状态进一步分析:

  1. 本地 db 同步:如果本地 db 未同步,则首先同步 db。如果同步成功,则 log.local_db = 1。(对 log 产生侧来说,local_db 默认 1,即默认不同步)
  2. 异地同步:该流程为异步操作,仅仅是将 log 发送到异地并被接收,并在稍后将配置回写到 db, redis, zk。如果同步成功,则 log.remote_db = 1.
  3. 本地 redis 同步:如果同步成功,则 log.local_redis = 1.
  4. 本地 zk 同步:如果同步成功,则 log.local_zk = 1.

3-可用性

承诺 Lion 服务的可用性不低于 99.99%。

模块名称 集群分布 外部依赖 内部依赖 RTO 故障时主动切换 SLA
lion-consistency 两地多机房 MySQL、S3、专线(弱)、DNS(弱) lion-meta 120s 0s
lion-api 两地多机房 MySQL、S3、Redis(弱)   120s 0s
lion-config 两地多机房 MySQL、S3、Redis(弱) lion-meta 120s 0s
lion-console 两地多机房 MySQL lion-api 60s 0s
lion-meta 两地多机房 MySQL、MNS(弱)   60s 0s
lion-client - DNS lion-config、lion-meta(弱) T_{断连感知}=120s 3s (timeout)+3s * (0~N) (N:异常 Config 数目)

config 服务北上两地多机房部署,支持弹性伸缩:

  • 对于异常 config 节点,meta 会及时剔除,保证下发给 client 节点列表的可用性
  • client 在请求 config 服务端获取配置时,优先访问同地域节点;同地域无可用节点时,访问异地节点

如果所有 config 节点均不可用,则降级到本地持久化文件

config 服务加载配置时,强依赖 db,s3(文件配置),弱依赖 redis:

  • redis 不可用时,对配置获取 & 推送没有影响
  • db,s3 不可用时,client 无法获取到最新配置(通过 config 内存缓存可以获取到旧配置)

consistency 服务北上两地多机房部署,支持弹性伸缩:

  • 同步 log 加载强依赖 db,db 不可用时,同步任务无法执行
  • consistency 节点异常时,meta 会及时剔除,并更新同步策略(尽可能降低节点变更对同步延迟的影响)

4-隔离性

配置隔离可以从两个方面来看:

  1. 配置获取:客户端通过长轮询访问 lion-config 获取目标配置
  2. 配置同步:配置变更在北上两地/同一数据中心内保持一致

4-1 配置获取

config 服务支持多集群,不同集群管理特定业务配置。对于一些配置容量大/稳定性要求高的业务,会拆分出独立集群,其他业务共享默认集群。

config 集群 集群所属项目
zebra {ds, group-ds}
buffalo {buffalo-reader-task,buffalo-client,buffalo-writer-task}
waf {com.sankuai.sec.waf.manage}
default {others}

各个集群配置管理相互独立,业务流量隔离

  • buffalo 配置获取只会访问 buffalo 集群,同时 buffalo 集群只管理 buffalo 相关的配置

不过,各个 config 集群依赖的底层存储(redis, db)仍共用一个集群

4-2 配置同步

由于不同业务对推送延迟的敏感程度不同,因此我们将配置同步分为两种:1. 普通配置,2. 高优先级配置;这两种配置同步流程相互隔离。

通过将不同配置的同步流程隔离,使得普通配置的堆积并不会对高优先级配置的同步流程产生影响。

4-2-1 局限

不管是配置同步还是配置推送,目前都是在业务逻辑层进行隔离,在底层存储方面,所有业务共享一套存储。因此,目前针对业务隔离仍存在一定的局限性,如 db 集群异常时,所有业务方都会受到影响。

不过,鉴于当前单集群存储容量在中短期内仍能支撑业务增长,并且存储层的可用性也能满足 Lion 的需求,因此短期内仍会保持现有隔离方案。

5-可观测性

5-1 推送链路

对于每次配置变更的推送情况,Lion 提供了推送链路的功能帮助用户排查配置的推送情况,包括:

  • 推动成功率,推送延迟,每台机器的推送详情等

不过,该功能对 lion-client 版本有所要求(>=0.8.15.4),目前覆盖率 >80%(各版本分布情况).

5-2 数据同步

对于配置同步,有两个关键指标:1. 同步延迟,2. 同步成功率。

  • 同步延迟的大小与配置大小,配置的变更频率,同步批次大小,同步任务间隔等有关。经过测试,对于小配置(<1KB),在 1700 QPS 下延迟 TP999<500ms,最大延迟 1.8s。
  • 通过一致性大盘可以看到,数据一致性 >99.99%

6-FAQ

  1. 业务在接入 lion 时,会按照 key 的粒度编码获取配置,如果业务方代码中获取引用了大量 key,是否会导致不同 key 每次都通过 http 访问 config 服务端是否导致耗时增大?
    • 如果同一个项目调用了多个 key(>5),那么配置获取粒度会由 key 升级为项目/分组,也就是说会从 config 服务端一次获取整个项目/分组的配置;这样当访问其他 key 时,只需要从本地内存缓存即可获取,减小了配置获取耗时。
  2. 如果 lion 服务端均不可用,对业务方有何影响?
    • 从配置获取来说,在服务端完全不可用时会降级到本地持久化文件,可能会获取到已经失效的配置,并且不会感知到最新的配置变更
  3. 除了服务端内部一致性,如何保证客户端与服务端间的数据一致性?
    • 增量同步:客户端通过长轮询周期性地与服务端通信,请求信息携带了当前客户端配置的最新版本号,服务端通过比较版本号来判断客户端的配置是否过期,如果过期则更新客户端
    • 全量同步:客户端定时全量同步本地配置,此时直接从服务端获取全量配置并更新本地过期配置
  4. 北上数据同步时,如何保证同一条 log 不会被重复同步?
    • log 通过 status 区分同步状态;同步任务只会加载 status = 0 的记录,确保同一条 log 不会被重复加载
    • log 被加载后会进行分区,保证不同 consistency 节点不会同步相同的 log
  5. 如何保证配置变更一定会被同步成功?
    • 有三种同步机制:1. log 创建后被立即同步;2. 同步失败后会进行有补扫任务进行再次同步(50次);3. 北上定时检测机制,对于不一致的数据会重新触发同步流程。
  6. 北上同步时,如何解决数据冲突?
    • 冲突解决方式为:最后写胜出(lrw),以最新版本为主。不过,两地同时写入的概率较低。
  7. 在上海侧的一次变更,需要多长时间才能推送到北京侧业务机器?
    • 北上同步延迟 TP999 < 500ms,我们承诺 99.9% 的配置会在 5s 内会全量推送到所有机器(具体每次配置变更推送延迟可以查看:Lion 推送链路)

7-未来规划

中短期来看,Lion 规划主要有:

  • 提升用户体验:丰富多语言客户端功能,提升MWS易用性
  • 提升系统可观测性:增强配置链路跟踪能力,提升监控能力,
  • 提升容灾能力
  • 提升系统效率:对齐业界同类系统,提高系统支撑能力、提升系统效率
  • 提升平台化能力:增强存储及流量隔离,增强集群监控、运营统计,开放集群拆分