时钟导致TDengine偶尔连接失败问题排查和解决

一、环境信息

CentOS 7.9
TDengine 3.1.0.0
OpenJDK 11

二、问题现象

一个数据接入程序,定时将数据从指定文件导入到TDengine,发现每天执行的约1000次导入作业里,有1%~2%是失败的,查询失败作业的日志可以看到抛出如下异常:

[2023-08-07 08:35:00.370] [INFO] [INFO ][2023-08-07 08:33:36] 导入中 2001637/QiJia003/1650038400000/Sec_20220416.csv, 已成功导入 51000 行, 182 MB
[2023-08-07 08:35:06.980] [INFO] [INFO ][2023-08-07 08:33:34] 导入中 2001637/QiJia001/1650038400000/Sec_20220416.csv, 已成功导入 57000 行, 183 MB
[2023-08-07 08:35:00.875] [ERROR] create connection SQLException, url: jdbc:TAOS://:/repo_QiJia_1sec, errorCode -2147483637, state 
java.sql.SQLException: TDengine ERROR (8000000b): Unable to establish connection
    at com.taosdata.jdbc.TSDBError.createSQLException(TSDBError.java:76) ~[k2box-storage-taos3-2.2.5.jar:?]
    at com.taosdata.jdbc.TSDBJNIConnector.executeQuery(TSDBJNIConnector.java:119) ~[k2box-storage-taos3-2.2.5.jar:?]
    at com.taosdata.jdbc.TSDBStatement.executeQuery(TSDBStatement.java:47) ~[k2box-storage-taos3-2.2.5.jar:?]
    at com.alibaba.druid.pool.DruidAbstractDataSource.validateConnection(DruidAbstractDataSource.java:1419) ~[k2box-storage-taos3-2.2.5.jar:?]
    at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1719) ~[k2box-storage-taos3-2.2.5.jar:?]
    at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2813) ~[k2box-storage-taos3-2.2.5.jar:?]
