单节点部署与基准测试:Docker Compose部署、使用JMeter/TB内部工具进行压力测试、理解吞吐量(TPS)与延迟指标

好,咱们直接进入正题。这一章我打算带你亲手搭建一个ThingsBoard单节点环境,然后给它来一顿“压力测试”。说白了,就是看看这小家伙到底能吃下多少活儿。

我个人习惯,在调优之前,先拿到一组基准数据。没有基准,你后面做的所有优化都是“盲人摸象”。你想想看,你改了个参数,系统快了还是慢了?没有对比数据,你根本说不清楚。

2.1 用Docker Compose快速部署单节点

部署ThingsBoard,我推荐用Docker Compose。为什么?因为干净、可复现、好清理。我在项目中遇到过好几次,生产环境里因为手动部署漏了某个依赖,排查了一整天。用Compose,一个文件搞定所有。

下面这个是我常用的单节点部署文件,去掉了高可用的部分,专注于性能测试。

version: '3.8'
services:
  mypostgres:
    image: postgres:15
    environment:
      POSTGRES_DB: thingsboard
      POSTGRES_PASSWORD: postgres
    volumes:
      - tb-postgres-data:/var/lib/postgresql/data
    networks:
      - tb-net

  mytb:
    image: thingsboard/tb:3.6.1
    depends_on:
      - mypostgres
    ports:
      - "8080:8080"
      - "1883:1883"
      - "5683:5683/udp"
    environment:
      TB_QUEUE_TYPE: in-memory
      SPRING_DATASOURCE_URL: jdbc:postgresql://mypostgres:5432/thingsboard
      SPRING_DATASOURCE_USERNAME: postgres
      SPRING_DATASOURCE_PASSWORD: postgres
    volumes:
      - tb-data:/data
    networks:
      - tb-net

volumes:
  tb-postgres-data:
  tb-data:

networks:
  tb-net:
    driver: bridge

嗯,这里要注意。我把队列类型设成了 in-memory。这是单节点测试的常用配置,省去了Kafka或RabbitMQ的依赖。但你要记住,生产环境千万别这么干,否则一重启,队列里的消息全丢了。

启动命令很简单:

docker-compose up -d

等个一两分钟,看看日志:

docker-compose logs -f mytb | grep "Started ThingsBoard Server"

看到“Started”字样,就说明部署成功了。这时候访问 http://localhost:8080,用默认账号 sysadmin@thingsboard.org / sysadmin 登录。

我的小技巧: 第一次启动时,ThingsBoard会初始化数据库和缓存,可能会慢一些。我建议你泡杯咖啡,等个3-5分钟再访问。别心急,心急吃不了热豆腐。

2.2 准备测试数据:设备与遥测

基准测试不能空跑。你得先创建一批模拟设备,让它们持续上报数据。我个人习惯用TB自带的REST API来批量创建设备。

下面这个Python脚本,我经常用它来生成测试数据:

import requests
import json
import time

BASE_URL = "http://localhost:8080"
HEADERS = {
    "Content-Type": "application/json",
    "Accept": "application/json"
}

# 先登录获取token
login_data = {
    "username": "tenant@thingsboard.org",
    "password": "tenant"
}
login_resp = requests.post(f"{BASE_URL}/api/auth/login", json=login_data, headers=HEADERS)
token = login_resp.json().get("token")
HEADERS["X-Authorization"] = f"Bearer {token}"

# 批量创建100个设备
for i in range(100):
    device_data = {
        "name": f"benchmark-device-{i:03d}",
        "type": "benchmark"
    }
    resp = requests.post(f"{BASE_URL}/api/device", json=device_data, headers=HEADERS)
    if resp.status_code == 200:
        print(f"设备 {i:03d} 创建成功")
    else:
        print(f"设备 {i:03d} 创建失败: {resp.text}")
    time.sleep(0.1)  # 稍微慢一点,别把API打挂了

跑完这个脚本,你就有了100个测试设备。接下来,我们需要让这些设备上报数据。你可以用JMeter来模拟,也可以用TB自带的REST API直接推送。

2.3 使用JMeter进行压力测试

JMeter是压测的老牌工具了。我一般用它来模拟大量设备并发上报遥测数据。

先创建一个测试计划,结构如下:

  • 线程组: 设置并发用户数(比如50个线程)
  • HTTP请求默认值: 设置服务器地址和端口
  • HTTP头管理器: 添加 Content-Type: application/jsonX-Authorization: Bearer {token}
  • HTTP请求: 模拟设备上报遥测

