0 介绍

0.1 背景

SAWF的数据信道模块目前主要是指的接口层,由许多标准化的restful API组成,其价值如下:

在AIOps算法服务上解耦数据和计算,对于数据的读写操作均通过API进行,屏蔽各种数据库的具体细节,使得算法能专注于模型和计算;
具备一定的数据处理功能,能够将不同数据源(VM、Mongodb、mySQL等)的数据加工成统一的、算法易于使用的数据格式;
利于AI计算结果的存储和查询,缩短从上线到运营结果的时间;
更好的可扩展性,对于后续引入其他算法和数据源,可以加入相应的api来获取和存储数据,不影响其他已有的功能,整体改动小,降低开发成本;
标准化数据的获取和存储,优雅管理、减少维护、便于统计;

0.2 需求场景及问题

目标是将数据信道模块建设成易使用、高可用、易扩展的基础模块。

SAWF数据源:

VictoriaMetrics:时序数据等(主要)
MongoDB:配置数据、结果数据、告警等(主要)
MySQL:配置信息等
ClickHouse:日志信息等
nebula:图数据库
(待扩展)
主要需求:

Read API:
AIOps算法需要从各种数据源拉取各种数据进行计算;
算法的结果数据可被二次查询。
Write API:
算法的计算结果存入aiops的数据库(MongoDB);
产生的告警数据存储到aiops的数据库(MongoDB);
日志服务产生的日志信息需要存入aiops数据库(ClickHouse)。
健康检查:检查后端存储数据库是否健康状态;
批量处理:支持读写的批量操作,提高查询和读写效率。
目标和问题:

易使用:数据的读写更简单易用,数据的格式更标准统一,改变现在各自为战、风格迥异的现状。
高可用:将数据信道模块建设成统一的数据入口和出口,可以复用代码、减少开发成本、避免重复工作,但是也会带来性能、容错等问题,特别是VictoriaMetrics的访问压力较大,需要保证模块的可用性和容错性。
易扩展:无论是引入新的算法模型、新的数据库还是新的数据源,均可以在不影响其他模块和功能的前提下,以较小的改动(例如仅仅修改配置文件)来支撑新的内容。

0.3 选型结论

基于goFrame框架建立一整套覆盖各个数据源的restful风格的API,以HTTP请求的方式读写数据,具备一定的计算能力,支持对数据格式进行灵活的变换。
gf框架支持docker镜像编译,默认提供了Kubernetes集群化部署的Yaml模板,通过kustomize管理。
gf框架的ORM组件功能完备、扩展性强,但仅支持mySQL和redis,其他数据源需根据情况自行开发。

1 开发

1.1 框架选型

Gin GoFrame
优势 轻量级,简单易用,高效 活跃的中文社区,详细的中文文档,模块化设计和工程化设计思想,组件丰富
社区 英语 中文(国内)
缺点 缺少配套的开发组件 缺少成熟的实践
框架性能 较快
开发组件 基本没有,需要自行实现或者用第三方 全面、开箱即用且不断更新优化中
路由算法 压缩前缀树:快,但是规则苛刻 快,精准匹配用哈希表;带参数路由用链表;对象注册路由的方式方便实现mvc
官方文档 https://gin-gonic.com/zh-cn/docs/ https://goframe.org/pages/viewpage.action?pageId=1114399
简介 Go语言写的轻量级HTTP Web框架。它提供了Martini风格的API并有更好的性能。 工程完备、简单易用,模块化、高质量、高性能、企业级开发框架。
项目地址 http://github.com/gin-gonic/gin http://github.com/gogf/gf

GoFrame框架更加适合,主要优势:

针对业务项目而言,提供了开发规范、项目规范、命名规范、设计模式、开发工具链、丰富的模块、高质量代码和详细的文档,社区活跃。
提供了常用的核心开发组件,如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、 并发安全容器等等。
提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、路由注册、配置管理、模板引擎等等,支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。

1.2 工程组织

gf框架给出了详细且严格的组织目录模板,通过gf命令可自动生成:gf init demo -u

