可维护的APM架构

  1. 整体架构
  2. 针对于具体场景的设计
    1. 模块注册
    2. 模块通信
    3. 模块配置
  3. Demo

起因:在头条实习的时候,我的偶像诗彬大佬设计了一个抽象合理,功能强大的APM项目架构,随着我不断往里面塞垃圾代码… 并且APM又有单独模块可以独立使用的需求,之前的架构已经不能满足如今的需求。我在学校的期间,花了几天时间重新设计了一下。
着重解决的问题是 模块独立使用和模块间因为功能相互依赖的问题

整体架构

在我看来,一个APM系统功能可以大致拆分为:1. 收集 2. 记录 3. 存储 4. 上报 5. 控制下发 6.数据加工

  1. 收集,各种元数据的采集
  2. 数据加工,数据指标的具体意义
  3. 记录,数据记录的格式和记录形式
  4. 存储,在客户端本地持久化的形式
  5. 上报,将记录上报给后台
  6. 控制下发,远程下发APM系统配置

记录,存储,上报,控制下发 的核心逻辑是整个APM系统运行的必要条件。因此可以把这4个部分视为核心系统。收集视为各种插件模块。 整个系统类似于一个微内核的结构。

现在我们的项目可以拆出20多个subspec, 后续势必也会逐渐增加更多的功能,这样的架构设计我认为还是合理的。

针对于具体场景的设计

膨胀的模块数量,模块数据的共享,业务逻辑的处理会导致一系列的问题,这里会逐一给出我具体的方案。

模块注册

模块注册的设计参考自 BeeHive,基于Spring的Service理念。 在注册类中写个简单的宏就能搞定。
类似于这种
@OAPMMod(OAPMMemoryMonitor)

模块通信

理想情况下,监控模块不互相依赖,能获得对方的数据。

对OOM模块来说,它做出OOM Event的判断会依赖于 ANR监控模块,Memory监控模块 等其他模块的监控情况。对于这种情况,我们可以把 监控模块做成纯监控模块,将判断的业务逻辑放在核心系统中 或者 在OOM模块中拿到其他模块的数据,在OOM模块做逻辑判断。这两种处理方式对于整个系统的维护和拆分都有各自的弊端。

  1. 核心模块过于复杂,庞大
  2. 监控模块依赖于其他监控模块

我个人偏好第二种方案一些,可以引入Protocol 做一个中介,使模块依赖于Protocols。

具体的通信分为两类:

  1. Service, 模块A 调用 模块B Service. 通过模块B暴露的Service接口。
  2. Event, 模块B会产生一系列事件,模块A可以选择性监听。

Service的设计和BeeHive类似不再阐述。

Event考虑到收集模块的功能单一,仅是数据的收集?,我做的只是单纯的proxy转发。
模块A遵循了模块B的事件协议便可以收到相应的事件。这个调用也是很简单同时具有接口约束

事件发送:

1
2
3
4
5
/// 随便写的回调,后续要加入合适的参数 返回值
- (void)timerCallBack {
NSLog(@"CPUMonitor 产生的模块事件");
[[OAPMManager shared].moduleEventTriger updateCPUNumber:@1];
}

事件接收:

1
2
3
@interface OAPMMemoryMonitor : OAPMTimerMonitor<OAPMMemoryMonitorProtocol, OAPMCPUEventListenerProtocol>
- (void)updateCPUNumber:(NSNumber *)number {}

模块配置

模块的配置应该清晰,有结构性。不能图方便直接用key,value取,最好实例化成具体的配置实例,这样既避免了 const char *key = “”;满天飞的情况,也自带了层次结构的效果。我个人真的很讨厌声明常量字符串…

具体到实现,我写了个简单的范例

1
2
// 更新配置
[[OAPMManager shared] updateConfigs:[[OAPMConfig shared] configurations]];

这里对象分发,我把 configProtocol 和 moduleProtocol 绑在一起,实现了对应模块的配置分发。(我真的很讨厌,每添加一个模块都要加一个if else!)

1
2
3
4
5
6
7
8
9
10
11
12
// 配置对象分发
- (void)updateConfigs:(NSMutableArray<id<OAPMConfigurationProtocol>> *)configs
{
[[[OAPMModuleManager shared] allSharedModuleInstances] enumerateObjectsUsingBlock:^(id<OAPMModuleProtocol> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
for (id<OAPMConfigurationProtocol> config in configs) {
Protocol *protocol = config.protocol;
if ([obj conformsToProtocol:protocol]) {
[obj updateConfig:config];
}
}
}];
}

Demo

具体的Demo在这里.

script>