HTTP请求的配置如下:

方法: POST
路径: /api/v1/{device_token}/telemetry
Body: {"temperature": 25.5, "humidity": 60, "timestamp": 1700000000000}

这里有个坑。每个设备都有自己的访问令牌(device token)。你需要在创建设备后,通过API获取它的token。我曾经因为忘了这茬,压测了半天,结果所有数据都报401错误。

获取设备token的API:

GET /api/device/{device_id}/credentials

返回结果里有个 credentialsId 字段,那就是设备的token。

避坑指南: 我曾经在压测时,把所有设备都用同一个token上报数据。结果ThingsBoard把数据都归到了同一个设备下,测试数据完全失真。记住,每个设备必须用自己独立的token。

2.4 使用TB内部工具进行压力测试

除了JMeter,ThingsBoard本身也提供了一些压测工具。我个人觉得,TB自带的 mqtt-benchmark 工具更贴近实际场景,因为它模拟的是真实的MQTT协议通信。

TB的Docker镜像里其实内置了一个压测脚本,位置在 /usr/share/thingsboard/tools/mqtt-benchmark.js。你可以这样调用:

docker exec -it mytb node /usr/share/thingsboard/tools/mqtt-benchmark.js \
  --host localhost \
  --port 1883 \
  --clients 50 \
  --rate 10 \
  --size 50 \
  --duration 60

参数说明:

参数 说明 我的建议值
--clients 模拟的客户端数量 50-200
--rate 每个客户端每秒发送的消息数 1-10
--size 每条消息的payload大小(字节) 50-200
--duration 测试持续时间(秒) 60-300

跑完之后,控制台会输出类似这样的结果:

Total messages sent: 30000
Total messages received: 29850
Success rate: 99.5%
Average latency: 12.3 ms
P99 latency: 45.7 ms
Throughput: 500 msg/s

这些数据,就是我们后面做性能优化的基准。

2.5 理解吞吐量(TPS)与延迟指标

好,数据拿到了。但你知道这些指标到底意味着什么吗?

吞吐量(TPS),全称是Transactions Per Second,每秒事务数。在ThingsBoard的场景里,一个“事务”通常指一条遥测数据从设备上报到入库的完整过程。TPS越高,说明系统处理能力越强。

延迟,指的是从设备发送数据到系统确认接收的时间差。通常我们会关注几个关键分位值:

  • 平均延迟: 所有请求延迟的平均值。容易被极端值影响。
  • P50延迟: 50%的请求在这个延迟以内。代表“一般情况”。
  • P99延迟: 99%的请求在这个延迟以内。代表“最差情况”。
  • P999延迟: 99.9%的请求在这个延迟以内。代表“极端情况”。

我见过不少团队,只看平均延迟,觉得“哎呀才10毫秒,系统性能很好嘛”。结果一查P99,发现已经飙到500毫秒了。为什么?因为平均延迟被大量的小请求拉低了,但那些慢请求才是真正的用户体验杀手。

我的经验: 做性能优化时,我一般盯着P99延迟看。只要P99延迟在可接受范围内(比如物联网场景通常要求P99 < 200ms),平均延迟再低一点高一点,问题都不大。但如果P99开始飙升,那就说明系统快扛不住了。

还有一个概念叫 吞吐量与延迟的关系。你想想看,当系统负载很低时,延迟通常也很低。但随着并发量增加,延迟会逐渐上升。当系统达到某个临界点后,延迟会急剧上升,甚至导致系统崩溃。这个临界点,就是系统的“最大吞吐量”。

所以,基准测试的目的,就是找到这个临界点。比如,你测出来在50个并发客户端、每秒500条消息时,P99延迟是50ms。当并发增加到100个客户端、每秒1000条消息时,P99延迟变成了200ms。当并发到150个客户端时,P99延迟直接飙到1秒以上。那么,这个系统的安全吞吐量,大概就在每秒800-1000条消息之间。

有了这个基准数据,后面你做任何优化——调整JVM参数、优化数据库连接池、改队列配置——都能用数据说话,而不是凭感觉。

一个小提醒: 基准测试不是跑一次就完事了。我建议你在不同时间段跑三次,取平均值。因为系统负载、网络波动、甚至Docker宿主机的CPU调度策略,都会影响测试结果。多跑几次,数据才可靠。

好了,这一章的内容就到这里。你手头应该已经有一个跑起来的单节点ThingsBoard,也拿到了第一份基准测试数据。下一章,我会带你深入分析这些数据,看看瓶颈到底在哪里。