目录/文件名 说明 描述 具体业务
- api 对外接口 N/A
|L v1 当前接口版本 N/A
||- victoria.go vm接口文件 路由注册
||-mangodb.go mangodb接口文件 路由注册
|L XXX 待扩展 可扩展mySQL、redis等
|-hack 工具脚本 存放项目开发工具、脚本等内容,主要是CLI工具的配置。 暂时不用
|- internal 内部逻辑 业务逻辑存放目录。通过Golang internal特性对外部隐藏可见性。 N/A
||-cmd 入口命令 项目的总管理目录 总路由管理
||-const 常量定义 项目所有常量定义。 所有常量定义
||-controller 接口处理 接收/解析用户输入参数的入口/接口层,转到具体的业务逻辑。 转到logic处理数据
||-dao 数据操作对象 通过对象方式访问底层数据源,底层基于ORM组件实现。往往需要结合entity和do通用使用。其中的文件按照数据表名称进行命名,一个数据表一个文件及其一个对应的DAO对象。操作数据表即是通过DAO对象以及相关操作方法实现。支持MySQL。 暂时不用
||-logic 业务封装 业务逻辑实现和封装。 业务逻辑:获取数据、处理数据、存储数据等。
||-model 结构模型 N/A
|||-do 数据转换模型 由gf gen dao生成。数据转换模型用于业务模型到数据模型的转换,由工具维护,不能修改。 N/A
|||-entity 数据模型 由gf gen dao生成。数据模型即与数据表一一对应的数据结构,不能修改。 N/A
|||-victoria.go 业务模型文件 业务模型即是与业务相关的数据结构,自行按需定义。 自定义所需的数据结构
||Lmango.go 业务模型文件 业务模型即是与业务相关的数据结构,自行按需定义。 自定义所需的数据结构
|Lservice 业务接口 由gf gen service根据logic包生成。生成后需要1)在每个业务模块中加上接口的具体实现注入;2)在main包的最顶部合适位置引入同步生成的一个接口实现注册文件logic.go N/A
|-manifest 交付清单 包含程序编译、部署、运行、配置的文件。 N/A
||-config 配置管理 配置文件存放目录。 项目配置文件(用toml),会自动解析。
||-docker 镜像文件 Docker镜像相关依赖文件,脚本文件等等。 暂时不用
|Ldeploy 部署文件 部署相关的文件。默认提供了Kubernetes集群化部署的Yaml模板,通过kustomize管理。 暂时不用
|-utility 工具函数 公共函数在这里
Lmango.go 入口文件 程序入口。 程序入口。

2 设计细节

遵循数据信道模块的目标:易使用、高可用、易扩展的基础模块,仅实现对各数据源的封装,提供restful的API供其他业务模块(前端、算法等)使用。

2.1 易用性

模块设计了一整套满足restful风格的API,逐步覆盖各类数据源,通过HTTP请求获取JSON格式的数据,以HTTP请求方法区分、参数控制。

具体请参考本文档第3节【接口设计】。

2.2 可用性

2.2.1 横向扩展

横向扩展能力主要是指模块水平扩展:主要是指面对业务压力时,能够通过水平扩展达到负载均衡,从而保证服务的可用性。

压力主要集中在Victoria Metrics数据库,VM数据库天然支持水平扩展。而API作为VM数据库的数据出口同样也会面临访问压力,因此,一方面,模块需要保证无状态来支持水平扩展;另外一方面,所选用的开发框架gf默认支持docker镜像部署和Kubernetes集群化部署,通过kustomize管理(默认提供了Kubernetes集群化部署的Yaml模板,细节需要后续深入学习),能够进行水平扩展,通过分布式多节点来实现负载均衡。

2.2.2 查询缓存

AIOps中的算法、前端等业务对数据的操作均已读为主,对于短时间重复的读操作,若能设置查询缓存,将能有效的提升查询性能。

单进程内存缓存:优点是性能非常高效,但是只能在单进程内使用,若服务采用分布式多点部署,多节点之间的缓存可能会失效/产生数据不一致的情况。

分布式查询缓存:更加适用大多数的场景,通过Redis服务器来实现查询数据的缓存,Redis缓存在多节点保证缓存的数据一致性时非常有用。

gf框架支持缓存管理:内置的gcache模块采用了适配器设计模式,提供了Adapter适配器接口,任何实现了Adapter接口的对象均可注册到缓存管理对象中,使得开发者可以对缓存管理对象进行灵活的扩展。并且框架通过社区模块提供了gcache的Redis缓存适配实现,API服务可以直接拿来使用。仓库地址:https://github.com/gogf/gcache-adapter。

2.2.3 流量治理

开发框架gf正在设计自己的微服务熔断和限制模块。

1.服务容错

AIOps中不可避免的会出现一些意外情况,比如数据库不可用/响应超时、API本身崩溃、网络中断等,为了防止整个系统雪崩或者因为错误占用大量服务器资源,服务需要具备一定的容错性才能保证其可用性

