古茗是如何做前端数据中心的

avatar
@古茗科技

banner

简介

古茗的前端数据中心包含了前端监控、性能、日志、埋点等能力,还支持错误分析、埋点分析报表等功能 不仅支持小程序、web 还支持客户端 flutter、服务端 nodejs 等。由于我们有不少的 nodejs 应用,所以 nodejs 的监控也是必不可少的。

功能设计

在功能设计上分为了 8 个大模块,包含了前端所有需要的数据,故命名为大前端数据中心。

image.png

架构设计

由于平台设计包含了大量数据上报/分析的能力,所以架构设计上需要满足以下一些要求:

  • 实时性:异常发现、异常告警需要较高的实时性
  • 高可用:服务集群需要具有较高的可靠性,具备快速恢复、容灾等能力
  • 稳定性:具备一定弹性、有处理高并发、高峰值、流量波动的能力
  • 高吞吐:服务有接收大量数据日志和埋点数据,需要具备大量吞吐能力
  • 客户端容错:服务故障/SDK 报错,不应该影响业务正常运行

所以我们初版架构设计如下:

image.png

客户端

客户端 SDK 中,我们设计了部分资源采样能力,这是因为我们监控了资源加载、请求、性能等数据,对于正常的数据,例如正常请求、正常加载的资源、正常性能数据,我们可以配置开启采样来减少服务端压力、当然也可以根据业务需求开启全量上报。

另外我们还支持了配置下发能力,例如采样率,上报开关、上报队列、上报通道等等。

数据网关

在数据网关中,我们也支持采样(服务端采样),用户可以选择其中之一开启,与客户端采样的区别是服务端采样更加精确和实时。

数据分流

我们将数据分流为3个通道,大数据通道、持久化通道和实时计算通道,用于满足不同的业务需求:

  • 大数据通道:用于埋点业务分析、商业分析等
  • 实时计算通道:用于实时计算指标,例如产品创建的分析图表、平台内置的指标等等
  • 持久化通道:用于将数据写入 ES 集群,便于后置的查询和分析原始数据

埋点分析

对于错误和性能等等常规监控,大家一般都大同小异,这里介绍一下我们是如何做埋点分析的。

对于上报的埋点数据,不管是无痕埋点还是手动埋点,我们要分析就需要去查看各种图表,所以我们设计了自定义图表创建功能,可以让产品主动创建分析图表,要达到这个目的我们就需要走一个埋点的流程。

在这之前我们的流程是这样的:产品需要先和开发者提出埋点的需求,等埋点上线后,再和大数据提出数据分析的需求,最后才能根据需求定制图表。

image.png

但是在数据平台上我们就变成了这样: 产品自助创建埋点需求(直接创建点位指定给对应开发者),然后产品可以同时根据点位创建图表,上线后就可以直接查看图表了。

image.png

那么,在技术上我们做了哪些能力来支持这个功能呢:

  1. 预解析:我们对每个图都做了实时计算指标,可以做到创建后即可以查看,因为是计算好的指标,出图速度可以达到几十毫秒左右(直接查询 influxdb)
  2. 历史数据分析:对于没有事先计算好指标的历史数据,我们也提供了速度较慢的实时分析能力(查询ES)
  3. 另外,对于 web 端,我们支持了点位管理事件管理属性管理等等能力

日志查询

在 SDK 侧我们可以通过调用 logger API 来上报数据,SDK 会自动采集上下文和环境信息并且还可以关联用户埋点数据:

logger().debug(`xxxxx`)
logger().warn(`xxxxx`)
logger().error(`xxxxx`)
logger().info(`xxxxx`)

在平台侧我们就可以通过事件、环境信息、用户信息、日志内容等等维度来搜索日志,还可以直接指定设备来实时输出日志(规划中)。

数据串联

我们对所有数据都进行了关联,例如查询日志的时候可以通过 userId/traceid 等标记信息来串联日志、请求、埋点、错误等等。

image.png

所以不管是发现了日志不对、代码错误、请求错误等信息,我们就可以将所有链路串联起来排查问题。

Nodejs

Nodejs 模块是一个相对独立的模块,我们的 SDK 也是直接整合在框架内部,由于 nodejs 是服务端应用,所以我们针对 nodejs 做了很多单独的模块,例如 cpu 分析、GC 监控分析、服务数据监控等等。

CPU分析

主要是用包 GitHub - davidmarkclements/0x: 🔥 single-command flamegraph profiling 🔥 来进行 cpu 火焰图分析,cpu 数据使用 c++ 编写的扩展(调用 v8 api)来实现,直接使用 node node_modules/0x/cmd.js --visualize-cpu-profile 命令来生成图表(需要收集的 cpu 数据符合 0x 的格式)。

收集流程如下

image.png

// v8::CpuProfiler 实例
static CpuProfiler *current_cpuprofiler = CpuProfiler::New(Isolate::GetCurrent());

// 启动 v8 cpu profiling
void StartCpuProfiling(const FunctionCallbackInfo<Value> &info) {
    HandleScope scope;
    Local<String> profilerTitle = New<String>("cpu_profiler").ToLocalChecked();
    current_cpuprofiler->StartProfiling(profilerTitle, true);
}

 // 停止 v8 cpu profiling
void StopCpuProfiling(const FunctionCallbackInfo<Value> &info) {
    HandleScope scope;
    const CpuProfile *profile = current_cpuprofiler->StopProfiling(profilerTitle);
    /// - 后续对 profile 数据进行解析写入等等操作
}

GC 分析

GC 数据也是使用 c++ 编写的扩展(调用 v8 api)来实现:

NAN_GC_CALLBACK(GCTracerPrologueCallback) {
    /// - 记录GC
}

NAN_GC_CALLBACK(GCTracerEpilogueCallback) {
    /// - 记录GC
}
    

void StartGC(const FunctionCallbackInfo<Value> &info) {
	/// - 做一些初始化操作
    /// - 例如判断文件在不在什么的
    
    InitGcStatusHooks(); /// - 初始化
    AddGCPrologueCallback(GCPrologueCallback);
    AddGCEpilogueCallback(GCEpilogueCallback);
}


void StopGC(const FunctionCallbackInfo<Value> &info) {
    RemoveGCPrologueCallback(GCPrologueCallback);
    RemoveGCEpilogueCallback(GCEpilogueCallback);
    RemoveGcStatusHooks();
    /// - 做一些收尾操作
    /// - 比如重置数据之类的
}

和 cpu 数据一样,收集完成后上传到 oss 保存,然后解析数据进行图表等等分析。

结尾

古茗大前端数据平台才刚上线不久,已经扮演了不可或缺的角色,但是还需要不断打磨,希望后续可以在业务治理当中发挥重要的作用,由于平台功能繁多,架构复杂,这篇文章也是做一个简简单单的介绍,如果有什么问题欢迎大家提问和讨论。

最后

📚 小茗文章推荐:

关注公众号「Goodme前端团队」,获取更多干货实践,欢迎交流分享~