第4章:SDK模块化设计——核心层、路由层、配置层、监控层

很多团队做SDK,一开始就是个“大泥球”。

所有代码揉在一起,灰度逻辑、配置加载、上报埋点全在一个类里。嗯,我见过最夸张的一个文件有8000行。改个配置读取方式,能把流量分发逻辑搞挂。说白了,模块化不是“好看”,是让你在灰度发布失败时,能精准切走那根出问题的神经,而不是把整个胳膊砍掉。

4.1 为什么必须是四层?

我个人习惯把SDK拆成四层:核心层、路由层、配置层、监控层。每一层只干一件事,层间通过定义好的接口通信。为什么是四层,不是三层或五层?

你想想看,灰度发布中流量从哪里来、往哪里去、怎么判断用哪个版本、出事了怎么知道——刚好对应这四个问题。少一层,责任就模糊了。多一层,调用链就复杂了。

核心设计原则: 每一层的修改都不应该影响其他层。

我曾经遇到一个事故:配置层改了JSON解析库,结果核心层的初始化因为依赖冲突全挂了。从那以后,我规定配置层只能用纯数据接口,不允许引入任何业务依赖。

4.2 核心层(Core)

核心层是SDK的心脏。它封装了最底层的业务能力——比如网络请求、数据加解密、本地缓存。这一层不关心灰度,不关心版本号,只负责把事情“做对”。

举个例子。我们做推送SDK,核心层就提供三个能力:

  • 建立长连接——TCP连接的建立、心跳、重连
  • 消息编解码——Protobuf序列化/反序列化
  • 本地存储——把离线消息写到SQLite

灰度发布的时候,路由层告诉核心层“你现在用V2.1的协议版本”,核心层只需要知道怎么编解码V2.1的包,不需要知道为什么要用V2.1。

// 核心层接口示例
public interface CoreEngine {
    // 建立连接,不关心灰度
    boolean connect(String host, int port);
    // 发送数据,只关心编解码
    boolean send(Message msg);
    // 注册监听,只关心事件通知
    void registerListener(CoreListener listener);
}
避坑指南: 核心层千万不要引用配置层的任何类。我曾经让核心层直接读配置文件,结果配置层改格式时,核心层也得跟着改。改成接口注入后,这个问题就彻底消失了。

4.3 路由层(Router)

路由层是灰度发布的“大脑”。它的职责很单一:根据规则,决定流量走哪个版本的逻辑。

我在项目中遇到过一种常见场景:同一个用户,在iOS和Android上需要走不同的灰度比例。路由层就是干这个的——它读取配置层的规则,对每个请求做判断,然后把流量分到对应的核心层实例上。

路由层常见的判断依据:

  1. 用户ID哈希——通过Hash取模,实现一致性分流
  2. 设备类型——Android/iOS/鸿蒙各自不同版本
  3. 地域——先灰度某个城市
  4. 自定义标签——比如白名单用户
路由策略 适用场景 注意点
Hash取模 全量灰度,比例固定 要确保Hash结果稳定,不要因字段变化导致分流漂移
标签匹配 内测、白名单 标签数据要同步到客户端,注意隐私合规
比例采样 渐进式灰度 配合监控层,当错误率超过阈值时自动切回
SDK 模块化四层架构 配置层 (Config) 路由层 (Router) 核心层 (Core) 监控层 (Monitor) — 贯穿全流程 各层职责说明: 1. 配置层:拉取远端配置,解析灰度规则,提供纯数据接口 2. 路由层:根据规则分流,不关心业务逻辑,只做流量决策 3. 核心层:执行具体业务,不关心灰度,只保证功能正确 4. 监控层:采集各层指标,触发告警和自动回滚(贯穿全流程) 数据流:配置层 → 路由层 → 核心层(监控层全链路采集)
注意: 路由层的判断逻辑必须在编译时就固定。不要在运行时动态加载路由规则,这会导致安全漏洞。所有路由策略列表,必须在SDK启动时由配置层一次性注入。

4.4 配置层(Config)

配置层是整个灰度体系的“遥控器”。它负责从远端拉取灰度规则,缓存到本地,然后在适当时机推给路由层。配置层不能挂,挂了SDK就失去了调整能力。

我设计的配置层通常包含:

  • 配置拉取——HTTP轮询或长连接推送
  • 本地缓存——防止网络不通时SDK“失忆”
  • 版本管理——配置自身的版本号,避免重复应用

配置层最容易被忽视的问题是:配置格式的前向兼容。我记得有次团队新增了一个字段,老版本的SDK解析报错直接挂了。从那以后,配置层必须用宽松模式解析JSON——不认识的就跳过。

// 配置层接口示例
public interface ConfigProvider {
    // 获取灰度规则列表,返回不可修改的副本
    List<GrayRule> getGrayRules();
    // 注册配置变更监听器
    void registerListener(ConfigChangeListener listener);
    // 手动触发一次拉取
    void refresh();
}
实操经验: 配置层每次拉取不要超过10KB。我见过一个团队把全量用户标签塞进配置,每次更新50KB,结果用户频繁掉线。配置只传规则,不要传数据。

4.5 监控层(Monitor)

监控层是SDK的“体检报告”。没有它,灰度就是盲人摸象。监控层采集三类核心指标:成功率、延迟、稳定性。

我曾经遇到过一个问题:灰度版本成功率99.9%,但是平均延迟从200ms飙到了2s。如果没有延迟监控,这问题可能要等用户投诉才知道。所以监控层必须三个维度都覆盖。

监控层的核心能力:

  • 指标采集——埋点上报,涵盖核心层、路由层、配置层的所有关键路径
  • 异常告警——当某指标超过阈值时,自动触发告警
  • 自动回滚——结合路由层,当错误率超过预设值时自动切回上一个稳定版本
指标类型 采集点 告警阈值
成功率 核心层的请求/响应 低于99%且持续1分钟
延迟 核心层的耗时 P99超过500ms
稳定性 SDK崩馈率 单日崩馈率超过0.1%

关于自动回滚,我有个惨痛教训。一开始我设了“失败率超过5%就回滚”,结果某个CDN节点波动导致全量回滚,事故反而扩大了。现在我的做法是:回滚前观察至少2个独立数据中心的数据,确认不是偶发问题再执行。

再次强调: 监控层必须独立于核心层部署。如果核心层崩馈了,监控层还能上报最后的崩馈信息。我曾经见过监控逻辑写在核心层里,结果核心层一挂,连崩馈报告都传不回来。

模块化设计,说白了就是把复杂问题拆成四个独立的小问题。配置层管规则,路由层管决策,核心层管执行,监控层管反馈。每层只做一件事,才能保证灰度发布时,你能快速定位问题并安全回滚。嗯,这个骨架搭好了,后面的章节我们就在这四层之上构建真正的灰度策略。