问题 容错设计思路 说明 参考实现
数据库不可用/响应超时 熔断 如果某个数据库不可用或者大量超时,此时,熔断数据库的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。 Netflix的开源项目Hystrix、go-kratos/aegis实现的google_sre算法
API模块崩溃 重启 因为意外情况服务挂掉 K8S支持监控并重启

2.流量控制

作为AIOps的数据出入口,API服务会承受流量压力。算法等上游服务对本服务请求QPS过大时,需要通过一定的策略(如延迟处理、拒绝处理)对上游服务的请求量进行限制,以保证本服务不被压垮,从而持续提供稳定服务。常见的限流算法有滑动窗口、令牌桶、漏桶等。

算法 优点 缺点 参考实现
自适应模式(容器化) 根据硬件配置,设置load或者cpu占用率,根据设置的cpu占用率和load阈值,获取过去一段时间内的请求数量。将这个数量作为限流,如果系统负载达到这个显示,将这个请求数量作为请求阈值。 根据系统资源利用率进行限流,会因为其它程序或进程的高cpu占用率触发限流。这部分可以采用docker 封装,获取docker 的相关占用率进行避免。 alibaba/Sentinel、go-kratos/kratos
漏桶模式 实现简单;基本实现了平滑处理,在单位时间内,可以控制请求数量。 1. 不考虑服务器负载,如果有大量请求会对请求进行缓存,按照漏桶流量进行处理;2. 难以配置合适的参数。 N/A
滑动窗口模式 细分时间精度可以避免请求超过阈值,可以应对简单的突发流量。 1. 使用内存或者redis来维护窗口,如果时间颗粒度太细,会造空间容量过大;2. 否决式限流,很难进行阻塞等待处理,无法“削峰填谷”。 N/A
令牌桶模式(非容器化) 实现简单;限制平均流入速率,允许一定程度突发请求(支持一次拿多个令牌)。 不好估算流入速度, 需要根据不同机器或环境进行配置。 N/A

3.服务降级

API自身服务压力增大时,采取一些手段,增强自身服务的处理能力,以保障服务的持续可用,常用的手段有读旧数据、降低实时性、降低数据一致性等。

需要在后续根据实际业务情况具体分析。

2.3 扩展性

在不影响已有功能和服务其他模块的前提下,以较小的改动(例如仅仅修改配置文件)来引入新的业务需求、新的数据库还是新的数据源。

场景 说明 解决思路
新增业务需求 主要是指当有新的算法模型、业务模块需要使用数据时调用API。 API的设计在满足易用的前提下,应该提供尽可能全面可用的接口供上层业务(包括算法、前端等)使用,避免接口开发不完备和冗余。
新增已有数据库实例 能够在不更改代码的情况下通过增加/修改配置文件的方式引入新的已有的数据库实例。 gf框架的数据库ORM支持mySQL和redis的相关实现,对于AIOps使用较多的mongodb缺乏支持,需要根据gf的源码自行实现/寻找成熟的开源实现(七牛封装了一个)。
新增数据源 主要是指当业务有需要时引入新的数据库类型,例如后续可能会加入的图数据库nebula。 当有此类需求时需要对模块的代码进行迭代,新增代码对相应的数据库类型的功能进行封装,并且不会影响已有的功能和业务。

3 接口设计

3.1 Read API(P1)

支持以GET接口的方式获取AIOps算法需要的数据,后续还需要支持获取结果数据(从VM,MongoDB中)供前端、监控等模块使用。

边界定义如下:

单次请求获取数据时间范围不超过7天
单次请求获取数据返回的总条数不超过10万条
单次请求超时时间为3s

3.1.1 Victoria Metrics API

Victoria Metrics数据库/普罗米修斯提供默认且全面的restful api接口,因此数据信道模块只需要在原始接口的基础上进行封装,使得整体接口的风格统一。

配置字段名 说明 示例
vm_url 实际普罗米修斯API的url http://10.109.134.70:30481/select/0/prometheus/api/v1/

查询单点时序数据

接口名称 查询单点数据
所属模块 v1/victoria
接口功能 查询单时间点数据v1/victoria
方法 GET
URL /v1/victoria/single
请求参数
参数名 类型
———— ——————
metrics string
includeEndpoints string
excludeEndpoints string
includeTags string
excludeTags string
aggrFunc string
aggrKeys string
time uint64
isTransform bool
customAdditionalKey string
others map[string]string
响应参数(转换)
参数名 类型
———— ——————
metric string
endpoint string
tags map[string]interface{}
values []map[string]float

请求示例:

