SkyWalking 数据生成器(Mock Data Generator)实战指南:模板编写、HTTP 接口与压测数据制造
【免费下载链接】skywalkingAPM, Application Performance Monitoring System项目地址: https://gitcode.com/gh_mirrors/sky/skywalking
本文围绕 Apache SkyWalking 后端提供的mock 数据生成器(data-generator)模块展开。该模块自 9.1.0 版本引入,运行在 OAP 进程内,可通过 REST 接口按
size(总量)或qps(每秒条数)两种模式向存储写入构造的Segment(调用链)与 Log(日志)数据,并内置uuid、randomString、randomBool、randomInt、randomList、fixedString、sequence、time共 8 种字段生成器。读完本文,你将掌握:如何启动数据生成器(脚本或 Docker)、如何编写可复用的 JSON 生成模板、如何通过 curl 提交/取消生成任务,以及这些接口背后的源码实现原理,可直接用于开发自测、功能验证与存储压测。
一、模块定位:为什么需要 mock 数据生成器
在没有真实业务流量的环境中验证 SkyWalking 后端(OAP)的存储、告警、查询与 UI 展示,通常需要手工构造大量 Segment 和 Log。自 9.1.0 起,SkyWalking 在oap-server/server-tools/data-generator模块中提供了专门的数据生成能力:该模块作为 OAP 的一个模块运行,复用 OAP 已有的解析/接收链路,将生成的 mock 数据直接"喂"给存储层,从而免去部署 Agent 和业务系统的成本。
从源码结构看,该模块主要由三部分组成:
- 生成器(Generator)体系:定义在 Generator.java,通过 Jackson 的
@JsonSubTypes将 JSON 模板中的type字段映射为具体生成器实现,实现"JSON 模板驱动数据生成"; - REST 接口:SegmentGeneratorHandler.java 与 LogGeneratorHandler.java,提供
/mock-data/segments/*与/mock-data/logs/*两组 HTTP 端点; - 模块装配:DataGeneratorModuleProvider.java 将上述 Handler 注册进 OAP 核心的
HTTPHandlerRegister(支持 GET / POST / DELETE 三种方法)。
生成的数据并不是"一次性扔掉",Segment 会通过ISegmentParserService走标准的 trace 解析链路,Log 则通过SourceReceiver进入 OAP 处理管线,因此后续的告警、指标聚合、查询都是真实生效的。
二、启动数据生成器
数据生成器随 OAP 一起构建分发,启动方式有两种:
2.1 通过启动脚本运行
执行打包产物中的脚本:
tools/data-generator/bin/start.sh该模块与 OAP 共享配置,默认监听12800端口(HTTP REST)与11800端口(gRPC),这与 backend-setup.md 中描述的 OAP 默认端口一致。
2.2 通过 Docker 构建运行
SkyWalking 官方不发布该模块的 Docker 镜像,但仓库提供了完整的构建脚本与 Dockerfile,可自行构建:
# 构建本地使用的 Docker 镜像 make docker.data-generator # 或推送到自己的 registry export HUB=<your-registry> make push.docker.data-generator在 Makefile 中可以看到相关规则:DATA_GENERATOR_NAME ?=>curl -XPOST 'http://localhost:12800/mock-data/segments/tasks?size=20' -H'Content-Type: application/json' -d "@segment-template.json" curl -XPOST 'http://localhost:12800/mock-data/logs/tasks?size=20' -H'Content-Type: application/json' -d "@logs-template.json"
3.2 两种任务模式:size与qps
size模式(如?size=20):一次性生成总共20 条 Segment/Log,生成完毕任务自动结束;qps模式(如?qps=20):以每秒 20 条的速率持续生成,直到任务被 取消 为止,适合模拟持续流量与压测。
两个参数不能同时设置,否则接口返回400 Bad Request(size and qps can't be both set)。
从 SegmentGeneratorHandler.java 源码可以看到任务执行的底层机制:
- Handler 内部维护一个容量为 10 的 Netty
EventLoopGroup,以及一个ConcurrentHashMap<String, Future<?>> futures用于登记所有任务; size模式通过eventLoopGroup.submit(...)提交一个一次性任务,内部用IntStream.range(0, size).forEach(generator)循环生成;qps模式通过eventLoopGroup.scheduleAtFixedRate(..., 0, 1, TimeUnit.SECONDS)每秒调度一次,每次生成qps条;- 每次提交都会生成一个
UUID作为requestId返回给调用方,任务结束后自动从futures中移除。
另外 Segment 端点还额外支持duration与group两个可选参数(Log 端点暂无):duration指定任务最多持续秒数(超过即自动 cancel),group用于给服务名加命名空间前缀,详见下节模板说明。
四、取消任务
4.1 取消单个任务
提交任务后,响应体即为任务 id(形如70d8a39e-b51e-49de-a6fc-43abf80482c1)。取消时向对应端点的单数路径发送 DELETE 请求并携带requestId:
curl -XDELETE 'http://localhost:12800/mock-data/segments/task?requestId=70d8a39e-b51e-49de-a6fc-43abf80482c1' curl -XDELETE 'http://localhost:12800/mock-data/logs/task?requestId=70d8a39e-b51e-49de-a6fc-43abf80482c1'如果requestId不存在,接口返回404 Not Found(No such request: %s);取消成功返回200 OK。
4.2 取消全部任务
对复数路径发送 DELETE 请求即可取消所有进行中的同类任务:
curl -XDELETE 'http://localhost:12800/mock-data/segments/tasks' curl -XDELETE 'http://localhost:12800/mock-data/logs/tasks'从源码看,取消实现是遍历futures对每个Future调用cancel(true)(SegmentGeneratorHandler.java),被取消的任务会触发 listener 中的CancellationException记录日志,但不会视为异常。
五、生成模板(Template)与 Generators 详解
模板即 POST 请求体,是一个 JSON 对象。每个字段对应一个生成器,生成器通过type字段标识类型。仓库内置了 8 种生成器,类型映射定义在 Generator.java:
type | Java 实现 | 产出类型 | 用途举例 |
|---|---|---|---|
uuid | UUIDGenerator.java | String | traceId 等需全局唯一的 ID |
randomString | StringGenerator.java | String | service/endpoint 名称等 |
randomBool | BoolGenerator.java | Boolean | 是否报错等标志位 |
randomInt | IntGenerator.java | Long | latency、componentId、spanId 等数值 |
randomList | ListGenerator.java | List<T> | tags、spans 等嵌套集合 |
fixedString | FixedStringGenerator.java | String | 固定值字段 |
sequence | SequenceGenerator.java | Long | 单调递增的时间戳/序号 |
time | TimeGenerator.java | Long | 基于真实时钟的毫秒时间戳 |
两个官方参考模板位于 segment-template.json 与 logs-template.json,下文逐个讲解各生成器。
5.1uuid:复用式 UUID
uuid基于java.util.UUID生成字符串,典型用途是填充 Segment 的traceId。
关键参数changingFrequency:当希望同一个 UUID 被多次复用时设置。例如希望 1 个traceId被 5 个 segment 复用,则设changingFrequency: 5——生成器产出 1 个 UUID,连续使用 5 次后再重新生成。源码 UUIDGenerator.java 用AtomicInteger counter计数,counter.incrementAndGet() < changingFrequency时直接返回上次值,达到次数后 reset 并换新 UUID。
"traceId": { "type": "uuid", "changingFrequency": "5" }注意changingFrequency必须大于 0(构造器中有checkArgument校验),默认值为 1,即每次都生成新 UUID。
5.2randomString:随机字符串
产出String,用于 service 名、instance 名、endpoint 名、tag 键值等。
length(int):随机字符串长度,保证generatedString.length() == length恒成立;prefix(String):在随机串生成之后拼上前缀,因此generatedString.startsWith(prefix)恒成立,且generatedString.length() == length + prefix.length();letters(boolean):是否包含字母(a-zA-Z);numbers(boolean):是否包含数字(0-9);domainSize(int):如果希望"少数几个随机串反复随机使用",可设置domainSize,生成器会预先构造domainSize个候选串,每次从候选集中随机挑一个返回。
从 StringGenerator.java 源码可见:开启 domain 后,构造器用RandomStringUtils.random(length, letters, numbers)预先生成domainSize个串(带前缀)存入Set<String> domain,next()时随机skip(random.nextInt(domain.size()))取一个。
5.3randomBool:按概率的布尔值
产出Boolean,默认 true/false 各 50%,适用于error这类标志位。
参数possibility(double,范围[0, 1]):true 的出现概率,默认0.5。实现见 BoolGenerator.java:random.nextDouble() < possibility。
possibility = 1:恒为true;possibility = 0:恒为false;possibility = 0.9:约 90% 的值为true。
"error": { "type": "randomBool", "possibility": "0.9" }5.4randomInt:区间随机整数
产出Long,用于 latency、componentId、spanId 等。
min(long):最小值,保证generatedInt >= min;max(long):最大值,保证generatedInt < max(注意是开区间);domainSize(int):与randomString的domainSize语义相同,预设候选值集合后随机抽取。
IntGenerator.java 的取值逻辑分三种情况:min、max都给定时用random.nextLong(max - min + 1) + min;只给min时Math.abs(random.nextLong()) + min;只给max时random.nextLong(max)。构造时要求min <= max,若设置了domainSize还要求domainSize <= max - min。
5.5randomList:嵌套列表生成器
产出List<T>,用于tags、spans这类集合字段,是模板嵌套组合的核心。
size(int):列表长度,保证generatedList.size() == size;item(object):列表项的"原型"模板,可继续用任意生成器组合。例如生成Tag列表时,item就是由key、value两个生成器组成的 Tag 原型。
官方 segment 模板中的 tags 示例:
"tags": { "type": "randomList", "size": 5, "item": { "key": { "type": "randomString", "length": "10", "prefix": "test_tag_", "letters": true, "numbers": true, "domainSize": 10 }, "value": { "type": "randomString", "length": "10", "prefix": "test_value_", "letters": true, "numbers": true } } }从 ListGenerator.java 看,next()用IntStream.range(0, size)复制item原型size份,reset()会级联重置 item 内部的生成器状态。
5.6fixedString:固定字符串
总是返回固定的value,实现极简(FixedStringGenerator.java),适合serviceName等需要恒定值的字段:
"serviceName": { "type": "fixedString", "value": "service_" }5.7sequence:单调递增序列(可带波动)
产出Long,默认严格递增,适合时间戳、序号等有序字段。
min(long):序列最小值;max(long):序列最大值;step(long):步长,即下一个值 == 上一个值 + step;domainSize(int):与randomString的domainSize语义相同;fluctuation(int):在递增基础上叠加一个>= -fluctuation且<= fluctuation的随机波动。
例如min = 10, max = 15, step = 1生成严格序列[10, 11, 12, 13, 14, 15];加上fluctuation = 2后可能生成[10, 12, 11, 14, 13, 15]这样的带抖动序列。
源码实现(SequenceGenerator.java)值得注意两点:
- 有波动时先按
next = last + step计算,再随机加减random.nextInt(fluctuation),最后做min/max的夹逼(越界时返回边界值); - 开启
domainSize后,会在reset()时预先填充domainSize个不重复的序列值,之后每次从候选域随机抽取,因此不再保证严格递增。
单元测试 SequenceGeneratorTest.java 验证了默认严格递增与fluctuation = 1时波动值满足i <= next <= i*2两种行为。
5.8time:真实时钟时间戳
产出Long(毫秒时间戳),两个参数(均有默认值):
stepMillisecond:每次取值的递增毫秒数,默认1000(即默认每秒 +1000ms,等价于跟随真实时间);waitMillisecond:两次"与真实时钟同步"之间的最小间隔,默认0。
实现见 TimeGenerator.java:每次先取System.currentTimeMillis(),若与上次同步时间差小于waitMillisecond,则在上次值基础上incrementAndGet()(防止时钟回拨导致时间倒退),否则同步真实时间并addAndGet(stepMillisecond)。
六、官方模板解读:从零理解组合方式
6.1 Segment 模板(segment-template.json)
Segment 模板顶层字段为traceId、serviceInstanceName、serviceName、segments,其中segments是一个randomList,其item又包含endpointName、error、tags、spans等字段;spans同样是randomList,item 内是latency、operationName、componentId、error、tags。整体形成"请求 → 段列表 → 跨度列表 → 标签列表"的多层嵌套结构:
{ "traceId": { "type": "uuid", "changingFrequency": "1" }, "serviceInstanceName": { "type": "randomString", "length": "10", "letters": true, "numbers": true, "domainSize": 10 }, "serviceName": { "type": "fixedString", "value": "service_" }, "segments": { "type": "randomList", "size": 5, "item": { "endpointName": { "type": "randomString", "length": "10", "prefix": "test_", "letters": true, "numbers": true, "domainSize": 10 }, "error": { "type": "randomInt", "min": 1, "max": 1 }, "tags": { "type": "randomList", "size": 5, "item": { "key": { "type": "randomString", "length": "10", "prefix": "test_tag_", "letters": true, "numbers": true, "domainSize": 5 }, "value": { "type": "randomString", "length": "10", "prefix": "test_value_", "letters": true, "numbers": true, "domainSize": 10 } } }, "spans": { "type": "randomList", "size": 5, "item": { "latency": { "type": "randomInt", "min": 100, "max": 1000 }, "operationName": { "type": "randomString", "length": "10", "prefix": "test_endpoint_", "letters": true, "numbers": true }, "componentId": { "type": "randomInt", "min": "0", "max": "4" }, "error": { "type": "randomBool", "possibility": "0.2" }, "tags": { "type": "randomList", "size": 5, "item": { "key": { "type": "randomString", "length": "10", "prefix": "test_tag_key_", "letters": true, "numbers": true, "domainSize": 10 }, "value": { "type": "randomString", "length": "10", "prefix": "test_tag_val_", "letters": true, "numbers": true } } } } } } } }模板背后的组装逻辑在 SegmentRequest.java 与 SegmentGenerator.java 中:init(group)时先为每个 segment 生成服务名(若传了group参数则形如group::service_0),next()时生成一条 trace 下的一串 segment,后一个 segment 通过parentSegment引用前一个 segment,并自动构造SegmentReference(父 span id、parentTraceSegmentId 等),从而模拟出同一条 trace 内的跨 segment 父子调用关系;同时生成SegmentObject(走 gRPC 解析链路)与 OAP 侧的Segment记录(含 serviceId、endpointId、latency、timeBucket 等聚合所需字段)。也就是说,mock 出的 segment 数据是带有完整调用链拓扑结构的,而不是孤立片段。
单元测试 SegmentGeneratorTest.java 会对模板连续调用 1000 次并断言:服务名集合大小介于 2~10(
serviceName由 fixedString + domainSize 决定),实例名与 endpoint 集合大小介于 2~100——可以直接用来验证模板中domainSize参数的取值效果。
6.2 Log 模板(logs-template.json)
Log 模板顶层字段包括timestamp(sequence生成 1649643929000~1649653929000 毫秒时间戳)、serviceName、serviceInstanceName、endpointName、traceId、traceSegmentId、spanId、contentType、content、error、tags,覆盖了 OAP Log 数据的基本字段:
{ "timestamp": { "type": "sequence", "min": "1649643929000", "max": "1649653929000" }, "serviceName": { "type": "randomString", "length": "20", "prefix": "test_svc_name_", "letters": true, "numbers": true }, "serviceInstanceName": { "type": "randomString", "length": "20", "prefix": "test_svc_inst_name_", "letters": true, "numbers": true }, "endpointName": { "type": "randomString", "length": "20", "prefix": "test_endpoint_", "letters": true, "numbers": true }, "traceId": { "type": "randomString", "length": "20", "prefix": "test_trace_id_", "letters": true, "numbers": true }, "traceSegmentId": { "type": "randomString", "length": "20", "prefix": "test", "letters": true, "numbers": true }, "spanId": { "type": "randomInt", "min": "0", "max": "5" }, "contentType": { "type": "randomInt", "min": 1, "max": 1 }, "content": { "type": "randomString", "length": "10", "prefix": "test", "letters": true, "numbers": true }, "error": { "type": "randomBool" }, "tags": { "type": "randomList", "size": 5, "item": { "key": { "type": "randomString", "length": "10", "prefix": "test", "letters": true, "numbers": true, "domainSize": 10 }, "value": { "type": "randomString", "length": "10", "prefix": "test", "letters": true, "numbers": true } } } }Log 的生成链路相对更直接:LogGeneratorHandler.java 将模板反序列化为LogRequest,next()产出Log对象后直接交给SourceReceiver.receive(l),进入 OAP 的日志分析(LAL)与存储流程,因此可以用它配合 log-analyzer.md 等日志分析功能做端到端验证。
七、JSON 模板编写要点与注意事项
综合官方模板与生成器源码,编写模板时有几个实用要点:
type字段是硬性要求:每个生成器节点必须有type,Jackson 依据 Generator.java 的@JsonSubTypes映射完成反序列化,未知类型会直接报错;- 数值字段的字符串/数字写法皆可:官方模板中
length、min、max、possibility等参数既出现过字符串("10")也出现过数字(5)写法,Jackson 的宽松绑定两者均可接受; randomInt的max是开区间:希望恒定取 1 时可写成"min": 1, "max": 1(如模板中的error、contentType字段);- 善用
domainSize控制数据分布:需要"少量取值反复出现"(模拟真实场景中有限的 service/endpoint/tag 集合)时设置domainSize,且注意IntGenerator/SequenceGenerator会校验domainSize <= max - min; - 多层嵌套靠
randomList的item递归:Segment 模板中 spans → tags 的层级即是典型用法,ListGenerator.reset()会级联重置内部生成器,保证每次任务生成数据的一致性; qps任务记得取消:qps模式默认永久运行(除非设置了duration),长时间压测后应通过 DELETE 接口清理,避免资源占用;- 模板文件可复用:建议将模板保存为
segment-template.json/logs-template.json之类的文件,配合curl -d "@file"使用,便于版本化管理。
八、典型应用场景小结
结合模块能力,数据生成器在以下场景中尤为实用:
- 功能自测:OAP 开发或二次开发时,用
size模式快速制造一批 segment/log,验证存储写入、查询接口与 UI 展示是否符合预期; - 告警验证:用
randomBool(possibility 控制错误率)与sequence(时间戳推进)构造带错误的 trace,配合 backend-alarm.md 验证告警规则是否触发; - 存储压测:用
qps模式持续灌入数据,配合domainSize控制基数,观察 Elasticsearch / BanyanDB / JDBC 等存储插件(见 backend-storage.md)在不同写入速率下的表现; - 演示环境铺底:为 UI 演示或培训环境批量生成有调用链拓扑、日志与 tag 的"仿真数据",无需真实业务系统。
需要再次说明的是:官方并未发布该模块的 Docker 镜像,但仓库提供make docker.data-generator一键构建,构建产物与启动细节可参考 docker/data-generator/Dockerfile 与 docker/data-generator/docker-entrypoint.sh;本仓库 docker-compose.yml 中的 OAP 健康检查也使用了curl http://localhost:12800/internal/l7check,说明 12800 端口是 HTTP 侧的统一入口,mock 接口与其同端口共存。
【免费下载链接】skywalkingAPM, Application Performance Monitoring System项目地址: https://gitcode.com/gh_mirrors/sky/skywalking
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考