【SkyWalking从入门到精通】第49篇:SkyWalking存储模型全景图:四大模型如何撑起海量观测数据
上一篇【第48篇】OAP集群规划实战:从理论到生产的容量设计
下一篇【第50篇】注册模型与明细模型:名字的管家和现场的记录官
一、开篇:不是所有的数据都叫"观测数据"
APM系统要存储的数据五花八门——服务名、Trace ID、响应时间、HTTP状态码、JVM指标、调用拓扑……如果像NoSQL那样用一个万能JSON把什么都往里塞,查询性能会惨不忍睹。
SkyWalking的设计者做了一个聪明的分类:把所有数据按"使用方式"分成四大类型,每种类型用专门的存储结构和优化策略。这就是四大存储模型的由来。
APM数据 ======= | | 按"怎么用"来分类 v +---+---+---+---+ | | | | | v v v v v 注册 明细 指标 采样 模型 模型 模型 模型 查得少 查得多 查得多 查得少 写得少 写得多 读得频繁 按需获取 小体积 大体积 小体积 大体积 永久 有限TTL 有限TTL 有限TTL 图1:四大模型对APM数据的分类策略二、四种模型的特征速览
| 维度 | 注册模型 | 明细模型 | 指标模型 | 采样模型 |
|---|---|---|---|---|
| 存储内容 | 服务名、端点名等实体 | Trace/Span明细 | 聚合后的数值指标 | 慢查询/异常堆栈 |
| 数据量 | 很小(千级) | 极大(亿级/天) | 中等(万级/天) | 小(按规则触发) |
| 写频率 | 极低(几乎不变) | 极高(实时) | 中(分钟级) | 低(按需) |
| 读频率 | 高(Dashboard常查) | 低(问题排查时) | 极高(Dashboard) | 低(问题排查时) |
| 保留周期 | 永久 | 7-14天 | 分钟:30天/小时:90天/天:180天 | 7-14天 |
| 典型大小 | KB级 | GB-TB级 | MB-GB级 | MB级 |
三、注册模型(Register Model)
3.1 解决什么问题?
当探针上报一个Span说"这是user-service调用了order-service的/createOrder接口",OAP需要把这些名字存起来。但每次Span都带完整名字太浪费,于是SkyWalking做了一层名字到ID的映射:
name = "user-service" --> serviceId = 1 name = "order-service" --> serviceId = 2 name = "/createOrder" --> endpointId = 1013.2 注册模型的类结构
// 注册模型 - 服务注册@Stream(name="service_inventory",scopeId=0)publicclassServiceInventoryextendsRegisterSource{@Column(columnName="service_id")@IDColumnprivateintserviceId;// 服务ID(主键)@Column(columnName="name")@NeedDefaultprivateStringname;// 服务名称@Column(columnName="is_address")privateintisAddress;// 是否是地址@Column(columnName="properties")privateStringproperties;// 自定义属性}// 注册模型 - 端点注册@Stream(name="endpoint_inventory",scopeId=0)publicclassEndpointInventoryextendsRegisterSource{@Column(columnName="endpoint_id")@IDColumnprivateintendpointId;// 端点ID@Column(columnName="service_id")privateintserviceId;// 所属服务ID@Column(columnName="name")privateStringname;// 端点名称@Column(columnName="detect_point")privateintdetectPoint;// 探测点类型}// 注册模型 - 网络地址注册@Stream(name="network_address_inventory",scopeId=0)publicclassNetworkAddressInventoryextendsRegisterSource{@Column(columnName="address_id")@IDColumnprivateintaddressId;@Column(columnName="name")privateStringname;}四、明细模型(Record Model)
4.1 解决什么问题?
明细模型存储的是"原始证据"——每一条Trace、每一个Span的完整信息。当需要排查问题时,这些"现场的细节"是必不可少的。
4.2 明细模型示例
// 明细模型 - Segment记录(一个Trace的完整记录)@Stream(name="segment",scopeId=1)publicclassSegmentRecordextendsRecord{@Column(columnName="segment_id")@IDColumnprivateStringsegmentId;// Segment唯一ID@Column(columnName="trace_id")privateStringtraceId;// 所属Trace ID@Column(columnName="service_id")privateintserviceId;// 服务ID@Column(columnName="service_instance_id")privateintserviceInstanceId;// 实例ID@Column(columnName="endpoint_id")privateintendpointId;// 端点ID@Column(columnName="start_time")privatelongstartTime;// 开始时间@Column(columnName="end_time")privatelongendTime;// 结束时间@Column(columnName="latency")privateintlatency;// 延迟(ms)@Column(columnName="is_error")privateintisError;// 是否出错@Column(columnName="data_binary")privatebyte[]dataBinary;// Span列表的二进制数据@Column(columnName="version")privateintversion;// 协议版本}// 明细模型 - 告警记录@Stream(name="alarm_record",scopeId=1)publicclassAlarmRecordextendsRecord{@Column(columnName="alarm_record_id")@IDColumnprivateStringid;@Column(columnName="rule_name")privateStringruleName;@Column(columnName="scope")privateintscope;@Column(columnName="scope_id")privateintscopeId;@Column(columnName="name")privateStringname;@Column(columnName="alarm_message")privateStringalarmMessage;@Column(columnName="start_time")privatelongstartTime;@Column(columnName="value")privateStringvalue;}五、指标模型(Metrics Model)
指标模型是SkyWalking当之无愧的"主角",因为Dashboard上的每个图表背后都有指标模型在支撑。
// 指标模型 - 服务响应时间指标@Stream(name="service_resp_time",scopeId=2)@MetricsExtension(supportDownSampling=true)publicclassServiceRespTimeMetricsextendsMetricsimplementsLongValueHolder{@Column(columnName="entity_id")@IDColumnprivateStringentityId;@Column(columnName="value",dataType=Column.ValueDataType.COMMON_VALUE,function=Function.Avg)privatelongvalue;// 平均值@Column(columnName="total")privatelongtotal;// 调用次数@Column(columnName="sum")privatelongsum;// 延迟总和@Column(columnName="time_bucket")privatelongtimeBucket;// 时间窗口}// 指标模型 - 百分位响应时间指标@Stream(name="service_percentile",scopeId=2)publicclassServicePercentileMetricsextendsMetricsimplementsMultiLongValuesHolder{@Column(columnName="entity_id")@IDColumnprivateStringentityId;@Column(columnName="p50")privatelongp50;@Column(columnName="p75")privatelongp75;@Column(columnName="p90")privatelongp90;@Column(columnName="p95")privatelongp95;@Column(columnName="p99")privatelongp99;@Column(columnName="total")privatelongtotal;@Column(columnName="time_bucket")privatelongtimeBucket;}六、采样模型(Sampling Model)
采样模型存储的是"按需抓取"的数据——不是所有数据都存,只有满足特定条件时才记录。
// 采样模型 - 慢查询记录@Stream(name="top_n_database_statement",scopeId=3)publicclassTopNDatabaseStatementextendsRecord{@Column(columnName="trace_id")privateStringtraceId;@Column(columnName="id")@IDColumnprivateStringid;@Column(columnName="statement")privateStringstatement;// SQL语句@Column(columnName="latency")privatelonglatency;// 执行时间@Column(columnName="service_id")privateintserviceId;@Column(columnName="time_bucket")privatelongtimeBucket;}// 采样模型 - Profile堆栈快照@Stream(name="profile_task_segment_snapshot",scopeId=3)publicclassProfileThreadSnapshotRecordextendsRecord{@Column(columnName="task_id")privateStringtaskId;@Column(columnName="segment_id")privateStringsegmentId;@Column(columnName="dump_time")privatelongdumpTime;@Column(columnName="stack_binary")privatebyte[]stackBinary;// 线程堆栈数据}七、注解驱动的存储字段编排
SkyWalking使用一套优雅的注解体系来声明字段如何存储:
// @Column注解——定义字段的存储行为public@interfaceColumn{// 存储中的列名StringcolumnName();// 值的数据类型ValueDataTypedataType()defaultValueDataType.COMMON_VALUE;// 如果是指标,使用的聚合函数Functionfunction()defaultFunction.Avg;// 是否可搜索booleanmatchQuery()defaultfalse;// 是否可全文搜索booleancontentQuery()defaultfalse;// 存储类型StorageTypestorageType()defaultStorageType.COMMON;enumValueDataType{COMMON_VALUE,// 普通数值CONSTANT_VALUE,// 常数值NOT_VALUE,// 非数值SAMPLED_RECORD,// 采样记录值LABELED_VALUE// 标签值}}// @IDColumn——标记主键字段public@interfaceIDColumn{}// @Stream——定义存储流(索引/表)public@interfaceStream{Stringname();// 流名称intscopeId();// 范围IDClassbuilder();// Builder类Classprocessor();// Processor类}// @MetricsExtension——指标扩展属性public@interfaceMetricsExtension{booleansupportDownSampling()defaulttrue;booleansupportUpdate()defaultfalse;booleantimeRelativeID()defaulttrue;}八、四种模型的读写流程
+-------------------+ | Agent探针 | +--------+----------+ | gRPC上报 | v +--------+----------+ | SegmentParser | | 解析原始Segment | +--------+----------+ | +------------+------------+ | | v v +-----------+-----------+ +----------+--------+ | 注册模型处理 | | 明细模型处理 | | | | | | 1. 检查服务名是否已注册 | | 1. 创建SegmentRecord| | 2. 检查端点名是否已注册 | | 2. 记录latency等字段| | 3. 未注册则生成新ID | | 3. 写入存储 | | 4. 将ID映射放入缓存 | | | +-----------+-----------+ +---------+---------+ | | v v +-----------+-----------+ +---------+---------+ | 后续使用 | | 指标模型处理 | | | | | | 其他模型引用注册ID | | 1. 拿到服务ID | | (如指标模型中的entityId)| | 2. 解析OAL指标定义 | | | | 3. 执行聚合计算 | | | | 4. 写入指标数据 | +-----------------------+ +---------+---------+ | (慢调用/异常时) | v +---------+---------+ | 采样模型触发 | | | | 1. 判断是否满足条件 | | 2. 抓取堆栈/慢SQL | | 3. 存储采样数据 | +--------------------+ 图2:四种模型的协同工作流程九、存储模型的时间窗口分层
指标模型的数据按时间粒度分为三层存储:
原始数据写入(分钟级) | v [分钟窗口] ── TTL: 默认90天 | | 每小时聚合一次 v [小时窗口] ── TTL: 默认45天 | | 每天聚合一次 v [天窗口] ── TTL: 默认180天 查询时自动选择最合适的粒度: - 查询最近1小时 → 使用分钟粒度数据 - 查询最近24小时 → 使用小时粒度数据 - 查询最近30天 → 使用天粒度数据十、总结
SkyWalking的四大存储模型是一种"根据数据使用方式分层存储"的经典设计:
- 注册模型:名→ID映射,体量最小,永久存储
- 明细模型:原始证据,体量最大,较短TTL
- 指标模型:聚合结果,体量中等,是Dashboard的数据源
- 采样模型:按需抓取,体量按规则决定
这套模型体系让SkyWalking既能在Dashboard上秒级响应查询,又能完整保留问题排查所需的原始数据,实现了"快"和"全"的完美平衡。
下一篇文章,我们将深入注册模型和明细模型的细节实现。
上一篇【第48篇】OAP集群规划实战:从理论到生产的容量设计
下一篇【第50篇】注册模型与明细模型:名字的管家和现场的记录官