1
/v1/victoria/single?metrics=host_net_dp_sess_use&includeEndpoints=host-0894efb78dc1

接口正常 (不转换)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"status": "success",
"isPartial": false,
"data": {
"resultType": "matrix",
"result": [
{
"metric": {
"__name__": "host_net_recv_bytes_per_second",
"az_id": "87feb6f5-10a6-40fa-961f-4aac9fa1bacb",
"cloud_id": "6338720532",
"cluster_id": "6338720597",
"endpoint": "host-0894efb78dc1",
"iface": "eth0",
"region_id": "4aa88ced54da4d4ab305c3b8a3d72174",
"resource": "sf_server",
"step": "30"
},
"values": [
[
1664453644,
"138184.2"
],
[
1664453944,
"104945.9"
]
]
},
......
]
}
}

响应示例 (转换)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"count": n,
"data": [
{
"metric": "host_net_recv_bytes_per_second",
"endpoint": "host-0894efb78dc1",
"tags": {
"az_id": "87feb6f5-10a6-40fa-961f-4aac9fa1bacb",
"cloud_id": "6338720532",
"cluster_id": "6338720597",
"iface": "eth0",
"region_id": "4aa88ced54da4d4ab305c3b8a3d72174",
"resource": "sf_server",
"step": "30"
},
"values": [
{"timestamp": 1664453644, "value": "138184.2"},
{"timestamp": 1664453944, "value": "104945.9"}
]
},
......
],
"message": "ok",
"success": 1
}

接口异常响应参数:

参数名 类型 必选 说明
success int Y 接口异常响应时为0
errcode int Y scc错误码
message string Y 错误信息(unicode编码)

接口异常响应示例:

1
2
3
4
5
{
"success": 0,
"errcode": 503,
"message": "系统繁忙,请稍后重试"
}

查询范围时序数据

查询标签

3.1.2 MongoDB API

目前的gf框架不支持mongodb,需要额外引入第三方库,go语言常用mongodb驱动是mgo和mongo-go-driver,二者的比较如下:

mgo mango-go-driver
优点 特性丰富
实践丰富
文档充足
简单易上手
官方驱动
设计底层,效率更高
支持事务
功能更全面
缺点 停止维护 上手较复杂

考虑到代码之后的扩展性,选择功能更加强大且全面的官方驱动mongo-go-driver,并且mongo-go-driver支持在建立连接时可以通过option设置超时时间等加入限制,更加贴合业务需求。

可配置信息

查询文档数量

3.2 Write API(P1)

支持以POST接口的方式写入数据。

主要接收数据如下:

AIOps算法的结果数据,写入MongoDB
告警数据,写入MongoDB
边界定义如下:

单次请求写入数据大小不超过10MB,如果超过,需要以迭代形式处理
单次请求超时时间为3s

3.3 Healthy API(P1)

支持检查后端存储是否健康状态,支持检查的后端存储有:

MongoDB
VictoriaMetrics
ClickHouse
MySQL

3.4 Batch API(P2)

支持批量读写数据,是在Read API 和 Write API上的二次封装。主要用于批量操作,提高查询和读写效率。

4.REST API错误代码

rest api应当有合适的状态码或者响应码来反映错误,通常控制在20个以内常用的,并且出错后需要在响应体补充细化的error信息(包含code和message)。

代码 描述 说明
200 描述正确 不明确区分时返回
201 资源正确创建 Write API成功写入数据时返回
202 请求正确,处理中 计算/处理中,暂时没法返回结果
204 请求正确,无返回 Write API成功删除数据时返回
400 请求错误 参数不正确
401 未授权/未认证 请求的时候没有带上Token/认证失败
403 禁止访问 访问权限不对
404 找不到资源
405 不可使用的HTTP方法
409 唯一性的资源冲突等
500 服务器错误 不明确区分时返回
503 服务不可用 超时/数据库连接失败等

5.安全与校验

JWT:Json Web Token

优点:基于JSON,通用;构成简单,便于传送;服务端不保存会话信息,易于横向扩展;可附加一定的非敏感信息。

GoFrame的gvalid。

6.hydra性能测试

本次测试了1)通过hydra连接mongodb和直连mongodb获取数据性能差别;2)hydra分批获取数据性能差别,如下表所示:

说明:查询循环10次。

结论:通过hydra查询数据相较于直连mongodb会有额外时间开销:单次获取数据区别不大,连续获取数据hydra耗时约为直连的2~3倍(因为连续获取同库同集合数据直连会有导管加速)。

结论:分批查询数据会造成较大的额外时间开销。