一、背景
货拉拉为微服务架构提供了多种服务发现和服务路由的能力,截止目前(2023年初),服务发现提供了通过 Consul 和域名降级两种发现方式,服务路由已提供了灰度路由、多泳道路由等多种路由能力。
然而随着业务发展和基础设施建设,对服务发现与路由能力有了更灵活的需求,目前已经陆续收集到如下诉求:
• 服务分多个分组发布
○ 不同分组运行不同版本的业务代码,从而实现业务灰度或业务 AB 版本
○ 不同分组有着不同的稳定性要求,例如部署着不同的实例数等
• 服务调用方可以根据不同的场景需要,选择调用不同的服务端分组
• 不同的业务场景可以通过网关和 SOA 路由配置或自定义灵活的路由策略
• 等等
经过对货拉拉现状以及上述需求的详细了解,并结合业界对微服务路由的优秀实践,我们决定引入“应用多分组架构”来满足业务的需求
二、基本原理
2.1
架构基本原理
应用多分组架构的基本原理是如何解决下面三个问题
服务如何多分组部署?
怎么进行多分组的服务发现与路由?
路由策略如何满足不同的业务场景?
2.1.1 应用多分组部署
应用多分组架构基础是需要支持同一 appid 的分分组部署,并且不同分组可以配置不同的发布配置,同时运行在不同分组的进程也可以感知到自己所属的分组
2.1.2多分组服务发现与路由
SOA SDK 通过感知服务所属分组配置进行 Consul 打标,从而提供统一的应用多分组发现和路由机制
2.1.3 路由策略
多分组架构将通过两种方式提供灵活的路由策略,满足不同业务场景:
1. 多分组架构内置路由策略:将提供静态路由、协议头路由、开关、黑白名单、降级等路由策略,满足大部分情况下的多分组诉求
2. 提供路由策略扩展机制,满足业务自定义策略,例如
a. SOA 的 SPI 扩展机制
b. LAPIGateway 的插件机制
2.1.4 其他问题
除了上述三个基本问题,多分组架构还应该考虑如下问题:
• 多分组架构涉及的环境和元数据统一规范
• 多分组架构的可观测性设计
• 配置中心支持不同分组差异化配置
• 开放相关能力给业务方组合自定义场景问题等等
2.2
可以解决的问题
应用多分组架构可以解决的问题如下:
• 同一个 appid 不拆分的情况下,在不同分组运行不同版本的业务代码
• 相同版本的代码在不同分组部署拥有不同的配置,例如:配置中心、实例数量、 HPA 规则等等
• 服务调用方可以根据不同的场景需要,选择调用不同的服务端分组,也可以通过网关 + SOA 路由配置灵活的多分组路由策略
三、架构设计
应用多分组架构涉及的范围集中在微服务相关组件,不涉及流量接入层和数据层
• ① 链路通过网关路由策略控制分组匹配
• ② 链路通过 SDK 路由策略控制分组匹配
分组是应用下一级的概念,分组属于应用,下图虚线圈定部分仅为应用下同名的逻辑概念
3.2
多分组路由
只有满足了多分组路由规则的请求才会路由到对应的分组服务,多分组路由核心约定包括如下两个要素
1. 通过上述服务注册机制已经为服务实例进行打标 Consul tag(hll.group),用于服务发现
2. 分组路由只提供一级路由(A -> B),不涉及协议头和链路透传能力,同时分组路由提供静态路由和扩展点
a. SDK 提供静态服务引用参数 group(非必填)
b. 多分组路由
3.1
多分组服务注册
SOA 服务在注册时会额外写入了服务实例的分组标识,将通过新增 Consul tag(hll.group) 承载,空值不写入 tag,consul tag 现状如下图所
SOA 获取分组元数据依赖于对服务实例分组的感知能力,例如:通过环境变量
hll_group
分组路由流程图如下图
下图逻辑均在 SOA 客户端执行
同时为了方便了解多分组路由与其他路由关系以及后续的扩展机制,可以了解多分组路由在 SOA SDK 内部位置
3.3
分组路由扩展机制
如上多分组架构整体方案所述,SOA 框架需要提供路由策略扩展机制,满足业务自定义策略
• 分组路由扩展获取的分组优先级高于内置机制
• 分组路由扩展机制设计原则
○ 【输出】对默认路由策略干预尽可能收敛
▪ 只能改变本次请求的分组标识选择
▪ 存在多个 SPI 时,高优先级 SPI 决策成功阻塞后续 SPI 执行
○ 【输入】尽可能多的提供路由决策所需要的数据,比如:当前服务信息、协议头、注册信息等等
▪ List<Invoker<T>> invokers: 下游节点信息
▪ Invocation invocation: 调用上下文信息
▪ MetaContextUtil、RpcContext 等工具类
• 扩展方式按 SOA 惯例,使用 SPI 机制
Java// SPI 伪代码@SPI(internal = false)public interface GroupRouterExt { Optional<PinnedGroup> getGroup(List<Invoker<T>> invokers, Invocation i);}// 内部类public class PinnedGroup { // 分组标识 private String group; // 无匹配分组时是否允许跨分组调用,默认 false private boolean cross;}
四、应用场景
SOA服务多分组在开发中有许多应用场景,以下是一些常见的应用场景:
4.1
功能模块划分
将服务实例按照不同的功能模块进行划分,每个功能模块对应一个服务分组。这样可以将不同的功能模块进行解耦,提高系统的可维护性和可测试性。举例:XXX 服务提供 A、B两个功能接口,针对不同的上游提供不同的分组
4.2
业务领域划分
根据业务的不同领域将相关的服务实例划分到不同的服务分组中。这样可以使得每个服务分组的资源更有效利用,提高系统的吞吐量。举例:假如user 和 driver 的token provider服务的代码是同一套并且只申请一个 appid,那么我们可以针对上游的用户和司机的token consumer服务提供两个分组服务
4.3
服务SLA
将相同服务 SLA 的服务实例划分到同一个服务分组中,方便进行隔离和优化。举例:XXX 服务给 QPS 较高和 RT 较小的 APP 端接口服务、QPS 较小和 RT较高的运营端接口服务提供不同的分组
4.4
技术栈划分
根据不同的技术栈将相关的服务实例划分到不同的服务分组中。例如,将给前端调用的服务服务实例、给后端内部调用服务实例和给数据服务调用的服务实例分别划分到不同的服务分组中,每个分组使用不同的实例数量,提高资源的利用率
4.5
安全和权限管理
将具有相似安全需求或权限管理需求的服务实例划分到同一个服务分组中,方便进行统一的安全策略和权限控制
ps:代码相似度较高或者代码放在同一个APPID 中更好维护,那么我们可以在不拆分服务(APPID)的情况下,可以使用 SOA多分组方式给不同的上游提供服务