[2023-08-07 08:35:00.883] [ERROR] create connection SQLException, url: jdbc:TAOS://:/repo_QiJia_1sec, errorCode -2147483637, state 
java.sql.SQLException: TDengine ERROR (8000000b): Unable to establish connection
    at com.taosdata.jdbc.TSDBError.createSQLException(TSDBError.java:76) ~[k2box-storage-taos3-2.2.5.jar:?]
    at com.taosdata.jdbc.TSDBJNIConnector.executeQuery(TSDBJNIConnector.java:119) ~[k2box-storage-taos3-2.2.5.jar:?]
    at com.taosdata.jdbc.TSDBStatement.executeQuery(TSDBStatem

三、问题排查

由于此环境的TDengine是不久前从2.6.0升级到3.1.0.0的,升级之前没有这个问题,所以一开始怀疑是TDengine的新版本有问题,或者是新版本的默认参数与旧版本不同导致的。但尝试修改了服务端和客户端(连接池)的各种参数后问题依旧。用ulimit -nlsof -p查询打开的文件数量和文件上限也在正常范围内。

后来考虑是否作业(docker容器)消耗资源有异常,查看资源占用图表时发现折线图有向反方向走的情况(下图红色箭头指向的位置),说明服务器系统时间有倒流的情况。

file

仔细查看前面的失败作业日志,其实也有时间戳倒流的现象,并且连接失败现象与时间戳倒流是同时发生的:

file

因此将问题嫌疑定位到与系统时钟有关。经过进一步排查发现,此服务器使用ntpdate定期从两台ntp服务器分别进行周期性的对时:

1、每5分钟从服务器10.102.9.31同步时间:

[root@taos-1 ~]# crontab -l
*/5 * * * * /usr/sbin/ntpdate 10.102.9.31 >/dev/null 2>&1

2、每64秒从服务器10.102.9.32同步时间:

[root@taos-1 ~]# ntpstat
synchronised to NTP server (10.102.9.32) at stratum 3
   time correct to within 115 ms
   polling server every 64 s
[root@taos-1 ~]#

并且服务器10.102.9.32的时钟比10.102.9.31慢7秒左右,这就造成了这台服务器的时钟周期性的向前和向后跳动。当TDengine服务端的时钟改变时,连接池里原有的连接会失效无法连接。

[root@taos-1 ~]# /usr/sbin/ntpdate 10.102.9.32
 7 Aug 18:51:23 ntpdate[49651]: step time server 10.102.9.32 offset 7.341666 sec

[root@taos-1 ~]# /usr/sbin/ntpdate 10.102.9.31
 7 Aug 18:54:31 ntpdate[1798]: step time server 10.102.9.31 offset -7.200571 sec

四、问题解决

从两台服务器分别对时显然是没有必要的,应该是运维人员不了解情况的情况下操作导致的,去掉其中一个对时的任务后即可。这里我们去掉的是ntpstat里配置的对时服务,我们发现它是用chrony服务实现的,因此执行systemctl stop chronydsystemctl disable chronyd

使用SysBench测试PostgreSQL并发访问性能

本文使用SysBench测试PostgreSQL服务在不同并发线程数下的性能表现。

一、环境信息

被测服务器(192.168.132.167):

  • 4核8线程,32GB,1TB 7200转机械硬盘
  • CentOS 7.9
  • PostgreSQL 9.6.2

测试客户端(192.168.130.152):

  • 4核8线程,32GB,2TB 7200转机械硬盘
  • CentOS 7.9
  • SysBench 1.0.17

网络:

  • 千兆以太局域网

二、测试综合性能

为了测试不同参数下数据库的性能表现,我们通常会将测试过程写成shell脚本循环执行。由于性能测试的时间可能很长,我们用tee命令将每次测试的结果保存到文件以免丢失数据。

我们用下面的脚本测试在不同线程数下的综合性能(oltp_read_write)。

#!/bin/sh
pg_host=192.168.132.167
pg_port=5432
pg_user=sbtest
pg_password=password
pg_db=sbtest

for ((threads=5; threads<=160; threads=threads+5));do
    sysbench oltp_read_write \
    --pgsql-host=$pg_host --pgsql-port=$pg_port \
    --pgsql-user=$pg_user --pgsql-password=$pg_password \
    --pgsql-db=$pg_db \
    --table_size=100000 --tables=12 \
    prepare

    sysbench oltp_read_write \
    --pgsql-host=$pg_host --pgsql-port=$pg_port \
    --pgsql-user=$pg_user --pgsql-password=$pg_password \
    --pgsql-db=$pg_db \
    --report-interval=5 \
    --table_size=100000 --tables=12 \
    --threads=$threads \
    --time=30 \
    run | tee -a result.txt 2>&1

    sysbench oltp_read_write \
    --pgsql-host=$pg_host --pgsql-port=$pg_port \
    --pgsql-user=$pg_user --pgsql-password=$pg_password \
    --pgsql-db=$pg_db \
    --tables=12 \
    cleanup
done

上面测试脚本的结果保存在result.txt文件里,使用下面的命令可以获取到所需的数据项,方便粘贴到excel表格里画图:

获取平均QPS值(考虑到超过100000的情况因此用两个cut实现):
cat result.txt |grep queries:|cut -c 40-57|cut -d '(' -f 2
获取平均Latency值:
cat result_read.txt |grep avg:|cut -c 48-65

为减小误差,我们把测试脚本执行了2次,将每次得到的各线程数下的QPS数值画成一条曲线并叠加,得到下面的统计图,其中横轴是并发线程数,纵轴是QPS均值:

file

测试结果里各个线程数下TPS数值与QPS都十分接近20:1的关系,所以这里不单独画图展示TPS了。

从图中可以看出,两次测试结果比较接近,随着线程数增加QPS指标也同步增加,在120线程左右达到峰值。

两次测试的平均请求延迟(latency,毫秒)统计如下图所示,其中横轴仍然是并发线程数,纵轴是延迟时间:

file

延迟基本与线程数正相关,并且在140线程以上斜率有所增加。

三、测试只读性能

我们使用sysbench oltp_read_only命令测试纯读取场景下,不同线程数的QPS表现:

#!/bin/sh
pg_host=192.168.132.167
pg_port=5432
pg_user=sbtest
pg_password=password
pg_db=sbtest

sysbench oltp_read_only \
--db-driver=pgsql \
--pgsql-host=$pg_host --pgsql-port=$pg_port \
--pgsql-user=$pg_user --pgsql-password=$pg_password \
--pgsql-db=$pg_db \
--table_size=100000 \
--tables=12 \
prepare

for ((threads=5; threads<=120; threads=threads+5));do
    sysbench oltp_read_only \
    --db-driver=pgsql \
    --report-interval=5 \
    --table-size=100000 \
    --tables=12 \
    --threads=$threads \
    --time=30 \
    --pgsql-host=$pg_host --pgsql-port=$pg_port \
    --pgsql-user=$pg_user --pgsql-password=$pg_password \
    --pgsql-db=$pg_db \
    run | tee -a result.txt 2>&1
done

sysbench oltp_read_only \
--db-driver=pgsql \
--pgsql-host=$pg_host --pgsql-port=$pg_port \
--pgsql-user=$pg_user --pgsql-password=$pg_password \
--pgsql-db=$pg_db \
--tables=12 \
cleanup

为减小误差,我们把测试脚本执行了2次,将每次得到的各线程数下的QPS数值画成一条曲线并叠加,得到下面的统计图,其中横轴是并发线程数,纵轴是QPS均值:

file

测试结果里各个线程数下,TPS数值与QPS都十分接近16:1的关系,所以这里不单独画图展示TPS了。

从图中可以看出,两次测试结果比较接近,都是在50个并发线程时达到QPS峰值(大约35000请求/秒)。

两次测试的平均请求延迟(latency,毫秒)统计如下图所示,其中横轴仍然是并发线程数,纵轴是延迟时间:

file

从图中可以看出,两次测试结果比较接近,随着线程数量的增加,平均延迟是单调上升的,即使在并发50个线程QPS已经达到峰值后,平均延迟也是上涨的。因此从这两项测试结果来看,在50线程以上继续增加线程数量是没有意义的。

使用Docker部署PostgreSql高可用集群(RepMgr方案)

本文记录了某项目中使用docker部署PostgreSQL集群的步骤和注意事项,使用的镜像是bitnami/postgresql-repmgr,其中与用户身份有关的内容在其他bitnami打包的镜像中也适用。

一、环境信息

CentOS 7.9
PostgreSQL 16

二、部署方案

PostgreSQL集群有多种方案,这里我们使用在项目中多次使用比较稳定的repmgr方案,repmgr能够在集群环境管理每个PostgreSQL节点的主从状态,官网介绍如下:

repmgr是一个开源的工具套件,用于管理PostgreSQL服务器集群中的复制和故障转移。它通过提供设置备用服务器、监控复制和执行故障转移或手动切换操作等管理任务的工具,增强了PostgreSQL内置的热备功能。
repmgr自从PostgreSQL 9.0引入内置的复制机制后,就提供了高级的支持。当前的repmgr系列,repmgr 5,支持了PostgreSQL 9.3引入的最新的复制功能,如级联复制、时间线切换和通过复制协议进行基础备份。

bitnami提供的postgresql-repmgr镜像是将PostgreSQL与repmgr打包在一起,形成一个集成的解决方案,以便用户能够快速搭建PostgreSQL集群服务,用户可以从dockerhub获取到这个镜像。

这个PostgreSQL集群解决方案包括PostgreSQL复制管理器,一个用于管理PostgreSQL集群上的复制和故障转移的开源工具。

三、部署步骤

首先确认集群环境各个服务器节点的状态:

关闭防火墙(如果docker已启动需要重启docker服务,否则关闭防火墙后启动容器会报iptables命令错):

systemctl stop firewalld 
systemctl disable firewalld

确保服务器时钟准确:

ntpdate cn.ntp.org.cn

安装和启动docker服务和docker-compose工具:

yum install -y epel-release
yum install -y docker
yum install -y docker-compose
systemctl enable docker
systemctl start docker

安装postgresql-repmgr镜像:

docker pull bitnami/postgresql-repmgr

将各个服务器名称写入hosts文件,vi /etc/hosts添加下面内容:

10.102.9.80 pg-0
10.102.9.81 pg-1
10.102.9.82 pg-2

四、配置

数据目录

bitnami的镜像使用非root用户身份,即容器里的root用户映射到宿主机的非root用户,此用户是ID为1001的无名称用户。要让容器的数据能够持久化到宿主机,需要准备一个数据目录(此例中为/mnt/sda/bitnami/postgresql)并映射到容器内,此目录的owner是1001:

mkdir /mnt/sda/bitnami
mkdir /mnt/sda/bitnami/postgresql
chown 1001:root /mnt/sda/bitnami -R

网络

创建docker网络以便节点间能够通信:

docker network create --subnet=172.25.0.0/24 --gateway=172.25.0.1 pg-network

配置文件

在任意目录创建pg.yml文件,内容如下:

version: '2'
networks:
  default:
    external:
      name: pg-network
services:
  pg:
    container_name: "pg"
    image: bitnami/postgresql-repmgr:latest
    networks:
      default:
        ipv4_address: 172.25.0.110
    ports:
      - "5432:5432"
    restart: always
    volumes:
      - /mnt/sda/bitnami/postgresql:/bitnami/postgresql
      - /etc/hosts:/etc/hosts
    environment:
      - POSTGRESQL_POSTGRES_PASSWORD=adminpassword
      - POSTGRESQL_USERNAME=myuser
      - POSTGRESQL_PASSWORD=mypassword
      - POSTGRESQL_DATABASE=mydatabase
      - REPMGR_PASSWORD=adminpassword
      - REPMGR_PRIMARY_HOST=pg-0
      - REPMGR_PRIMARY_PORT=5432
      - REPMGR_PARTNER_NODES=pg-0,pg-1,pg-2:5432
      - REPMGR_NODE_NAME=pg-0
      - REPMGR_NODE_NETWORK_NAME=pg-0
      - REPMGR_PORT_NUMBER=5432

在第二个节点类似创建pg.yml文件,修改其中的部分内容(ip地址、以及2处节点名称,见代码中的标注)如下所示:

version: '2'
networks:
  default:
    external:
      name: pg-network
services:
  pg:
    container_name: "pg"
    image: bitnami/postgresql-repmgr:latest
    networks:
      default:
        ipv4_address: 172.25.0.111    <-- 修改了这里
    ports:
      - "5432:5432"
    restart: always
    volumes:
      - /mnt/sda/bitnami/postgresql:/bitnami/postgresql
      - /etc/hosts:/etc/hosts
    environment:
      - POSTGRESQL_POSTGRES_PASSWORD=adminpassword
      - POSTGRESQL_USERNAME=myuser
      - POSTGRESQL_PASSWORD=mypassword
      - POSTGRESQL_DATABASE=mydatabase
      - REPMGR_PASSWORD=adminpassword
      - REPMGR_PRIMARY_HOST=pg-0
      - REPMGR_PRIMARY_PORT=5432
      - REPMGR_PARTNER_NODES=pg-0,pg-1,pg-2:5432
      - REPMGR_NODE_NAME=pg-1    <-- 修改了这里
      - REPMGR_NODE_NETWORK_NAME=pg-1    <-- 修改了这里
      - REPMGR_PORT_NUMBER=5432

第三个节点的情况类似,为节约篇幅这里不再贴配置文件内容。

启动服务

在每个节点分别使用docker-compose命令启动服务:

docker-compose -f /root/pg-ha/pg.yml up -d

查看repmgr状态,例如当前primary节点是哪一个:

docker exec -ti pg /opt/bitnami/scripts/postgresql-repmgr/entrypoint.sh repmgr -f /opt/bitnami/repmgr/conf/repmgr.conf service status

 ID | Name | Role    | Status    | Upstream | repmgrd | PID | Paused? | Upstream last seen
----+------+---------+-----------+----------+---------+-----+---------+--------------------
 1000 | pg-0 | standby |   running | pg-1     | running | 1   | no      | 1 second(s) ago
 1001 | pg-1 | primary | * running |          | running | 1   | no      | n/a
 1002 | pg-2 | standby |   running | pg-1     | running | 1   | no      | 0 second(s) ago

尝试连接数据库,验证服务是否正常(-U参数很重要):

docker exec -ti pg psql -U myuser -d mydatabase

若需要手工切换standby节点为primary执行下面的命令,需要节点之间配置过免密:

docker exec -it pg /opt/bitnami/scripts/postgresql-repmgr/entrypoint.sh repmgr -f /opt/bitnami/repmgr/conf/repmgr.conf standby switchover

使用SysBench进行数据库性能测试

SysBench是一个基于LuaJIT的可脚本化多线程基准测试工具。它最常用于数据库基准测试,但也可用于创建不涉及数据库服务器的任意复杂工作负载。本文以一个典型测试为例,介绍SysBench的安装和使用。

一、环境信息

被测服务器(192.168.132.167):

  • 4核8线程,32GB,1TB 7200转机械硬盘
  • CentOS 7.9
  • PostgreSQL 9.6.2

测试客户端(192.168.130.152):

  • 4核8线程,32GB,2TB 7200转机械硬盘
  • CentOS 7.9
  • SysBench 1.0.17

网络:

  • 千兆以太局域网

二、准备工作

安装SysBench

不同发行版的Linux按照官网上的说明安装即可:

Debian/Ubuntu

curl -s https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh | sudo bash
sudo apt -y install sysbench

RHEL/CentOS:

curl -s https://packagecloud.io/install/repositories/akopytov/sysbench/script.rpm.sh | sudo bash
sudo yum -y install sysbench

Fedora:

curl -s https://packagecloud.io/install/repositories/akopytov/sysbench/script.rpm.sh | sudo bash
sudo dnf -y install sysbench

验证安装成功:

sysbench --version
sysbench 1.0.17

创建测试用DB

在被测服务器上,创建一个空的专门用于性能测试的库(sbtest),以及相应的用户:

psql -h 192.168.132.167 -U postgres -W
> CREATE USER sbtest WITH PASSWORD 'password';
> CREATE DATABASE sbtest;
> GRANT ALL PRIVILEGES ON DATABASE sbtest TO sbtest;

验证从客户端能够访问到此数据库:

psql -h 192.168.132.167 -U sbtest -W -d sbtest

三、开始测试

SysBench基本用法

SysBench自带了多种数据库性能测试的场景,这些测试场景的名称(testname)列表可以在/usr/share/sysbench/目录下找到,包括bulk_insert, oltp_delete, oltp_insert, oltp_point_select, oltp_read_only, oltp_read_write, oltp_update_index, oltp_update_non_index, oltp_write_only, select_random_pointsselect_random_ranges,从名字可以大致猜测出所代表的场景,例如oltp_read_write代表综合读写的场景,oltp_write_only代表只读的场景。

SysBench执行命令的统一格式是:

sysbench [options]... [testname] [command]

其中testname就是上面列出的测试名称,每个不同的testname有自己的command,但大多数支持的command都是prepareruncleanup。当然我们一般还需要填写options来指定如数据库地址、数据库密码等信息。

下面以oltp_read_write场景为例,介绍最经常使用的几个命令。

准备测试数据(prepare)

prepare的作用是向目标数据库里插入一些随机数据,作为后面真正的测试的数据环境。下面的命令在目标数据库里创建12张表,每张表里添加10万行随机数据:

sysbench oltp_read_write \
    --db-driver=pgsql --pgsql-host=192.168.132.167 --pgsql-port=5432 \
    --pgsql-user=sbtest --pgsql-password=password --pgsql-db=sbtest \
    --table_size=100000 --tables=12 \
    prepare

执行测试(run)

下面的命令执行实际的测试:

sysbench oltp_read_write \
    --db-driver=pgsql --pgsql-host=192.168.132.167 --pgsql-port=5432 \
    --pgsql-user=sbtest --pgsql-password=password --pgsql-db=sbtest \
    --report-interval=5 \
    --table_size=100000 --tables=12 \
    --threads=32 \
    --time=30 --warmup-time=10 \
    run

其中time参数规定了测试执行的时长(30秒),warmup-time参数规定了测试前预热阶段的时长(10秒),threads参数规定了客户端并发请求的线程数量(32线程)。

测试启动后,屏幕上会按指定时间间隔输出当前性能指标:

[ 5s ] thds: 32 tps: 217.41 qps: 4476.39 (r/w/o: 3138.93/895.24/442.22) lat (ms,95%): 467.30 err/s: 0.40 reconn/s: 0.00
[ 10s ] thds: 32 tps: 221.21 qps: 4380.99 (r/w/o: 3076.74/861.64/442.62) lat (ms,95%): 530.08 err/s: 0.00 reconn/s: 0.00
[ 15s ] thds: 32 tps: 301.40 qps: 6071.88 (r/w/o: 4239.86/1229.02/603.01) lat (ms,95%): 297.92 err/s: 0.00 reconn/s: 0.00
[ 20s ] thds: 32 tps: 262.78 qps: 5180.49 (r/w/o: 3626.58/1027.94/525.97) lat (ms,95%): 344.08 err/s: 0.20 reconn/s: 0.00
[ 25s ] thds: 32 tps: 186.81 qps: 3813.27 (r/w/o: 2673.39/765.05/374.83) lat (ms,95%): 590.56 err/s: 0.40 reconn/s: 0.00
[ 30s ] thds: 32 tps: 318.79 qps: 6382.66 (r/w/o: 4466.10/1278.17/638.39) lat (ms,95%): 227.40 err/s: 0.20 reconn/s: 0.00

测试结束后,会输出汇总指标报告:

SQL statistics:
    queries performed:
        read:                            106134
        write:                           30305
        other:                           15169
        total:                           151608
    transactions:                        7575   (252.30 per sec.)
    queries:                             151608 (5049.62 per sec.)
    ignored errors:                      6      (0.20 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          30.0216s
    total number of events:              7575

Latency (ms):
         min:                                    9.55
         avg:                                  126.79
         max:                                 1051.62
         95th percentile:                      369.77
         sum:                               960450.42

Threads fairness:
    events (avg/stddev):           236.7188/3.16
    execution time (avg/stddev):   30.0141/0.00

清除测试数据

要清除前面执行测试产生的数据,执行下面的命令:

sysbench oltp_read_write \
    --db-driver=pgsql --pgsql-host=192.168.132.167 --pgsql-port=5432 \
    --pgsql-user=sbtest --pgsql-password=password --pgsql-db=sbtest \
    --tables=12 \

注意tables参数需要手工指定并且与prepare时一致,否则下次prepare可能会报错table已存在。

四、参考资料

TDengine与Cassandra集群版性能对比测试

本文记录集群环境下TDengine与Cassandra数据库处理时序数据的性能对比,主要考察数据的写入和读取速度(吞吐率)指标。因为InfluxDB社区版不支持集群,所以没有放在一起进行对比。

一、测试环境

服务器

使用三台物理服务器组成数据库集群,配置如下:

CPU 内存 硬盘 网络
Intel i7 4核8线程 32G 2T 7200转机械 千兆网络
操作系统 TDengine Cassandra
CentOS 7.9 2.6 4.0.6

客户端

CPU 内存 硬盘 网络
Intel i7 4核8线程 32G 256G SSD固态 千兆网络
操作系统 JDK JMeter
Windows10 OpenJDK 11 v4

二、测试结果

数据写入性能

测试数据为10个csv文件,每个文件大小约为10GB,包含下面的内容:

  • 每个文件包含50个设备10天的数据
  • 数据频率为1秒
  • 每行数据包含500列
  • 所有数据均为double类型
  • 每个数据文件包含43200050行
  • 每个数据文件大小为167GB

以不同并发数向数据库写入10个csv文件,多次执行取结果平均值得到的结果见下图,其中横轴是并发线程数,纵轴是每秒写入的数据点数(行x列):

数据库配置为单副本的情况:

file

数据库配置为三副本的情况:

file

三副本时,服务器CPU资源占用率情况,纵轴为CPU占用百分比:

file

总体来看,在相同的条件下TDengine的数据写入速度高于Cassandra数据库,副本数量增加对写入速度没有太大影响,并且TDengine服务器CPU资源占用低于Cassandra。

数据读取性能

在两个数据库中分别建表testdb,并预先存入下面的数据:

  • 50个设备10天的数据
  • 数据频率为1秒
  • 每行数据包含500列
  • 所有数据均为double类型

数据读取测试在三副本配置下进行,测试结果如下图,其中横轴是读取的列数,纵轴是每秒读取到的点数(行x列):

file

从图中可以看到,随着抽取的列数增加两个数据库的数据抽取速度都有增加,TDengine在300列达到峰值,并且TDengine的的读取速度基本保持在Cassandra的4到5倍。

三、测试结论

以上测试结果显示,5并发下TDengine的数据写入性能与Cassandra接近,10并发以上则明显优于Cassandra;在数据读取方面TDengine的性能数倍于Cassandra具有压倒优势。

TDengine、InfluxDB与PostgreSQL单机版性能对比测试

本文记录单机环境下TDengine、InfluxDB和PostgreSQL三种数据库,处理时序数据的性能对比,主要考察数据的写入和读取速度。

一、测试环境

服务器

CPU 内存 硬盘 网络
Intel i7 4核8线程 32G 2T 7200转机械 千兆网络
操作系统 TDengine Influxdb PostgreSQL
CentOS 7.9 2.6 2.0 beta 9.0

客户端

CPU 内存 硬盘 网络
Intel i7 4核8线程 32G 256G SSD固态 千兆网络
操作系统 JDK JMeter
Windows10 OpenJDK 11 v4

二、测试数据

测试数据为10个csv文件,每个文件大小约为10GB,包含下面的内容:

  • 每个文件包含100个设备的数据
  • 数据频率为1秒
  • 每行数据包含1000列
  • 所有数据均为double类型

三、测试结果

数据写入

同时向数据库写入10个csv文件,多次执行取结果平均值:

数据库 数据行导入速率(record/s) 数据点导入速率(points/s)
InfluxDB 235 235000
TDengine 750 750208
PostgreSQL 213 213000

file

数据读取

数据读取性能与读取的列数相关,因此分别测试了不同列数情况下三种数据库的读取速度(单并发):

file

四、测试结论

以上初步测试结果显示,在单并发大多数场景下,TDengine的性能相比InfluxDB和PostgreSQL具有较大优势。由于项目时间所限,此次未能对多并发情况下各个数据库的性能进行对比略显遗憾。作为补充,可参考集群环境多并发条件下TDengine与Cassandra的性能对比(链接)。

TDengine 2.6集群版升级到3.0集群版

本文记录了在由三个物理节点组成的集群上,原地将TDengine 2.6版升级到3.0版的过程,由于3.0的改动比较大,所以此次升级并非完全平滑的升级,只升级了数据库服务,没有将数据库里的存量数据导入到新版环境,如需保留数据请联系taos团队。本文使用的TDengine版本均为社区版,非docker安装。

一、卸载2.6版

备份配置文件

> ansible allnode -m shell -a "mv /etc/taos/ /etc/taos.bak"

按taos文档说明卸载程序后配置文件是会保留的,但3.0版与2.6版的配置文件(配置项)有差异,直接让3.0使用2.6版配置文件会导致服务启动失败。因此决定自行保留并在安装后再3.0版的配置文件:

当缺省配置文件( /etc/taos/taos.cfg )存在时,仍然使用已有的配置文件,安装包中携带的配置文件修改为taos.cfg.org保存在 /usr/local/taos/cfg/ 目录

备份数据文件

数据文件默认位置在/usr/local/taos/data

> ansible allnode -m shell -a "mv /data/tdengine /data/tdengine.bak"

如果升级3.0失败需要恢复为2.6版,则备份的数据文件会起作用,否则可以删除。数据文件的位置以taos.cfg文件内的配置为准。

停止taosd服务并卸载程序

> ansible allnode -m shell -a "systemctl stop taosd"
> ansible allnode -m shell -a "rpm -e tdengine"

192.168.130.153 | CHANGED | rc=0 >>
taosadapter is running, stopping it...
TDengine is removed successfully!
192.168.130.154 | CHANGED | rc=0 >>
taosadapter is running, stopping it...
TDengine is removed successfully!
192.168.130.152 | CHANGED | rc=0 >>
taosadapter is running, stopping it...
TDengine is removed successfully!

其中ansible allnode -m shell -a命令是为了方便在集群所有节点上一起执行,如果没有装ansible工具可在每个节点上手工执行,效果是一样的。

二、安装TDengine3.0

下载单机版安装包

TDengine提供了多种格式的安装包,我们在CentOS上选择使用RPM版的。

> wget https://www.taosdata.com/assets-download/3.0/TDengine-server-3.0.1.5-Linux-x64.rpm

由于安装过程中需要输入一些信息,所以建议在每个节点上手工执行(而非通过ansible):

> rpm -ivh TDengine-server-3.0.1.5-Linux-x64.rpm

安装过程会提示输入两个信息,按taos的安装说明输入即可(我两个都选择了回车跳过):

当安装第一个节点时,出现 Enter FQDN: 提示的时候,不需要输入任何内容。只有当安装第二个或以后更多的节点时,才需要输入已有集群中任何一个可用节点的 FQDN,支持该新节点加入集群。当然也可以不输入,而是在新节点启动前,配置到新节点的配置文件中。

三、修改taos配置文件

根据之前备份的2.6版的taos.cfg文件修改3.0版的配置文件,我这里主要改的是firstEpfqdnlogDirdataDirtimezonecharset等几个:

# first fully qualified domain name (FQDN) for TDengine system
firstEp                   taos-1:6030
# local fully qualified domain name (FQDN)
fqdn                      taos-1
# The directory for writing log files
 logDir                    /data/tdengine/log
# All data files are stored in this directory
 dataDir                  /data/tdengine/data
# system time zone
 timezone              Asia/Shanghai (CST, +0800)
# system locale
 locale                    en_US.UTF-8
# default system charset
 charset                   UTF-8

为节约时间,可以先改好第一个节点的,再复制到其他节点,然后只要去每个节点修改fqdn的值即可。

> scp /etc/taos/taos.cfg root@taos-2:/etc/taos/taos.cfg
> scp /etc/taos/taos.cfg root@taos-3:/etc/taos/taos.cfg

四、启动taosd并查看状态

启动第一个节点

在第一个节点(firstEp)上启动taosd服务,并验证服务启动成功:

> systemctl start taosd
> systemctl status taosd

此处如果遇到启动失败可进一步查看日志文件来找原因。遇到过一个DND ERROR failed to start since read config error的错误比较坑,原因提示不明确。后来发现是hostname与taos.cfg不匹配造成的。

在taos命令行里验证此时有一个dnode:

> taos
taos> show dnodes;
     id      |            endpoint            | vnodes | support_vnodes |   status   |       create_time       |              note              |
=================================================================================================================================================
           1 | taos-1:6030                    |      0 |             16 | ready      | 2022-10-26 14:50:57.131 |                                |
Query OK, 1 rows in database (0.003960s)

添加其他节点

与第一个节点类似,只不过启动后要在taos命令行里执行create dnode命令将当前节点添加到集群。

> systemctl start taosd
> systemctl status taosd
> taos

taos> create dnode "taos-2";
Query OK, 0 of 0 rows affected (0.002127s)

taos> show dnodes;
     id      |            endpoint            | vnodes | support_vnodes |   status   |       create_time       |              note              |
=================================================================================================================================================
           1 | taos-1:6030                    |      0 |             16 | ready      | 2022-10-26 14:50:57.131 |                                |
           2 | taos-2:6030                    |      0 |             16 | ready      | 2022-10-26 14:54:48.635 |                                |
Query OK, 2 rows in database (0.003566s)

五、创建数据库用户(可选)

创建必要的数据库用户:

> taos -s "CREATE USER k2data PASS 'xxxxxxxx';"

创建必要的database:

注意在2.6里的DAYS改为DURATION了,且3.0不支持UPDATE参数了(相当于UPDATE=1不可修改):

taos -s 'CREATE DATABASE repos REPLICA 3 KEEP 3650 DURATION 10;'

至此TDengine版本升级完成。

参考资料

TDengine多种安装包的安装和卸载

使用安装包立即开始

集群部署和管理

HBase单机版和集群版安装配置

HBase的官方文档十分详细,按照quickstart介绍的步骤安装就可以完成,配置集群版时需要稍微留意一下与hadoop相关的几个配置项。本文记录了自己在某项目集成环境安装HBase集群版的过程。

一、环境信息

> uname -a
Linux node1 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

> java --version
openjdk 11.0.15 2022-04-19

> hbase version
HBase 2.5.1

二、单机版

HBase单机版的安装还是十分简单的。

下载HBase

国内建议用清华镜像比较快:

> wget https://mirrors.tuna.tsinghua.edu.cn/apache/hbase/2.5.1/hbase-2.5.1-bin.tar.gz --no-check-certificate

注:下载了几次2.4.15版解压缩时都报unexpected end of file错误,改为下载2.5.1版。

解压缩到指定目录

这里以安装到/usr/local为例:

> tar zxvf hbase-2.5.1-bin.tar.gz -C /usr/local/

启动HBase

启动HBase服务,成功后可以看到HMaster进程和HRegionServer进程,浏览器访问 http://localhost:16010 可以进入HBase Web界面,bin/hbase shell可进入命令行。

> bin/start-hbase.sh
running master, logging to /usr/local/hbase-2.5.1/bin/../logs/hbase-root-master-node1.out
: running regionserver, logging to /usr/local/hbase-2.5.1/bin/../logs/hbase-root-regionserver-node1.out

> jps
55825 HMaster
56062 HRegionServer

> bin/hbase shell
hbase:001:0>

如果提示Could not start ZK at requested port of 2181,修改hbase-site.xml设置hbase.cluster.distributed=true,这样其实HBase的运行模式是伪分布式(而非单机模式),即HMaster与HRegionServer运行在同一物理机的不同jvm进程。

停止HBase

> bin/stop-hbase.sh

停止后用jps命令确认看HMaster和HRegionServer进程是否已结束。

三、集群版

HBase单机版一般无法支撑生产环境的需要,这里介绍如何在单机版基础上配置集群版,这里以三台服务器为例,其中节点1和节点2作为HMaster(主备),节点1、节点2和节点3同时作为RegionServer。

配置ssh免密

主节点需要能够免密登录到其他节点,备份主节点也需要能够免密登录到其他节点。

> ssh-keygen
> ssh-copy-id root@node2
> ssh-copy-id root@node3

修改hbase-site.xml

HBase默认的hbase-site.xml内容是单机版配置,只适合测试和学习使用,现在需要改为集群版的配置。一是修改运行模式为集群模式;二是将HBase的数据目录由本地目录改为HDFS目录,其中host和port应参考core-site.xml里配置的fs.defaultFS的值:

> vi hbase-site.xml
<property>
  <name>hbase.cluster.distributed</name>
  <value>true</value>
</property>
<property>
  <name>hbase.rootdir</name>
  <value>hdfs://namenode1:8020/hbase</value>
</property>

同时删除单机版需要的hbase.tmp.dirhbase.unsafe.stream.capability.enforce这两个配置项。

此时重新启动HBase后,会自动在hdfs上创建hbase目录:

[root@node1 hbase-2.5.1]# hdfs dfs -ls /hbase
Found 12 items
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/.hbck
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/.tmp
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/MasterData
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/WALs
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/archive
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/corrupt
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/data
-rw-r--r--   3 hdfs supergroup         42 2022-01-05 13:34 /hbase/hbase.id
-rw-r--r--   3 hdfs supergroup          7 2022-01-05 13:34 /hbase/hbase.version
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/mobdir
drwxr-xr-x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/oldWALs
drwx--x--x   - hdfs supergroup          0 2022-01-05 13:34 /hbase/staging

修改regionservers

修改regionservers文件,每行填写一台服务器的名称:

> vi conf/regionservers
node1
node2
node3

创建备份HMaster

生产环境一般需要启动在两个节点上启动HMaster以提供高可用能力。在conf目录下创建backup-masters文件,里面填写备份HMaster服务器的名称:

> vi conf/backup-masters
node2

配置zookeeper

在hbase-site.xml里配置zookeeper:

> vi hbase-site.xml
<property>
  <name>hbase.zookeeper.quorum</name>
  <value>node1,node2,node3</value>
</property>
<property>
  <name>hbase.zookeeper.property.dataDir</name>
  <value>/usr/local/zookeeper</value>
</property>

配置从节点

将整个目录复制到其他从节点,我习惯用rsync比较快,如果没有装rsync也可以用scp命令:

> rsync -avr /usr/local/hbase-2.5.1 root@node2:/usr/local
> rsync -avr /usr/local/hbase-2.5.1 root@node3:/usr/local

启动HBase集群

在主节点上启动集群:

> bin/start-hbase.sh

启动后在各节点上执行jps检查HMaster和HRegionServer进程是否存在,如果启动失败查看日志以便定位原因。

四、常见问题

HMaster启动了,但所有RegionServer都没启动

检查日志文件发现HMaster在等待RegionServer,而RegionServer日志里则没有任何信息。

> tail logs/hbase-root-master-node1.out
2022-01-05 17:36:36,007 INFO  [master/zookeeper-2:16000:becomeActiveMaster] master.ServerManager (ServerManager.java:waitForRegionServers(805)) - Waiting on regionserver count=0; waited=862160ms, expecting min=1 server(s), max=NO_LIMIT server(s), timeout=4500ms, lastChange=862160ms
2022-01-05 17:36:37,512 INFO  [master/zookeeper-2:16000:becomeActiveMaster] master.ServerManager (ServerManager.java:waitForRegionServers(805)) - Waiting on regionserver count=0; waited=863665ms, expecting min=1 server(s), max=NO_LIMIT server(s), timeout=4500ms, lastChange=863665ms

向前翻日志发现有警告JAVA_HOME没有配置(但在命令行里echo $JAVA_HOME其实有值),在conf/hbase-env.sh里再指定一次并重启HBase问题解决。

> vi conf/hbase-env.sh
# The java implementation to use.  Java 1.8+ required.
export JAVA_HOME=/home/k2data/jdk-11

RegionServer报错ClockOutOfSyncException

如果主节点与从节点的时钟不同步,RegionServer会报错退出,可以手动更新时间错误的节点。生产环境建议ntpdate或chrony保持时钟同步。

停止HBase服务时提示"no hbase master found"

原因是记录hbase各个服务的pid文件默认放在/tmp目录下,由于某些原因文件丢失了。可以手工在每个节点jps查看hbase服务(H开头的)的进程id,然后kill掉。

SLF4J提示找到多个实现

启动hbase或在hbase shell里执行命令时总是出现如下的slf4j警告提示:

[root@taos-1 target]# start-hbase.sh
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/hdfs/hadoop-3.2.3/share/hadoop/common/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/usr/local/hbase-2.5.1/lib/client-facing-thirdparty/log4j-slf4j-impl-2.17.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
SLF4J: Class path contains multiple SLF4J bindings.

一般是与hadoop自带的log4j2版本不一致导致的,按提示中的路径删除旧版的log4j2包即可

五、参考链接

https://hbase.apache.org/book.html#quickstart

CentOS7.0安装mysql5.7

CentOS7.0里的yum默认不带mysql最新版本(其默认数据库是mariaDB,可替代mysql),所以如果要安装mysql需要参考A Quick Guide to Using the MySQL Yum Repository这个文档。

首先安装rpm源:

wget https://repo.mysql.com/mysql80-community-release-el7-3.noarch.rpm
sudo rpm -Uvh mysql80-community-release-el7-3.noarch.rpm

然后要选择安装哪个版本,默认是最新版本(8.0),我们希望安装5.7:

yum install yum-utils (如果没有安装过)
sudo yum-config-manager --disable mysql80-community
sudo yum-config-manager --enable mysql57-community

检验版本配置成功:

yum repolist enabled | grep mysql

禁用默认的mysql模块(EL8 systems only):

sudo yum module disable mysql

然后执行安装命令:

sudo yum install mysql-community-server –-nogpgcheck

启动mysqld服务:

sudo service mysqld start

mysql安装后会生成一个随机的root用户密码,用下面的命令获取到:

sudo grep 'temporary password' /var/log/mysqld.log

修改root用户密码:

ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass4!';

mysql数据库ibdata1文件过大问题处理

问题

一个mysql数据库,由于数据不断积累,目前ibdata1文件已经达到100GB大小,接近磁盘存储空间上限。

使用mysqlshow命令查看各个表占用的空间大小,发现dap_app_usage表占用的空间最大。其中data_length是数据大小,index_length是索引大小,data_free是未被使用的空间(通常是删除数据后留下的,这些空间不会被文件系统回收,但未来新插入的数据可以使用)。

mysqlshow --status apptimer

由于dap_app_usage表里保存了2014~2018年的数据,其中2015年及以前的数据属于冷数据,因此可以备份这些数据并从数据库里删除。

备份数据

我们按月备份数据以方便未来使用这些数据,每个月预估压缩后的文件大小为100MB,写一个脚本循环调用mysqldump命令以节约手工输入命令的时间并避免出错:

#!/bin/sh
#Dump dap_app_usage table to files by month
for i in {201401..201412}
do
    echo [date] Backing up $i ...
    mysqldump -uroot apptimer dap_app_usage -t --where="date between '${i}01' and '${i}31'"|gzip>~/dap_app_usage_${i}_data.sql.gz
done

删除数据

一次删除大量(如一亿条)数据效率很低,编写一个脚本小批量多次删除:

#!/bin/sh
# Delete specified range data from dap_app_usage table
# Make sure you have backup of these data

st=20140101
ed=20141231

if [[ ${st} -lt 19700101 ]]; then
    echo 'Wrong start date'
    exit
fi

if [[ ${ed} -lt ${st} ]]; then
        echo 'End date must be greater than start date'
        exit
fi

if [[ $((${ed}-${st})) -gt 1130 ]]; then
    echo 'Date range too large (1 year max)'
    exit
fi

echo "Delete data from ${st} to ${ed}"
for i in {1..99999}
do
    echo [date] delete iteration ${i}
    mysql -uroot apptimer -e "select count(*) from dap_app_usage where date between '${st}' and '${ed}'"
    mysql -uroot apptimer -e "delete from dap_app_usage where date between '${st}' and '${ed}' limit 1000"
    sleep 1
done

后续

为避免以后再出现类似问题,应定期执行上述清理操作,保证在dap_app_usage表里只保存近期一段时间的数据,这样可以维持数据库文件大小在一个固定值。

参考资料:

How to shrink/purge ibdata1 file in MySQL