# rocketmq-grpc-benchmark
**Repository Path**: project-store-mark/rocketmq-grpc-benchmark
## Basic Information
- **Project Name**: rocketmq-grpc-benchmark
- **Description**: 企业级 RocketMQ 性能测试工具 · 原生 gRPC Proxy
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 1
- **Created**: 2026-07-01
- **Last Updated**: 2026-07-13
## Categories & Tags
**Categories**: Uncategorized
**Tags**: mq, 性能, 测试, Grpc
## README
# 🚀 rocketmq-pressure
**企业级 RocketMQ 性能测试工具 · 原生 gRPC Proxy**
基于 `rocketmq-client-java` 5.x (gRPC) 的生产可用压测利器 —— 非演示 Demo
[English](README.md) · **简体中文**






[功能特性](#-功能特性) · [架构](#-架构) · [快速开始](#-快速开始) · [配置](#%EF%B8%8F-配置) · [使用](#-使用) · [监控](#-监控) · [路线图](#-路线图) · [贡献](#-贡献)
---
> **不是演示项目。** 这是一套可维护、生产可用的负载发生器,可直接部署到多台机器,对 RocketMQ 5.x 进行真实性能压测——配备精确的 HDR 直方图分位值、JVM/OS 监控、Prometheus 指标暴露,以及自动生成的 CSV/JSON 报告。
## ✨ 功能特性
| 领域 | 能力 |
|---|---|
| **生产者** | 同步 / 异步 / 单向 / 批量 / 事务(预留) · 固定字节 / 随机字符串 / 随机 JSON / 固定 JSON 负载 · 1K–256K 消息大小 · 令牌桶固定 TPS 限速 · 多 Topic(1/10/50/100/200…) · 固定负载零分配复用 |
| **消费者** | PushConsumer / SimpleConsumer · 集群 / 广播模式 · 多消费组 · Tag / SQL92 过滤 |
| **压测控制** | 固定 TPS · 不限速 · 时长控制 · 持续发送/消费 · 可配置消息大小与内容 |
| **实时统计** | 生产/消费 TPS · 成功/失败计数 · 平均/最大 RT · **P50/P90/P95/P99/P999(HdrHistogram)** · 吞吐 MB/s · 消息积压 · 每秒打印 |
| **报告** | 自动生成 **CSV + JSON**:起止时间、时长、TPS、RT 分位、吞吐、失败率 |
| **监控** | CPU / 内存 / GC / 线程 / 网络 / JVM Heap / NonHeap / 文件描述符 · **Prometheus 端点** |
| **工程规范** | SOLID · 单一职责 · SLF4J 日志 · 阿里 Java 规范 · 纯 `ThreadPoolExecutor`(禁止裸 `new Thread`) · 线程异常隔离 |
## 🏗 架构
```
┌──────────────────────────────────────┐
│ PressureRunner │ 角色分发 · 生命周期
│ CommandLineRunner + @PreDestroy │ 优雅关闭 · 报告
└──────────┬───────────────┬────────────┘
┌──────────────────────┘ └─────────────────────┐
┌───────▼─────────┐ ┌──────▼──────────┐
│ ProducerEngine │ │ ConsumerEngine │
│ SendWorker ×N │ │ Push / Simple │
└───────┬─────────┘ └──────┬──────────┘
│ gRPC (Proxy :8080) │
▼ ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ RocketMQ 5.4.0 Proxy (gRPC) │
└──────────────────────────────────────────────────────────────────────────────┘
横切关注点:Statistics(HdrHistogram+LongAdder) · Monitor(JVM/OS) · Report(CSV/JSON) · Micrometer→Prometheus
```
**分层设计(SOLID,每类单一职责):**
```
com.yyq
├── RocketmqPressureApplication # 启动入口 (@ConfigurationPropertiesScan)
├── common 枚举 · 常量 · 异常
├── model @ConfigurationProperties 配置类(唯一参数源)
├── config 客户端工厂 · 线程池 · Micrometer 绑定
├── thread 命名线程工厂 + 未捕获异常兜底
├── util 负载 · byte[] 池 · 限流器 · json/csv/host/time
├── statistics 收集器 · 快照 · 打印器 · 任务
├── monitor 采集器 · 快照 · 任务
├── report 模型 · csv/json 写入器 · 生成器
├── producer 发送策略 · Worker · Engine
├── consumer Push/Simple 管理器 · 监听器 · Worker · Engine
└── service PressureRunner 编排器
```
## 🧱 技术栈
| 层 | 选型 |
|---|---|
| 运行时 | JDK 17, Spring Boot 3.3.x |
| 消息 | `org.apache.rocketmq:rocketmq-client-java` 5.x (gRPC) |
| 分位值 | HdrHistogram 2.x |
| 指标 | Micrometer + Actuator + Prometheus registry |
| 序列化 | Jackson |
| 构建 | Maven 3.8+ |
## 📦 环境要求
- **RocketMQ 5.x** 且 **Proxy(gRPC)** 已启动(默认 `192.168.1.29:8080`,认证关闭)
- 每台压测机 **JDK 17**
- 构建机 **Maven 3.8+**
- 推荐 Linux(CentOS / Alibaba Linux);文件描述符与网络指标读取 `/proc`
## 🚀 快速开始
```bash
# 1. 构建 fat jar
mvn clean package
# → target/rocketmq-pressure.jar
chmod +x scripts/*.sh
# 2. 单机冒烟:先起消费,再起小流量生产
./scripts/start-consumer.sh --consumer.thread-count=20 --consumer.duration=90
sleep 3
./scripts/start-producer.sh \
--producer.send-mode=sync --producer.tps-limit=200 \
--producer.message-size=1024 --producer.thread-count=10 \
--producer.duration=60 --server.port=8082 # 第二实例换端口
# 3. 观察实时统计
tail -f logs/rocketmq-pressure.log
# 4. 停止并查看报告
./scripts/stop.sh
cat report/*.csv
```
**通过判据:** `sendSuccessTotal ≈ 12000`,`consumeSuccessTotal` 接近,`failureRate ≈ 0`。
## ⚙️ 配置
全部参数位于 [`conf/application.yml`](conf/application.yml)。启动脚本以 `spring.config.additional-location` 加载它,覆盖 jar 内默认配置。**命令行参数 `--key=value` 再次覆盖文件。**
> 优先级:**命令行参数** > `conf/application.yml` > 打包的 `src/main/resources/application.yml`
### 全局角色
| 键 | 取值 |
|---|---|
| `pressure.role` | `producer` / `consumer` |
### 连接
| 键 | 示例 |
|---|---|
| `rocketmq.endpoint` | `192.168.1.29:8080` |
| `rocketmq.request-timeout` | `3s` |
| `rocketmq.ssl-enabled` | `false` |
| `rocketmq.access-key` / `rocketmq.secret-key` | 空 = 不鉴权 |
#### 多 Proxy 端点与流量均分
5.x gRPC 客户端支持**多端点**,用 `;`(分号)分隔。2 个 Proxy 示例:
```yaml
rocketmq:
endpoint: 192.168.1.190:8080;192.168.1.186:8080
```
> 命令行使用时**务必加引号**,避免 shell 吞掉分号:
> `--rocketmq.endpoint='192.168.1.190:8080;192.168.1.186:8080'`
> 支持**任意数量**端点(1、2、7、…)。
**为什么需要均分**:5.x 客户端对多端点采用 `pick_first`——每个实例只连**第一个**端点,其余仅作故障转移。若所有节点端点串相同,流量会全部压到同一个 Proxy。
**本工具自动均分(各节点命令完全一致)**:按实例序号对端点列表做循环轮转,使不同实例选到不同首选 Proxy。轮转偏移量按以下优先级解析:
| 优先级 | 来源 | 怎么给 |
|---|---|---|
| 1 | 环境变量 `PRESSURE_NODE_INDEX` | `export PRESSURE_NODE_INDEX=0/1/2…` |
| 2 | 系统属性 `-DPRESSURE_NODE_INDEX=N` | JVM 参数 |
| 3 | 文件 `/etc/rocketmq-pressure/node-index` | 每台机器放一个数字 |
| 4 | 都没有 | 回退主机名哈希(近似均分) |
设置实例序号后,**N 台机器对 M 个端点严格均分**(每个端点分到 ⌊N/M⌋ 或 ⌈N/M⌉ 个实例,数学最优)。各实例仍保留全部端点作故障转移。启动日志会打印实际首选端点:
```text
Endpoints balanced (nodeIndex=3): primary=[192.168.1.186:8080] (offset=1/2), order=...
```
### 生产者参数
| 键 | 默认 | 说明 |
|---|---|---|
| `producer.topic` | `test_yyq_simple-workload-0` | 单 Topic / 派生前缀 |
| `producer.topics` | `[]` | 显式列表(优先级最高) |
| `producer.topic-count` | `1` | `>1` 派生 `prefix-0..N-1` |
| `producer.thread-count` | `200` | 发送线程数 |
| `producer.message-size` | `1024` | 字节(1K/4K/16K/64K/128K/256K) |
| `producer.payload-type` | `fixed_bytes` | `fixed_bytes`(最快、零分配复用)/`random_string`/`random_json`/`fixed_json`。random_* 每条重新生成 → 吞吐天花板显著降低 |
| `producer.attach-keys` | `false` | 是否附加 `pressure-` Keys。关闭=无全局计数器争用、无每条字符串分配(最快)。开启=可按 Keys 查询 |
| `producer.send-mode` | `async` | `sync`/`async`/`oneway`/`batch`/`transaction` |
| `producer.batch-size` | `1` | 每批条数 |
| `producer.tps-limit` | `0` | `0` = 不限速 |
| `producer.duration` | `1800` | 秒,`<=0` = 持续 |
| `producer.max-attempts` | `3` | 客户端重试 |
| `producer.max-inflight` | `10000` | async/oneway 最大在途消息数(背压);`0` = 不限。大消息请下调,峰值堆 ≈ maxInflight × messageSize |
| `producer.message-group` / `producer.tag` | 空 | 可选顺序分组 / Tag |
### 消费者参数
| 键 | 默认 | 说明 |
|---|---|---|
| `consumer.group` | `pressure-group` | 基础消费组 |
| `consumer.groups` | `[]` | 显式多组 |
| `consumer.topics` | `[]` | 空 → 复用 producer 的 Topic |
| `consumer.thread-count` | `200` | push 并发线程 / SimpleConsumer 拉取线程 |
| `consumer.consumer-type` | `push` | `push` / `simple` |
| `consumer.consume-mode` | `clustering` | `clustering` / `broadcasting` |
| `consumer.filter-expression` | `*` | tag 或 SQL92 |
| `consumer.filter-type` | `tag` | `tag` / `sql92` |
| `consumer.await-duration` | `30s` | Simple 长轮询等待 |
| `consumer.invisible-duration` | `15s` | Simple 不可见时长 |
| `consumer.receive-batch-size` | `64` | Simple 单次拉取(越大吞吐越高,仅 simple 生效) |
| `consumer.max-inflight-ack` | `4096` | Simple 异步 ack 在途上限(仅 simple 生效);`0` = 不限。既限制内存(≈ 上限 × 消息大小),又保证 ack 在 `invisible-duration` 内完成,避免 Broker 重投 → 多节点集群**不重复消费** |
| `consumer.max-cache-message-count` | `1024` | Push 缓存消息数 |
| `consumer.max-cache-message-size` | `128MB` | Push 缓存字节 |
| `consumer.duration` | `1800` | 秒,`<=0` = 持续 |
### 统计 / 监控 / 报告 / Web
| 键 | 默认 |
|---|---|
| `statistics.enabled` / `statistics.interval` | `true` / `1` |
| `statistics.per-topic-breakdown` | `false` —— 每条消息维护按 Topic 聚合计数(用于报告按 Topic 明细)。关闭 = 热路径跳过按 Topic 查找与写入(开销最低);开启 = 报告含按 Topic 明细 |
| `monitor.enabled` / `monitor.interval` / `monitor.collect-fd` | `true` / `5` / `true` |
| `report.enabled` / `report.dir` / `report.formats` | `true` / `report` / `[csv,json]` |
| `server.port` | `8081`(避开 Proxy `8080`) |
| `management.endpoints.web.exposure.include` | `prometheus,health,info` |
### 性能调优与 JVM 资源(16C16G / 5Gb)
启动脚本以 `MEM` 环境变量启动 JVM(默认 `-Xms4g -Xmx4g`)。启动前按机器覆盖即可——想"靠加线程把机器吃满",**这是第一道要先抬高的天花板**:
```bash
MEM="-Xms8g -Xmx8g" ./scripts/start-producer.sh --producer.thread-count=1000
```
`producer.thread-count` **没有硬性上限**,可随意调大。真正的天花板是 JVM 堆与 async 在途内存:
| 发送模式 | 并发上限来源 | 提升吞吐的方法 |
|---|---|---|
| `sync` | `thread-count`(每线程阻塞等 ack) | 调大 `producer.thread-count` |
| `async` / `oneway` | `producer.max-inflight`(信号量背压) | 调大 `max-inflight`(盯住堆),可选调 `thread-count` |
| `batch` | `batch-size × thread-count`(`join()` 自限) | 调大 `batch-size` |
**`producer.max-inflight`** 限制 `async`/`oneway` 同一时刻未完成的 `sendAsync` 数量。broker 变慢时,发送线程在获取许可处阻塞,而不是把消息堆进内存 → 不再 OOM,而是优雅自限速。峰值堆 ≈ `maxInflight × messageSize`(RANDOM 负载);设 `0` 完全关闭背压。
16C16G / 5Gb 推荐起点:
```bash
MEM="-Xms8g -Xmx8g" ./scripts/start-producer.sh \
--producer.send-mode=async --producer.tps-limit=0 \
--producer.payload-type=fixed_bytes --producer.attach-keys=false \
--producer.message-size=1024 --producer.thread-count=64 \
--producer.max-inflight=50000 --producer.duration=1800
```
> **async 是 IO 密集,不是 CPU 密集。** 吞吐由 `max-inflight` 与 gRPC/Netty IO 线程决定,**不是**发送线程数。async 下**不要**把 `thread-count` 拉到几百——那些自旋线程会跟处理 ack、发数据的 Netty IO 线程抢 CPU,反而*降低*吞吐。`thread-count` 保持在 `核数 × 2~4`,靠抬 `max-inflight` 提速(盯住堆 ≈ `maxInflight × messageSize`)。只有 `sync` 模式,`thread-count` 才是真正的并发旋钮。
**调参方法**:阶梯式抬 `max-inflight`(async/oneway)或 `thread-count`(sync),盯 `pressure_producer_tps`(Prometheus)与每秒控制台行。当 TPS 不再涨(只剩 RT / 失败率上升),就到了 **broker** 的天花板,即停。压测机 CPU 宜保持在 80% 以下,RT/TPS 才可信——压测机自己被打满,瓶颈就变成了测试工具而不是被测系统。
大消息(≥64KB)请下调 `max-inflight`,使 `maxInflight × messageSize` 远小于堆。极限吞吐下的可选进阶参数:`-XX:MaxDirectMemorySize=4g`(gRPC/Netty 直接内存)。
**消费侧** —— `consumer.thread-count` 无上限随意调大。要做**大批量 / 快速消费**,两条路径旋钮不同:
| 消费类型 | "批量"的含义 | 提升吞吐的旋钮 |
|---|---|---|
| `simple` | 真正的批量拉取——`receive()` 一次最多返回 `receive-batch-size` 条 | `consumer.receive-batch-size` + `consumer.thread-count` |
| `push` | 无批量概念(推模式、逐条回调)——只能靠并发 | `consumer.thread-count` + `consumer.max-cache-message-count` + `consumer.max-cache-message-size` |
`receive-batch-size` 只对 `simple` 生效。吞吐 ≈ `thread-count × receive-batch-size ÷ 单次往返`。注意 `await-duration`/`invisible-duration` **不是**提速旋钮(长轮询等待 / 重投语义,与速度无关)。
```bash
# 大批量快速拉取(simple)——最大化消费吞吐的正解
MEM="-Xms8g -Xmx8g" ./scripts/start-consumer.sh \
--consumer.consumer-type=simple --consumer.receive-batch-size=256 \
--consumer.thread-count=300 --consumer.duration=1800
# 高并发推送(push)——thread-count 与 cache 要一起抬
MEM="-Xms8g -Xmx8g" ./scripts/start-consumer.sh \
--consumer.consumer-type=push --consumer.thread-count=300 \
--consumer.max-cache-message-count=8192 --consumer.max-cache-message-size=512MB
```
`simple` 的 ack 是逐条的,但走 **`ackAsync`**(同一 worker 的多条 ack 并发在途,不串行),`receive-batch-size` 调很大时 ack 数线性增长但影响有限。`consumer.max-inflight-ack` 限制每个 SimpleConsumer 的在途 ack 数——既限制内存(≈ 上限 × 消息大小),又保证 ack 在 `invisible-duration` 内完成、Broker 不重投,从而保证多节点集群**不重复消费**。置 `0` 关闭背压(ack 跟不上时有重投风险)。
> **多节点不重复消费。** 集群模式(默认,各节点同一 `consumer.group`)本身按消息级负载均衡——每条消息只投递给一个节点。异步 ack 设计保持这一点:一条消息**只有在 ack 成功后才计为已消费**,在途上限保证 ack 不超过 `invisible-duration`,避免 Broker 重投。广播模式(`consume-mode=broadcasting`)有意让每个节点消费 100%(通过每个实例独立消费组模拟),其"重复"是设计语义。
## 📖 使用
### 生产者
```bash
# 异步、不限速、1KB、200 线程、30 分钟
./scripts/start-producer.sh \
--producer.send-mode=async --producer.tps-limit=0 \
--producer.message-size=1024 --producer.thread-count=200 --producer.duration=1800
# 自定义 Topic & 固定 5000 TPS
./scripts/start-producer.sh --producer.topic=my_topic --producer.tps-limit=5000
```
| `send-mode` | 行为 |
|---|---|
| `sync` | 阻塞等待确认,可靠性最高,RT 最准 |
| `async` | `sendAsync` + 回调,吞吐/可靠性均衡(**推荐**) |
| `oneway` | 发射后即忘,无确认(见[设计说明](#-设计说明)) |
| `batch` | 每周期提交 `batch-size` 条并等待 |
| `transaction` | 预留 |
### 消费者
```bash
# Push、集群、100 线程
./scripts/start-consumer.sh --consumer.consumer-type=push --consumer.thread-count=100
# Simple + SQL92 过滤
./scripts/start-consumer.sh --consumer.consumer-type=simple \
--consumer.filter-type=sql92 --consumer.filter-expression="Tag='A'"
```
> **先起消费再起生产** —— 新消费组默认从最新位消费。
## 📊 统计与报告
**实时(每秒):**
```
PTPS SendSucc SendFail ConsSucc ConsFail AvgRT MaxRT P50 P90 P95 P99 P999 MB/s Backlog FailR%
12345 1200034 12 1199500 5 2.31 45.20 1.95 3.10 4.02 6.88 12.40 15.20 5300 0.01
```
**报告**(停止后在 `report/`):`pressure-<角色>-<时间戳>-<主机>.{csv,json}` —— 起止时间、时长、角色、Topic、消费组、线程数、消息大小、TPS、平均/最大 RT、P50–P999、吞吐、成功/失败计数、失败率。
## 📈 监控
每个实例暴露 Prometheus 端点:
```
http://<压测机>:8081/actuator/prometheus
```
| 指标 | 类型 | 说明 |
|---|---|---|
| `pressure_producer_tps` | gauge | 生产者 msg/s |
| `pressure_consumer_tps` | gauge | 消费者 msg/s |
| `pressure_throughput_mbps` | gauge | 吞吐 MB/s |
| `pressure_consumer_backlog` | gauge | 消息积压 |
| `pressure_producer_send_success_total` | gauge | 累计发送成功 |
| `pressure_producer_send_failure_total` | gauge | 累计发送失败 |
| `pressure_consumer_consume_success_total` | gauge | 累计消费成功 |
| `pressure_consumer_consume_failure_total` | gauge | 累计消费失败 |
同机跑两个实例 → 第二个加 `--server.port=8082`。
## 🌐 分布式压测(6 台)
主机:`192.168.64.46 / .49`(消费) · `192.168.66.219 / .220 / .221 / .222`(生产)。
```bash
# 构建机:构建一次,分发 jar+配置+脚本到其余机器
for h in 192.168.64.49 192.168.66.219 192.168.66.220 192.168.66.221 192.168.66.222; do
ssh user@$h "mkdir -p ~/mq-test-tool/{target,conf,scripts,logs,report}"
scp target/rocketmq-pressure.jar user@$h:~/mq-test-tool/target/
scp conf/application.yml user@$h:~/mq-test-tool/conf/
scp scripts/*.sh user@$h:~/mq-test-tool/scripts/
done
# 消费机(46、49)—— 同组 → 集群负载均衡
./scripts/start-consumer.sh --consumer.thread-count=100 --consumer.duration=1900
# 生产机(219/220/221/222)
./scripts/start-producer.sh --producer.send-mode=async --producer.thread-count=200 --producer.duration=1800
# 停止(优雅,自动生成报告)
./scripts/stop.sh
```
## 🛠 排障
| 现象 | 处理 |
|---|---|
| `mvn` 解析不到 `rocketmq-client-java` | Maven Central 当前到 `5.2.0`;保持即可,或私有 Nexus 有更高版本则改 `pom.xml` 的 `rocketmq-client-java.version` |
| `endpoints should not be null` | 检查 `conf/application.yml` 的 `rocketmq.endpoint` 是否被加载 |
| 发送失败持续增加 | 检查压测机到 `192.168.1.29:8080` 网络、Proxy 是否启动、ACL 是否需开启 |
| 消费为 0 | **先起消费再起生产**;确认 Topic/消费组一致 |
| 同机端口冲突 | 第二实例 `--server.port=8082` |
| 无报告 | 检查 `report.enabled=true`、目录可写、`logs/` 是否有 `finalizing` 日志 |
| OOM / GC 频繁 | 调大 `MEM`(默认 `-Xms4g -Xmx4g`),降低 `thread-count`/`batch-size`/`max-inflight`/`max-cache-message-size` —— 见 [性能调优](#性能调优与-jvm-资源16c16g--5gb) |
## 📌 设计说明
- **客户端版本 `5.2.0`** —— Broker 为 5.4.0;`rocketmq-client-java` 在 Maven Central 当前可解析至 `5.2.0`。官方兼容矩阵确认客户端 `5.0.0+` 可连接 Broker `5.4.0`(服务端向后兼容)。若私有仓库有更新版本,改 `pom.xml` 的属性即可,API 向前兼容。
- **广播模式** —— 5.x gRPC 客户端的 `PushConsumer`/`SimpleConsumer` **原生仅支持集群**(API 已移除 `MessageModel.BROADCASTING`)。本工具在 `broadcasting` 下为**每个实例生成独立消费组**(`--`),各实例独立消费全量,等价广播语义,与官方描述一致。
- **单向发送** —— 5.x `Producer` 接口不再提供 `sendOneway`;`OnewaySender` 用 `sendAsync` 发射后即忘(立即返回、无确认、时延记 0),保证跨版本可编译。
- **异常隔离** —— 各职责独立线程池;`NamedThreadFactory` 设置未捕获异常处理器,单线程异常仅记日志、绝不崩溃进程;单条消息异常被捕获并计入失败。
## 🗺 路线图
- [ ] `TransactionSender` 实现(两阶段 + `TransactionChecker`)
- [ ] `cluster-run.sh` 一键编排(分发 → 起消费 → 起生产 → 停止 → 回收报告)
- [ ] 5.x 客户端开放原生批量 API 后接入
- [ ] 基于 JSON 报告的内置 HTML 仪表盘
## 🤝 贡献
欢迎贡献!本项目遵循**阿里 Java 开发规范**与 SOLID 原则。
1. Fork 并开分支:`feat/<主题>` 或 `fix/<主题>`
2. 保持每类单一职责;完整 Javadoc + SLF4J 日志;禁止裸 `new Thread`
3. 确保 `mvn clean package` 通过(JDK 17)
4. 提交 PR 说明改动与理由
如遇编译/运行问题,请附上你的 `rocketmq-client-java` 版本,以便我们定位 API 差异。
## 📄 许可证
基于 **Apache License, Version 2.0** 发布。详见 [LICENSE](LICENSE)。
## 🙏 致谢
- [Apache RocketMQ](https://rocketmq.apache.org/) 及 5.x gRPC 客户端
- [HdrHistogram](https://github.com/HdrHistogram/HdrHistogram) 精确分位值
- [Spring Boot](https://spring.io/projects/spring-boot)、[Micrometer](https://micrometer.io/)
---
为需要可信数据、而非玩具演示的工程师而构建。