mirror of
https://github.com/alibaba/DataX.git
synced 2025-05-02 04:59:51 +08:00
Add OpenTSDB reader and TSDB writer
This commit is contained in:
parent
eb1ea0bc24
commit
4d70b5ab86
161
.gitignore
vendored
161
.gitignore
vendored
@ -1,8 +1,157 @@
|
|||||||
/target/
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
.classpath
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/logs/
|
.AppleDouble
|
||||||
.idea/
|
.LSOverride
|
||||||
|
Icon
|
||||||
|
._*
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.ctxt
|
||||||
|
.mtj.tmp/
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
hs_err_pid*
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
cmake-build-debug/
|
||||||
|
cmake-build-release/
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
*.iws
|
||||||
|
out/
|
||||||
|
.idea_modules/
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
.idea/replstate.xml
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
.idea/httpRequests
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
buildNumber.properties
|
||||||
|
.mvn/timing.properties
|
||||||
|
!/.mvn/wrapper/maven-wrapper.jar
|
||||||
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
out
|
||||||
|
gen### Python template
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
.scrapy
|
||||||
|
docs/_build/
|
||||||
|
target/
|
||||||
|
.ipynb_checkpoints
|
||||||
|
.python-version
|
||||||
|
celerybeat-schedule
|
||||||
|
*.sage.py
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
.ropeproject
|
||||||
|
/site
|
||||||
|
.mypy_cache/
|
||||||
|
.metadata
|
||||||
|
bin/
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
local.properties
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.recommenders
|
||||||
|
.externalToolBuilders/
|
||||||
|
*.launch
|
||||||
|
*.pydevproject
|
||||||
|
.cproject
|
||||||
|
.autotools
|
||||||
|
.factorypath
|
||||||
|
.buildpath
|
||||||
|
.target
|
||||||
|
.tern-project
|
||||||
|
.texlipse
|
||||||
|
.springBeans
|
||||||
|
.recommenders/
|
||||||
|
.cache-main
|
||||||
|
.scala_dependencies
|
||||||
|
.worksheet
|
||||||
|
209
opentsdbreader/doc/opentsdbreader.md
Normal file
209
opentsdbreader/doc/opentsdbreader.md
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
|
||||||
|
# OpenTSDBReader 插件文档
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
|
||||||
|
## 1 快速介绍
|
||||||
|
|
||||||
|
OpenTSDBReader 插件实现了从 OpenTSDB 读取数据。OpenTSDB 是主要由 Yahoo 维护的、可扩展的、分布式时序数据库,与阿里巴巴自研 TSDB 的关系与区别详见阿里云官网:《[相比 OpenTSDB 优势](https://help.aliyun.com/document_detail/113368.html)》
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 2 实现原理
|
||||||
|
|
||||||
|
在底层实现上,OpenTSDBReader 通过 HTTP 请求链接到 OpenTSDB 实例,利用 `/api/config` 接口获取到其底层存储 HBase 的连接信息,再利用 AsyncHBase 框架连接 HBase,通过 Scan 的方式将数据点扫描出来。整个同步的过程通过 metric 和时间段进行切分,即某个 metric 在某一个小时内的数据迁移,组合成一个迁移 Task。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 3 功能说明
|
||||||
|
|
||||||
|
### 3.1 配置样例
|
||||||
|
|
||||||
|
* 配置一个从 OpenTSDB 数据库同步抽取数据到本地的作业:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"job": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"reader": {
|
||||||
|
"name": "opentsdbreader",
|
||||||
|
"parameter": {
|
||||||
|
"endpoint": "http://localhost:4242",
|
||||||
|
"column": [
|
||||||
|
"m"
|
||||||
|
],
|
||||||
|
"beginDateTime": "2019-01-01 00:00:00",
|
||||||
|
"endDateTime": "2019-01-01 03:00:00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"writer": {
|
||||||
|
"name": "streamwriter",
|
||||||
|
"parameter": {
|
||||||
|
"encoding": "UTF-8",
|
||||||
|
"print": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setting": {
|
||||||
|
"speed": {
|
||||||
|
"channel": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.2 参数说明
|
||||||
|
|
||||||
|
* **name**
|
||||||
|
* 描述:本插件的名称
|
||||||
|
* 必选:是
|
||||||
|
* 默认值:opentsdbreader
|
||||||
|
|
||||||
|
* **parameter**
|
||||||
|
* **endpoint**
|
||||||
|
* 描述:OpenTSDB 的 HTTP 连接地址
|
||||||
|
* 必选:是
|
||||||
|
* 格式:http://IP:Port
|
||||||
|
* 默认值:无
|
||||||
|
|
||||||
|
* **column**
|
||||||
|
* 描述:数据迁移任务需要迁移的 Metric 列表
|
||||||
|
* 必选:是
|
||||||
|
* 默认值:无
|
||||||
|
|
||||||
|
* **beginDateTime**
|
||||||
|
* 描述:和 endDateTime 配合使用,用于指定哪个时间段内的数据点,需要被迁移
|
||||||
|
* 必选:是
|
||||||
|
* 格式:`yyyy-MM-dd HH:mm:ss`
|
||||||
|
* 默认值:无
|
||||||
|
* 注意:指定起止时间会自动忽略分钟和秒,转为整点时刻,例如 2019-4-18 的 [3:35, 4:55) 会被转为 [3:00, 4:00)
|
||||||
|
|
||||||
|
* **endDateTime**
|
||||||
|
* 描述:和 beginDateTime 配合使用,用于指定哪个时间段内的数据点,需要被迁移
|
||||||
|
* 必选:是
|
||||||
|
* 格式:`yyyy-MM-dd HH:mm:ss`
|
||||||
|
* 默认值:无
|
||||||
|
* 注意:指定起止时间会自动忽略分钟和秒,转为整点时刻,例如 2019-4-18 的 [3:35, 4:55) 会被转为 [3:00, 4:00)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.3 类型转换
|
||||||
|
|
||||||
|
| DataX 内部类型 | TSDB 数据类型 |
|
||||||
|
| -------------- | ------------------------------------------------------------ |
|
||||||
|
| String | TSDB 数据点序列化字符串,包括 timestamp、metric、tags 和 value |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 4 性能报告
|
||||||
|
|
||||||
|
### 4.1 环境准备
|
||||||
|
|
||||||
|
#### 4.1.1 数据特征
|
||||||
|
从 Metric、时间线、Value 和 采集周期 四个方面来描述:
|
||||||
|
|
||||||
|
##### metric
|
||||||
|
|
||||||
|
固定指定一个 metric 为 `m`。
|
||||||
|
|
||||||
|
##### tagkv
|
||||||
|
|
||||||
|
前四个 tagkv 全排列,形成 `10 * 20 * 100 * 100 = 2000000` 条时间线,最后 IP 对应 2000000 条时间线从 1 开始自增。
|
||||||
|
|
||||||
|
| **tag_k** | **tag_v** |
|
||||||
|
| --------- | ------------- |
|
||||||
|
| zone | z1~z10 |
|
||||||
|
| cluster | c1~c20 |
|
||||||
|
| group | g1~100 |
|
||||||
|
| app | a1~a100 |
|
||||||
|
| ip | ip1~ip2000000 |
|
||||||
|
|
||||||
|
##### value
|
||||||
|
|
||||||
|
度量值为 [1, 100] 区间内的随机值
|
||||||
|
|
||||||
|
##### interval
|
||||||
|
|
||||||
|
采集周期为 10 秒,持续摄入 3 小时,总数据量为 `3 * 60 * 60 / 10 * 2000000 = 2,160,000,000` 个数据点。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 4.1.2 机器参数
|
||||||
|
|
||||||
|
OpenTSDB Reader 机型: 64C256G
|
||||||
|
|
||||||
|
HBase 机型: 8C16G * 5
|
||||||
|
|
||||||
|
|
||||||
|
#### 4.1.3 DataX jvm 参数
|
||||||
|
|
||||||
|
"-Xms4096m -Xmx4096m"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4.2 测试报告
|
||||||
|
|
||||||
|
|
||||||
|
| 通道数| DataX 速度 (Rec/s) |DataX 流量 (MB/s)|
|
||||||
|
|--------| --------|--------|
|
||||||
|
|1| 215428 | 25.65 |
|
||||||
|
|2| 424994 | 50.60 |
|
||||||
|
|3| 603132 | 71.81 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 5 约束限制
|
||||||
|
|
||||||
|
### 5.1 需要确保与 OpenTSDB 底层存储的网络是连通的
|
||||||
|
|
||||||
|
具体缘由详见 6.1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 5.2 如果存在某一个 Metric 下在一个小时范围内的数据量过大,可能需要通过 `-j` 参数调整 JVM 内存大小
|
||||||
|
|
||||||
|
考虑到下游 Writer 如果写入速度不及 OpenTSDB reader 的查询数据,可能会存在积压的情况,因此需要适当地调整 JVM 参数。以"从 OpenTSDB 数据库同步抽取数据到本地的作业"为例,启动命令如下:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python datax/bin/datax.py opentsdb2stream.json -j "-Xms4096m -Xmx4096m"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 5.3 指定起止时间会自动被转为整点时刻
|
||||||
|
|
||||||
|
指定起止时间会自动被转为整点时刻,例如 2019-4-18 的 `[3:35, 3:55)` 会被转为 `[3:00, 4:00)`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 5.4 目前只支持兼容 OpenTSDB 2.3.x
|
||||||
|
|
||||||
|
其他版本暂不保证兼容
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 6 FAQ
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
**Q:为什么需要连接 OpenTSDB 的底层存储,为什么不直接使用 `/api/query` 查询获取数据点?**
|
||||||
|
|
||||||
|
A:因为通过 OpenTSDB 的 HTTP 接口(`/api/query`)来读取数据的话,经内部压测发现,在大数据量的情况下,会导致 OpenTSDB 的异步框架会报 CallBack 过多的问题;所以,采用了直连底层 HBase 存储,通过 Scan 的方式来扫描数据点,来避免这个问题。另外,还考虑到,可以通过指定 metric 和时间范围,可以顺序地 Scan HBase 表,提高查询效率。
|
||||||
|
|
||||||
|
|
||||||
|
|
156
opentsdbreader/pom.xml
Normal file
156
opentsdbreader/pom.xml
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.alibaba.datax</groupId>
|
||||||
|
<artifactId>datax-all</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>opentsdbreader</artifactId>
|
||||||
|
<name>opentsdbreader</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
|
<!-- common -->
|
||||||
|
<commons-lang3.version>3.3.2</commons-lang3.version>
|
||||||
|
|
||||||
|
<!-- http -->
|
||||||
|
<httpclient.version>4.4</httpclient.version>
|
||||||
|
<commons-io.version>2.4</commons-io.version>
|
||||||
|
|
||||||
|
<!-- json -->
|
||||||
|
<fastjson.version>1.2.28</fastjson.version>
|
||||||
|
|
||||||
|
<!-- opentsdb -->
|
||||||
|
<opentsdb.version>2.3.2</opentsdb.version>
|
||||||
|
|
||||||
|
<!-- test -->
|
||||||
|
<junit4.version>4.12</junit4.version>
|
||||||
|
|
||||||
|
<!-- time -->
|
||||||
|
<joda-time.version>2.9.9</joda-time.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.datax</groupId>
|
||||||
|
<artifactId>datax-common</artifactId>
|
||||||
|
<version>${datax-project-version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>commons-math3</artifactId>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- common -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>${commons-lang3.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- http -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>${httpclient.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>${commons-io.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>fluent-hc</artifactId>
|
||||||
|
<version>${httpclient.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- json -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>${fastjson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- opentsdb -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.opentsdb</groupId>
|
||||||
|
<artifactId>opentsdb</artifactId>
|
||||||
|
<version>${opentsdb.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- time -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>joda-time</groupId>
|
||||||
|
<artifactId>joda-time</artifactId>
|
||||||
|
<version>${joda-time.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- test -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit4.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- compiler plugin -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
<encoding>${project-sourceEncoding}</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- assembly plugin -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>src/main/assembly/package.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<finalName>datax</finalName>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>dwzip</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
35
opentsdbreader/src/main/assembly/package.xml
Executable file
35
opentsdbreader/src/main/assembly/package.xml
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
<assembly
|
||||||
|
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||||
|
<id></id>
|
||||||
|
<formats>
|
||||||
|
<format>dir</format>
|
||||||
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>plugin.json</include>
|
||||||
|
<include>plugin_job_template.json</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>plugin/reader/opentsdbreader</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>opentsdbreader-0.0.1-SNAPSHOT.jar</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>plugin/reader/opentsdbreader</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
|
||||||
|
<dependencySets>
|
||||||
|
<dependencySet>
|
||||||
|
<useProjectArtifact>false</useProjectArtifact>
|
||||||
|
<outputDirectory>plugin/reader/opentsdbreader/libs</outputDirectory>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
|
</assembly>
|
@ -0,0 +1,104 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.conn;
|
||||||
|
|
||||||
|
import net.opentsdb.core.*;
|
||||||
|
import net.opentsdb.utils.DateTime;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:CliQuery
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-17
|
||||||
|
*/
|
||||||
|
final class CliQuery {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the query from the command lines.
|
||||||
|
*
|
||||||
|
* @param args The command line arguments.
|
||||||
|
* @param tsdb The TSDB to use.
|
||||||
|
* @param queries The list in which {@link Query}s will be appended.
|
||||||
|
*/
|
||||||
|
static void parseCommandLineQuery(final String[] args,
|
||||||
|
final TSDB tsdb,
|
||||||
|
final ArrayList<Query> queries) {
|
||||||
|
long start_ts = DateTime.parseDateTimeString(args[0], null);
|
||||||
|
if (start_ts >= 0) {
|
||||||
|
start_ts /= 1000;
|
||||||
|
}
|
||||||
|
long end_ts = -1;
|
||||||
|
if (args.length > 3) {
|
||||||
|
// see if we can detect an end time
|
||||||
|
try {
|
||||||
|
if (args[1].charAt(0) != '+' && (args[1].indexOf(':') >= 0
|
||||||
|
|| args[1].indexOf('/') >= 0 || args[1].indexOf('-') >= 0
|
||||||
|
|| Long.parseLong(args[1]) > 0)) {
|
||||||
|
end_ts = DateTime.parseDateTimeString(args[1], null);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignore) {
|
||||||
|
// ignore it as it means the third parameter is likely the aggregator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// temp fixup to seconds from ms until the rest of TSDB supports ms
|
||||||
|
// Note you can't append this to the DateTime.parseDateTimeString() call as
|
||||||
|
// it clobbers -1 results
|
||||||
|
if (end_ts >= 0) {
|
||||||
|
end_ts /= 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = end_ts < 0 ? 1 : 2;
|
||||||
|
while (i < args.length && args[i].charAt(0) == '+') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < args.length) {
|
||||||
|
final Aggregator agg = Aggregators.get(args[i++]);
|
||||||
|
final boolean rate = "rate".equals(args[i]);
|
||||||
|
RateOptions rate_options = new RateOptions(false, Long.MAX_VALUE,
|
||||||
|
RateOptions.DEFAULT_RESET_VALUE);
|
||||||
|
if (rate) {
|
||||||
|
i++;
|
||||||
|
|
||||||
|
long counterMax = Long.MAX_VALUE;
|
||||||
|
long resetValue = RateOptions.DEFAULT_RESET_VALUE;
|
||||||
|
if (args[i].startsWith("counter")) {
|
||||||
|
String[] parts = Tags.splitString(args[i], ',');
|
||||||
|
if (parts.length >= 2 && parts[1].length() > 0) {
|
||||||
|
counterMax = Long.parseLong(parts[1]);
|
||||||
|
}
|
||||||
|
if (parts.length >= 3 && parts[2].length() > 0) {
|
||||||
|
resetValue = Long.parseLong(parts[2]);
|
||||||
|
}
|
||||||
|
rate_options = new RateOptions(true, counterMax, resetValue);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final boolean downsample = "downsample".equals(args[i]);
|
||||||
|
if (downsample) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
final long interval = downsample ? Long.parseLong(args[i++]) : 0;
|
||||||
|
final Aggregator sampler = downsample ? Aggregators.get(args[i++]) : null;
|
||||||
|
final String metric = args[i++];
|
||||||
|
final HashMap<String, String> tags = new HashMap<String, String>();
|
||||||
|
while (i < args.length && args[i].indexOf(' ', 1) < 0
|
||||||
|
&& args[i].indexOf('=', 1) > 0) {
|
||||||
|
Tags.parse(tags, args[i++]);
|
||||||
|
}
|
||||||
|
final Query query = tsdb.newQuery();
|
||||||
|
query.setStartTime(start_ts);
|
||||||
|
if (end_ts > 0) {
|
||||||
|
query.setEndTime(end_ts);
|
||||||
|
}
|
||||||
|
query.setTimeSeries(metric, tags, agg, rate, rate_options);
|
||||||
|
if (downsample) {
|
||||||
|
query.downsample(interval, sampler);
|
||||||
|
}
|
||||||
|
queries.add(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.conn;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.plugin.RecordSender;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Connection for TSDB-like databases
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
public interface Connection4TSDB {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the address of Database.
|
||||||
|
*
|
||||||
|
* @return host+ip
|
||||||
|
*/
|
||||||
|
String address();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version of Database.
|
||||||
|
*
|
||||||
|
* @return version
|
||||||
|
*/
|
||||||
|
String version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get these configurations.
|
||||||
|
*
|
||||||
|
* @return configs
|
||||||
|
*/
|
||||||
|
String config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of supported version.
|
||||||
|
*
|
||||||
|
* @return version list
|
||||||
|
*/
|
||||||
|
String[] getSupportVersionPrefix();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data points by metric & start time & end time.
|
||||||
|
*
|
||||||
|
* @param metric metric
|
||||||
|
* @param start startTime
|
||||||
|
* @param end endTime
|
||||||
|
* @param recordSender sender
|
||||||
|
*/
|
||||||
|
void sendDPs(String metric, Long start, Long end, RecordSender recordSender) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put data point.
|
||||||
|
*
|
||||||
|
* @param dp data point
|
||||||
|
* @return whether the data point is written successfully
|
||||||
|
*/
|
||||||
|
boolean put(DataPoint4TSDB dp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put data points.
|
||||||
|
*
|
||||||
|
* @param dps data points
|
||||||
|
* @return whether the data point is written successfully
|
||||||
|
*/
|
||||||
|
boolean put(List<DataPoint4TSDB> dps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether current version is supported.
|
||||||
|
*
|
||||||
|
* @return true: supported; false: not yet!
|
||||||
|
*/
|
||||||
|
boolean isSupported();
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.conn;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:DataPoint for TSDB
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-10
|
||||||
|
*/
|
||||||
|
public class DataPoint4TSDB {
|
||||||
|
|
||||||
|
private long timestamp;
|
||||||
|
private String metric;
|
||||||
|
private Map<String, String> tags;
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
public DataPoint4TSDB() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataPoint4TSDB(long timestamp, String metric, Map<String, String> tags, Object value) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.metric = metric;
|
||||||
|
this.tags = tags;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(long timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMetric() {
|
||||||
|
return metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetric(String metric) {
|
||||||
|
this.metric = metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getTags() {
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTags(Map<String, String> tags) {
|
||||||
|
this.tags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(Object value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return JSON.toJSONString(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.conn;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.element.Record;
|
||||||
|
import com.alibaba.datax.common.element.StringColumn;
|
||||||
|
import com.alibaba.datax.common.plugin.RecordSender;
|
||||||
|
import net.opentsdb.core.*;
|
||||||
|
import net.opentsdb.core.Internal.Cell;
|
||||||
|
import org.hbase.async.KeyValue;
|
||||||
|
import org.hbase.async.Scanner;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Tool to dump the data straight from HBase
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-17
|
||||||
|
*/
|
||||||
|
final class DumpSeries {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DumpSeries.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump all data points with special metric and time range, then send them all by {@link RecordSender}.
|
||||||
|
*/
|
||||||
|
static void doDump(TSDB tsdb, String[] args, RecordSender sender) throws Exception {
|
||||||
|
final ArrayList<Query> queries = new ArrayList<Query>();
|
||||||
|
CliQuery.parseCommandLineQuery(args, tsdb, queries);
|
||||||
|
|
||||||
|
List<DataPoint4TSDB> dps = new LinkedList<DataPoint4TSDB>();
|
||||||
|
for (final Query query : queries) {
|
||||||
|
final List<Scanner> scanners = Internal.getScanners(query);
|
||||||
|
for (Scanner scanner : scanners) {
|
||||||
|
ArrayList<ArrayList<KeyValue>> rows;
|
||||||
|
while ((rows = scanner.nextRows().join()) != null) {
|
||||||
|
for (final ArrayList<KeyValue> row : rows) {
|
||||||
|
final byte[] key = row.get(0).key();
|
||||||
|
final long baseTime = Internal.baseTime(tsdb, key);
|
||||||
|
final String metric = Internal.metricName(tsdb, key);
|
||||||
|
for (final KeyValue kv : row) {
|
||||||
|
formatKeyValue(dps, tsdb, kv, baseTime, metric);
|
||||||
|
for (DataPoint4TSDB dp : dps) {
|
||||||
|
StringColumn tsdbColumn = new StringColumn(dp.toString());
|
||||||
|
Record record = sender.createRecord();
|
||||||
|
record.addColumn(tsdbColumn);
|
||||||
|
sender.sendToWriter(record);
|
||||||
|
}
|
||||||
|
dps.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse KeyValue into data points.
|
||||||
|
*/
|
||||||
|
private static void formatKeyValue(final List<DataPoint4TSDB> dps, final TSDB tsdb,
|
||||||
|
final KeyValue kv, final long baseTime, final String metric) {
|
||||||
|
Map<String, String> tagKVs = Internal.getTags(tsdb, kv.key());
|
||||||
|
|
||||||
|
final byte[] qualifier = kv.qualifier();
|
||||||
|
final int q_len = qualifier.length;
|
||||||
|
|
||||||
|
if (!AppendDataPoints.isAppendDataPoints(qualifier) && q_len % 2 != 0) {
|
||||||
|
// custom data object, not a data point
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Not a data point");
|
||||||
|
}
|
||||||
|
} else if (q_len == 2 || q_len == 4 && Internal.inMilliseconds(qualifier)) {
|
||||||
|
// regular data point
|
||||||
|
final Cell cell = Internal.parseSingleValue(kv);
|
||||||
|
if (cell == null) {
|
||||||
|
throw new IllegalDataException("Unable to parse row: " + kv);
|
||||||
|
}
|
||||||
|
dps.add(new DataPoint4TSDB(cell.absoluteTimestamp(baseTime), metric, tagKVs, cell.parseValue()));
|
||||||
|
} else {
|
||||||
|
final Collection<Cell> cells;
|
||||||
|
if (q_len == 3) {
|
||||||
|
// append data points
|
||||||
|
cells = new AppendDataPoints().parseKeyValue(tsdb, kv);
|
||||||
|
} else {
|
||||||
|
// compacted column
|
||||||
|
cells = Internal.extractDataPoints(kv);
|
||||||
|
}
|
||||||
|
for (Cell cell : cells) {
|
||||||
|
dps.add(new DataPoint4TSDB(cell.absoluteTimestamp(baseTime), metric, tagKVs, cell.parseValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.conn;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.plugin.RecordSender;
|
||||||
|
import com.alibaba.datax.plugin.reader.util.TSDBUtils;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:OpenTSDB Connection
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
public class OpenTSDBConnection implements Connection4TSDB {
|
||||||
|
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
public OpenTSDBConnection(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String address() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String version() {
|
||||||
|
return TSDBUtils.version(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String config() {
|
||||||
|
return TSDBUtils.config(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportVersionPrefix() {
|
||||||
|
return new String[]{"2.3"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendDPs(String metric, Long start, Long end, RecordSender recordSender) throws Exception {
|
||||||
|
OpenTSDBDump.dump(this, metric, start, end, recordSender);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(DataPoint4TSDB dp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(List<DataPoint4TSDB> dps) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
String versionJson = version();
|
||||||
|
if (StringUtils.isBlank(versionJson)) {
|
||||||
|
throw new RuntimeException("Cannot get the version!");
|
||||||
|
}
|
||||||
|
String version = JSON.parseObject(versionJson).getString("version");
|
||||||
|
if (StringUtils.isBlank(version)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String prefix : getSupportVersionPrefix()) {
|
||||||
|
if (version.startsWith(prefix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.conn;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.plugin.RecordSender;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import net.opentsdb.core.TSDB;
|
||||||
|
import net.opentsdb.utils.Config;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:OpenTSDB Dump
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-15
|
||||||
|
*/
|
||||||
|
final class OpenTSDBDump {
|
||||||
|
|
||||||
|
private static TSDB TSDB_INSTANCE;
|
||||||
|
|
||||||
|
private OpenTSDBDump() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dump(OpenTSDBConnection conn, String metric, Long start, Long end, RecordSender sender) throws Exception {
|
||||||
|
DumpSeries.doDump(getTSDB(conn), new String[]{start + "", end + "", "none", metric}, sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TSDB getTSDB(OpenTSDBConnection conn) {
|
||||||
|
if (TSDB_INSTANCE == null) {
|
||||||
|
synchronized (TSDB.class) {
|
||||||
|
if (TSDB_INSTANCE == null) {
|
||||||
|
try {
|
||||||
|
Config config = new Config(false);
|
||||||
|
Map configurations = JSON.parseObject(conn.config(), Map.class);
|
||||||
|
for (Object key : configurations.keySet()) {
|
||||||
|
config.overrideConfig(key.toString(), configurations.get(key.toString()).toString());
|
||||||
|
}
|
||||||
|
TSDB_INSTANCE = new TSDB(config);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Cannot init OpenTSDB connection!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TSDB_INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.opentsdbreader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Key
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-18
|
||||||
|
*/
|
||||||
|
public final class Constant {
|
||||||
|
|
||||||
|
static final String DEFAULT_DATA_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.opentsdbreader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Key
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-18
|
||||||
|
*/
|
||||||
|
public class Key {
|
||||||
|
|
||||||
|
static final String ENDPOINT = "endpoint";
|
||||||
|
static final String COLUMN = "column";
|
||||||
|
static final String BEGIN_DATE_TIME = "beginDateTime";
|
||||||
|
static final String END_DATE_TIME = "endDateTime";
|
||||||
|
}
|
@ -0,0 +1,207 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.opentsdbreader;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.exception.DataXException;
|
||||||
|
import com.alibaba.datax.common.plugin.RecordSender;
|
||||||
|
import com.alibaba.datax.common.spi.Reader;
|
||||||
|
import com.alibaba.datax.common.util.Configuration;
|
||||||
|
import com.alibaba.datax.plugin.reader.conn.OpenTSDBConnection;
|
||||||
|
import com.alibaba.datax.plugin.reader.util.TimeUtils;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Key
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-18
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class OpenTSDBReader extends Reader {
|
||||||
|
|
||||||
|
public static class Job extends Reader.Job {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Job.class);
|
||||||
|
|
||||||
|
private Configuration originalConfig;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
this.originalConfig = super.getPluginJobConf();
|
||||||
|
|
||||||
|
String address = originalConfig.getString(Key.ENDPOINT);
|
||||||
|
if (StringUtils.isBlank(address)) {
|
||||||
|
throw DataXException.asDataXException(
|
||||||
|
OpenTSDBReaderErrorCode.REQUIRED_VALUE,
|
||||||
|
"The parameter [" + Key.ENDPOINT + "] is not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> columns = originalConfig.getList(Key.COLUMN, String.class);
|
||||||
|
if (columns == null || columns.isEmpty()) {
|
||||||
|
throw DataXException.asDataXException(
|
||||||
|
OpenTSDBReaderErrorCode.REQUIRED_VALUE,
|
||||||
|
"The parameter [" + Key.COLUMN + "] is not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat(Constant.DEFAULT_DATA_FORMAT);
|
||||||
|
String startTime = originalConfig.getString(Key.BEGIN_DATE_TIME);
|
||||||
|
Long startDate;
|
||||||
|
if (startTime == null || startTime.trim().length() == 0) {
|
||||||
|
throw DataXException.asDataXException(
|
||||||
|
OpenTSDBReaderErrorCode.REQUIRED_VALUE,
|
||||||
|
"The parameter [" + Key.BEGIN_DATE_TIME + "] is not set.");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
startDate = format.parse(startTime).getTime();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw DataXException.asDataXException(OpenTSDBReaderErrorCode.ILLEGAL_VALUE,
|
||||||
|
"The parameter [" + Key.BEGIN_DATE_TIME +
|
||||||
|
"] needs to conform to the [" + Constant.DEFAULT_DATA_FORMAT + "] format.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String endTime = originalConfig.getString(Key.END_DATE_TIME);
|
||||||
|
Long endDate;
|
||||||
|
if (endTime == null || endTime.trim().length() == 0) {
|
||||||
|
throw DataXException.asDataXException(
|
||||||
|
OpenTSDBReaderErrorCode.REQUIRED_VALUE,
|
||||||
|
"The parameter [" + Key.END_DATE_TIME + "] is not set.");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
endDate = format.parse(endTime).getTime();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw DataXException.asDataXException(OpenTSDBReaderErrorCode.ILLEGAL_VALUE,
|
||||||
|
"The parameter [" + Key.END_DATE_TIME +
|
||||||
|
"] needs to conform to the [" + Constant.DEFAULT_DATA_FORMAT + "] format.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startDate >= endDate) {
|
||||||
|
throw DataXException.asDataXException(OpenTSDBReaderErrorCode.ILLEGAL_VALUE,
|
||||||
|
"The parameter [" + Key.BEGIN_DATE_TIME +
|
||||||
|
"] should be less than the parameter [" + Key.END_DATE_TIME + "].");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Configuration> split(int adviceNumber) {
|
||||||
|
List<Configuration> configurations = new ArrayList<Configuration>();
|
||||||
|
|
||||||
|
// get metrics
|
||||||
|
List<String> columns = originalConfig.getList(Key.COLUMN, String.class);
|
||||||
|
|
||||||
|
// get time range
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat(Constant.DEFAULT_DATA_FORMAT);
|
||||||
|
long startTime;
|
||||||
|
try {
|
||||||
|
startTime = format.parse(originalConfig.getString(Key.BEGIN_DATE_TIME)).getTime();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw DataXException.asDataXException(
|
||||||
|
OpenTSDBReaderErrorCode.ILLEGAL_VALUE, "解析[" + Key.BEGIN_DATE_TIME + "]失败.", e);
|
||||||
|
}
|
||||||
|
long endTime;
|
||||||
|
try {
|
||||||
|
endTime = format.parse(originalConfig.getString(Key.END_DATE_TIME)).getTime();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw DataXException.asDataXException(
|
||||||
|
OpenTSDBReaderErrorCode.ILLEGAL_VALUE, "解析[" + Key.END_DATE_TIME + "]失败.", e);
|
||||||
|
}
|
||||||
|
if (TimeUtils.isSecond(startTime)) {
|
||||||
|
startTime *= 1000;
|
||||||
|
}
|
||||||
|
if (TimeUtils.isSecond(endTime)) {
|
||||||
|
endTime *= 1000;
|
||||||
|
}
|
||||||
|
DateTime startDateTime = new DateTime(TimeUtils.getTimeInHour(startTime));
|
||||||
|
DateTime endDateTime = new DateTime(TimeUtils.getTimeInHour(endTime));
|
||||||
|
|
||||||
|
// split by metric
|
||||||
|
for (String column : columns) {
|
||||||
|
// split by time in hour
|
||||||
|
while (startDateTime.isBefore(endDateTime)) {
|
||||||
|
Configuration clone = this.originalConfig.clone();
|
||||||
|
clone.set(Key.COLUMN, Collections.singletonList(column));
|
||||||
|
|
||||||
|
clone.set(Key.BEGIN_DATE_TIME, startDateTime.getMillis());
|
||||||
|
startDateTime = startDateTime.plusHours(1);
|
||||||
|
// Make sure the time interval is [start, end).
|
||||||
|
// Because net.opentsdb.core.Query.setEndTime means less than or equal to the end time.
|
||||||
|
clone.set(Key.END_DATE_TIME, startDateTime.getMillis() - 1);
|
||||||
|
configurations.add(clone);
|
||||||
|
|
||||||
|
LOG.info("Configuration: {}", JSON.toJSONString(clone));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Task extends Reader.Task {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Task.class);
|
||||||
|
|
||||||
|
private List<String> columns;
|
||||||
|
private OpenTSDBConnection conn;
|
||||||
|
private Long startTime;
|
||||||
|
private Long endTime;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
Configuration readerSliceConfig = super.getPluginJobConf();
|
||||||
|
|
||||||
|
LOG.info("getPluginJobConf: {}", JSON.toJSONString(readerSliceConfig));
|
||||||
|
|
||||||
|
this.columns = readerSliceConfig.getList(Key.COLUMN, String.class);
|
||||||
|
String address = readerSliceConfig.getString(Key.ENDPOINT);
|
||||||
|
|
||||||
|
conn = new OpenTSDBConnection(address);
|
||||||
|
|
||||||
|
this.startTime = readerSliceConfig.getLong(Key.BEGIN_DATE_TIME);
|
||||||
|
this.endTime = readerSliceConfig.getLong(Key.END_DATE_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startRead(RecordSender recordSender) {
|
||||||
|
try {
|
||||||
|
for (String column : columns) {
|
||||||
|
conn.sendDPs(column, this.startTime, this.endTime, recordSender);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw DataXException.asDataXException(
|
||||||
|
OpenTSDBReaderErrorCode.ILLEGAL_VALUE, "获取或发送数据点的过程中出错!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.opentsdbreader;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.spi.ErrorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:OpenTSDB Reader Error Code
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-18
|
||||||
|
*/
|
||||||
|
public enum OpenTSDBReaderErrorCode implements ErrorCode {
|
||||||
|
|
||||||
|
REQUIRED_VALUE("OpenTSDBReader-00", "缺失必要的值"),
|
||||||
|
ILLEGAL_VALUE("OpenTSDBReader-01", "值非法");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
OpenTSDBReaderErrorCode(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Code:[%s], Description:[%s]. ", this.code, this.description);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.util;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.http.client.fluent.Content;
|
||||||
|
import org.apache.http.client.fluent.Request;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:HttpUtils
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
public final class HttpUtils {
|
||||||
|
|
||||||
|
public final static Charset UTF_8 = Charset.forName("UTF-8");
|
||||||
|
public final static int CONNECT_TIMEOUT_DEFAULT_IN_MILL = (int) TimeUnit.SECONDS.toMillis(60);
|
||||||
|
public final static int SOCKET_TIMEOUT_DEFAULT_IN_MILL = (int) TimeUnit.SECONDS.toMillis(60);
|
||||||
|
|
||||||
|
private HttpUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String get(String url) throws Exception {
|
||||||
|
Content content = Request.Get(url)
|
||||||
|
.connectTimeout(CONNECT_TIMEOUT_DEFAULT_IN_MILL)
|
||||||
|
.socketTimeout(SOCKET_TIMEOUT_DEFAULT_IN_MILL)
|
||||||
|
.execute()
|
||||||
|
.returnContent();
|
||||||
|
if (content == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return content.asString(UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String post(String url, Map<String, Object> params) throws Exception {
|
||||||
|
return post(url, JSON.toJSONString(params), CONNECT_TIMEOUT_DEFAULT_IN_MILL, SOCKET_TIMEOUT_DEFAULT_IN_MILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String post(String url, String params) throws Exception {
|
||||||
|
return post(url, params, CONNECT_TIMEOUT_DEFAULT_IN_MILL, SOCKET_TIMEOUT_DEFAULT_IN_MILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String post(String url, Map<String, Object> params,
|
||||||
|
int connectTimeoutInMill, int socketTimeoutInMill) throws Exception {
|
||||||
|
return post(url, JSON.toJSONString(params), connectTimeoutInMill, socketTimeoutInMill);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String post(String url, String params,
|
||||||
|
int connectTimeoutInMill, int socketTimeoutInMill) throws Exception {
|
||||||
|
Content content = Request.Post(url)
|
||||||
|
.connectTimeout(connectTimeoutInMill)
|
||||||
|
.socketTimeout(socketTimeoutInMill)
|
||||||
|
.addHeader("Content-Type", "application/json")
|
||||||
|
.bodyString(params, ContentType.APPLICATION_JSON)
|
||||||
|
.execute()
|
||||||
|
.returnContent();
|
||||||
|
if (content == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return content.asString(UTF_8);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.util;
|
||||||
|
|
||||||
|
import com.alibaba.datax.plugin.reader.conn.DataPoint4TSDB;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TSDB Utils
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
public final class TSDBUtils {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TSDBUtils.class);
|
||||||
|
|
||||||
|
private TSDBUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String version(String address) {
|
||||||
|
String url = String.format("%s/api/version", address);
|
||||||
|
String rsp;
|
||||||
|
try {
|
||||||
|
rsp = HttpUtils.get(url);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return rsp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String config(String address) {
|
||||||
|
String url = String.format("%s/api/config", address);
|
||||||
|
String rsp;
|
||||||
|
try {
|
||||||
|
rsp = HttpUtils.get(url);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return rsp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean put(String address, List<DataPoint4TSDB> dps) {
|
||||||
|
return put(address, JSON.toJSON(dps));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean put(String address, DataPoint4TSDB dp) {
|
||||||
|
return put(address, JSON.toJSON(dp));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean put(String address, Object o) {
|
||||||
|
String url = String.format("%s/api/put", address);
|
||||||
|
String rsp;
|
||||||
|
try {
|
||||||
|
rsp = HttpUtils.post(url, o.toString());
|
||||||
|
// If successful, the returned content should be null.
|
||||||
|
assert rsp == null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Address: {}, DataPoints: {}", url, o);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TimeUtils
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-22
|
||||||
|
*/
|
||||||
|
public final class TimeUtils {
|
||||||
|
|
||||||
|
private TimeUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final long SECOND_MASK = 0xFFFFFFFF00000000L;
|
||||||
|
private static final long HOUR_IN_MILL = TimeUnit.HOURS.toMillis(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weather the timestamp is second.
|
||||||
|
*
|
||||||
|
* @param ts timestamp
|
||||||
|
*/
|
||||||
|
public static boolean isSecond(long ts) {
|
||||||
|
return (ts & SECOND_MASK) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the hour.
|
||||||
|
*
|
||||||
|
* @param ms time in millisecond
|
||||||
|
*/
|
||||||
|
public static long getTimeInHour(long ms) {
|
||||||
|
return ms - ms % HOUR_IN_MILL;
|
||||||
|
}
|
||||||
|
}
|
10
opentsdbreader/src/main/resources/plugin.json
Executable file
10
opentsdbreader/src/main/resources/plugin.json
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "opentsdbreader",
|
||||||
|
"class": "com.alibaba.datax.plugin.reader.opentsdbreader.OpenTSDBReader",
|
||||||
|
"description": {
|
||||||
|
"useScene": "从 OpenTSDB 中摄取数据点",
|
||||||
|
"mechanism": "根据时间和 metric 直连底层 HBase 存储,从而 Scan 出符合条件的数据点",
|
||||||
|
"warn": "指定起止时间会自动忽略分钟和秒,转为整点时刻,例如 2019-4-18 的 [3:35, 4:55) 会被转为 [3:00, 4:00)"
|
||||||
|
},
|
||||||
|
"developer": "Benedict Jin"
|
||||||
|
}
|
11
opentsdbreader/src/main/resources/plugin_job_template.json
Normal file
11
opentsdbreader/src/main/resources/plugin_job_template.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "opentsdbreader",
|
||||||
|
"parameter": {
|
||||||
|
"endpoint": "http://localhost:8242",
|
||||||
|
"column": [
|
||||||
|
"m"
|
||||||
|
],
|
||||||
|
"startTime": "2019-01-01 00:00:00",
|
||||||
|
"endTime": "2019-01-01 01:00:00"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.conn;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:OpenTSDB Connection4TSDB Test
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class OpenTSDBConnectionTest {
|
||||||
|
|
||||||
|
private static final String OPENTSDB_ADDRESS = "http://localhost:8242";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVersion() {
|
||||||
|
String version = new OpenTSDBConnection(OPENTSDB_ADDRESS).version();
|
||||||
|
Assert.assertNotNull(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsSupported() {
|
||||||
|
Assert.assertTrue(new OpenTSDBConnection(OPENTSDB_ADDRESS).isSupported());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Const
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
final class Const {
|
||||||
|
|
||||||
|
private Const() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String OPENTSDB_ADDRESS = "http://localhost:8242";
|
||||||
|
static final String TSDB_ADDRESS = "http://localhost:8240";
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.util;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:HttpUtils Test
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class HttpUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleCase() throws Exception {
|
||||||
|
String url = "https://httpbin.org/post";
|
||||||
|
Map<String, Object> params = new HashMap<String, Object>();
|
||||||
|
params.put("foo", "bar");
|
||||||
|
|
||||||
|
String rsp = HttpUtils.post(url, params);
|
||||||
|
System.out.println(rsp);
|
||||||
|
Assert.assertNotNull(rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGet() throws Exception {
|
||||||
|
String url = String.format("%s/api/version", Const.OPENTSDB_ADDRESS);
|
||||||
|
String rsp = HttpUtils.get(url);
|
||||||
|
System.out.println(rsp);
|
||||||
|
Assert.assertNotNull(rsp);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.util;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TSDB Test
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-11
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class TSDBTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVersion() {
|
||||||
|
String version = TSDBUtils.version(Const.TSDB_ADDRESS);
|
||||||
|
Assert.assertNotNull(version);
|
||||||
|
System.out.println(version);
|
||||||
|
|
||||||
|
version = TSDBUtils.version(Const.OPENTSDB_ADDRESS);
|
||||||
|
Assert.assertNotNull(version);
|
||||||
|
System.out.println(version);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.alibaba.datax.plugin.reader.util;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:com.alibaba.datax.common.util
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-22
|
||||||
|
*/
|
||||||
|
public class TimeUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsSecond() {
|
||||||
|
Assert.assertFalse(TimeUtils.isSecond(System.currentTimeMillis()));
|
||||||
|
Assert.assertTrue(TimeUtils.isSecond(System.currentTimeMillis() / 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTimeInHour() throws ParseException {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
Date date = sdf.parse("2019-04-18 15:32:33");
|
||||||
|
long timeInHour = TimeUtils.getTimeInHour(date.getTime());
|
||||||
|
Assert.assertEquals("2019-04-18 15:00:00", sdf.format(timeInHour));
|
||||||
|
}
|
||||||
|
}
|
14
package.xml
14
package.xml
@ -159,6 +159,13 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>datax</outputDirectory>
|
<outputDirectory>datax</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>opentsdbreader/target/datax/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*.*</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>datax</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
|
|
||||||
<!-- writer -->
|
<!-- writer -->
|
||||||
<fileSet>
|
<fileSet>
|
||||||
@ -322,5 +329,12 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>datax</outputDirectory>
|
<outputDirectory>datax</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>tsdbwriter/target/datax/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*.*</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>datax</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
|
2
pom.xml
2
pom.xml
@ -62,6 +62,7 @@
|
|||||||
<module>rdbmsreader</module>
|
<module>rdbmsreader</module>
|
||||||
<module>hbase11xreader</module>
|
<module>hbase11xreader</module>
|
||||||
<module>hbase094xreader</module>
|
<module>hbase094xreader</module>
|
||||||
|
<module>opentsdbreader</module>
|
||||||
|
|
||||||
<!-- writer -->
|
<!-- writer -->
|
||||||
<module>mysqlwriter</module>
|
<module>mysqlwriter</module>
|
||||||
@ -85,6 +86,7 @@
|
|||||||
<module>hbase11xsqlwriter</module>
|
<module>hbase11xsqlwriter</module>
|
||||||
<module>hbase11xsqlreader</module>
|
<module>hbase11xsqlreader</module>
|
||||||
<module>elasticsearchwriter</module>
|
<module>elasticsearchwriter</module>
|
||||||
|
<module>tsdbwriter</module>
|
||||||
|
|
||||||
<!-- common support module -->
|
<!-- common support module -->
|
||||||
<module>plugin-rdbms-util</module>
|
<module>plugin-rdbms-util</module>
|
||||||
|
187
tsdbwriter/doc/tsdbhttpwriter.md
Normal file
187
tsdbwriter/doc/tsdbhttpwriter.md
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
|
||||||
|
# TSDBWriter 插件文档
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
|
||||||
|
## 1 快速介绍
|
||||||
|
|
||||||
|
TSDBWriter 插件实现了将数据点写入到阿里巴巴自研 TSDB 数据库中(后续简称 TSDB)。
|
||||||
|
|
||||||
|
|
||||||
|
时间序列数据库(Time Series Database , 简称 TSDB)是一种高性能,低成本,稳定可靠的在线时序数据库服务;提供高效读写,高压缩比存储、时序数据插值及聚合计算,广泛应用于物联网(IoT)设备监控系统 ,企业能源管理系统(EMS),生产安全监控系统,电力检测系统等行业场景。 TSDB 提供百万级时序数据秒级写入,高压缩比低成本存储、预降采样、插值、多维聚合计算,查询结果可视化功能;解决由于设备采集点数量巨大,数据采集频率高,造成的存储成本高,写入和查询分析效率低的问题。更多关于 TSDB 的介绍,详见[阿里云 TSDB 官网](https://help.aliyun.com/product/54825.html)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 2 实现原理
|
||||||
|
|
||||||
|
通过 HTTP 连接 TSDB 实例,并通过 `/api/put` 接口将数据点写入。关于写入接口详见 TSDB 的[接口说明文档](https://help.aliyun.com/document_detail/59939.html)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 3 功能说明
|
||||||
|
|
||||||
|
### 3.1 配置样例
|
||||||
|
|
||||||
|
* 配置一个从 OpenTSDB 数据库同步抽取数据到 TSDB:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"job": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"reader": {
|
||||||
|
"name": "opentsdbreader",
|
||||||
|
"parameter": {
|
||||||
|
"endpoint": "http://localhost:4242",
|
||||||
|
"column": [
|
||||||
|
"m"
|
||||||
|
],
|
||||||
|
"startTime": "2019-01-01 00:00:00",
|
||||||
|
"endTime": "2019-01-01 03:00:00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"writer": {
|
||||||
|
"name": "tsdbhttpwriter",
|
||||||
|
"parameter": {
|
||||||
|
"endpoint": "http://localhost:8242"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setting": {
|
||||||
|
"speed": {
|
||||||
|
"channel": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.2 参数说明
|
||||||
|
|
||||||
|
* **name**
|
||||||
|
* 描述:本插件的名称
|
||||||
|
* 必选:是
|
||||||
|
* 默认值:tsdbhttpwriter
|
||||||
|
|
||||||
|
* **parameter**
|
||||||
|
* **endpoint**
|
||||||
|
* 描述:TSDB 的 HTTP 连接地址
|
||||||
|
* 必选:是
|
||||||
|
* 格式:http://IP:Port
|
||||||
|
* 默认值:无
|
||||||
|
|
||||||
|
* **batchSize**
|
||||||
|
* 描述:每次批量数据的条数
|
||||||
|
* 必选:否
|
||||||
|
* 格式:int,需要保证大于 0
|
||||||
|
* 默认值:100
|
||||||
|
|
||||||
|
* **maxRetryTime**
|
||||||
|
* 描述:失败后重试的次数
|
||||||
|
* 必选:否
|
||||||
|
* 格式:int,需要保证大于 1
|
||||||
|
* 默认值:3
|
||||||
|
|
||||||
|
* **ignoreWriteError**
|
||||||
|
* 描述:如果设置为 true,则忽略写入错误,继续写入;否则,多次重试后仍写入失败的话,则会终止写入任务
|
||||||
|
* 必选:否
|
||||||
|
* 格式:bool
|
||||||
|
* 默认值:false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.3 类型转换
|
||||||
|
|
||||||
|
|
||||||
|
| DataX 内部类型 | TSDB 数据类型 |
|
||||||
|
| -------------- | ------------------------------------------------------------ |
|
||||||
|
| String | TSDB 数据点序列化字符串,包括 timestamp、metric、tags 和 value |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 4 性能报告
|
||||||
|
|
||||||
|
### 4.1 环境准备
|
||||||
|
|
||||||
|
#### 4.1.1 数据特征
|
||||||
|
|
||||||
|
从 Metric、时间线、Value 和 采集周期 四个方面来描述:
|
||||||
|
|
||||||
|
##### metric
|
||||||
|
|
||||||
|
固定指定一个 metric 为 `m`。
|
||||||
|
|
||||||
|
##### tagkv
|
||||||
|
|
||||||
|
前四个 tagkv 全排列,形成 `10 * 20 * 100 * 100 = 2000000` 条时间线,最后 IP 对应 2000000 条时间线从 1 开始自增。
|
||||||
|
|
||||||
|
| **tag_k** | **tag_v** |
|
||||||
|
| --------- | ------------- |
|
||||||
|
| zone | z1~z10 |
|
||||||
|
| cluster | c1~c20 |
|
||||||
|
| group | g1~100 |
|
||||||
|
| app | a1~a100 |
|
||||||
|
| ip | ip1~ip2000000 |
|
||||||
|
|
||||||
|
##### value
|
||||||
|
|
||||||
|
度量值为 [1, 100] 区间内的随机值
|
||||||
|
|
||||||
|
##### interval
|
||||||
|
|
||||||
|
采集周期为 10 秒,持续摄入 3 小时,总数据量为 `3 * 60 * 60 / 10 * 2000000 = 2,160,000,000` 个数据点。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 4.1.2 机器参数
|
||||||
|
|
||||||
|
TSDB Writer 机型: 64C256G
|
||||||
|
|
||||||
|
HBase 机型: 8C16G * 5
|
||||||
|
|
||||||
|
#### 4.1.3 DataX jvm 参数
|
||||||
|
|
||||||
|
"-Xms4096m -Xmx4096m"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4.2 测试报告
|
||||||
|
|
||||||
|
|
||||||
|
| 通道数 | DataX 速度 (Rec/s) | DataX 流量 (MB/s) |
|
||||||
|
| ------ | ------------------ | ----------------- |
|
||||||
|
| 1 | 129753 | 15.45 |
|
||||||
|
| 2 | 284953 | 33.70 |
|
||||||
|
| 3 | 385868 | 45.71 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 5 约束限制
|
||||||
|
|
||||||
|
### 5.1 目前只支持兼容 TSDB 2.4.x 及以上版本
|
||||||
|
|
||||||
|
其他版本暂不保证兼容
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 6 FAQ
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
136
tsdbwriter/pom.xml
Normal file
136
tsdbwriter/pom.xml
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.alibaba.datax</groupId>
|
||||||
|
<artifactId>datax-all</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>tsdbwriter</artifactId>
|
||||||
|
<name>tsdbwriter</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
|
<!-- common -->
|
||||||
|
<commons-lang3.version>3.3.2</commons-lang3.version>
|
||||||
|
|
||||||
|
<!-- http -->
|
||||||
|
<httpclient.version>4.4</httpclient.version>
|
||||||
|
<commons-io.version>2.4</commons-io.version>
|
||||||
|
|
||||||
|
<!-- json -->
|
||||||
|
<fastjson.version>1.2.28</fastjson.version>
|
||||||
|
|
||||||
|
<!-- test -->
|
||||||
|
<junit4.version>4.12</junit4.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.datax</groupId>
|
||||||
|
<artifactId>datax-common</artifactId>
|
||||||
|
<version>${datax-project-version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>commons-math3</artifactId>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- common -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>${commons-lang3.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- http -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>${httpclient.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>${commons-io.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>fluent-hc</artifactId>
|
||||||
|
<version>${httpclient.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- json -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>${fastjson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- test -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit4.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- compiler plugin -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
<encoding>${project-sourceEncoding}</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- assembly plugin -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>src/main/assembly/package.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<finalName>datax</finalName>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>dwzip</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
35
tsdbwriter/src/main/assembly/package.xml
Executable file
35
tsdbwriter/src/main/assembly/package.xml
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
<assembly
|
||||||
|
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||||
|
<id></id>
|
||||||
|
<formats>
|
||||||
|
<format>dir</format>
|
||||||
|
</formats>
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>plugin.json</include>
|
||||||
|
<include>plugin_job_template.json</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>plugin/writer/tsdbwriter</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>tsdbwriter-0.0.1-SNAPSHOT.jar</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>plugin/writer/tsdbwriter</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
|
||||||
|
<dependencySets>
|
||||||
|
<dependencySet>
|
||||||
|
<useProjectArtifact>false</useProjectArtifact>
|
||||||
|
<outputDirectory>plugin/writer/tsdbwriter/libs</outputDirectory>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependencySet>
|
||||||
|
</dependencySets>
|
||||||
|
</assembly>
|
@ -0,0 +1,85 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.conn;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.plugin.RecordSender;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Connection for TSDB-like databases
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
public interface Connection4TSDB {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the address of Database.
|
||||||
|
*
|
||||||
|
* @return host+ip
|
||||||
|
*/
|
||||||
|
String address();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version of Database.
|
||||||
|
*
|
||||||
|
* @return version
|
||||||
|
*/
|
||||||
|
String version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get these configurations.
|
||||||
|
*
|
||||||
|
* @return configs
|
||||||
|
*/
|
||||||
|
String config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of supported version.
|
||||||
|
*
|
||||||
|
* @return version list
|
||||||
|
*/
|
||||||
|
String[] getSupportVersionPrefix();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data points by metric & start time & end time.
|
||||||
|
*
|
||||||
|
* @param metric metric
|
||||||
|
* @param start startTime
|
||||||
|
* @param end endTime
|
||||||
|
* @param recordSender sender
|
||||||
|
*/
|
||||||
|
void sendDPs(String metric, Long start, Long end, RecordSender recordSender) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put data point.
|
||||||
|
*
|
||||||
|
* @param dp data point
|
||||||
|
* @return whether the data point is written successfully
|
||||||
|
*/
|
||||||
|
boolean put(DataPoint4TSDB dp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put data points.
|
||||||
|
*
|
||||||
|
* @param dps data points
|
||||||
|
* @return whether the data point is written successfully
|
||||||
|
*/
|
||||||
|
boolean put(List<DataPoint4TSDB> dps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put data points.
|
||||||
|
*
|
||||||
|
* @param dps data points
|
||||||
|
* @return whether the data point is written successfully
|
||||||
|
*/
|
||||||
|
boolean put(String dps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether current version is supported.
|
||||||
|
*
|
||||||
|
* @return true: supported; false: not yet!
|
||||||
|
*/
|
||||||
|
boolean isSupported();
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.conn;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:DataPoint for TSDB
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-10
|
||||||
|
*/
|
||||||
|
public class DataPoint4TSDB {
|
||||||
|
|
||||||
|
private long timestamp;
|
||||||
|
private String metric;
|
||||||
|
private Map<String, String> tags;
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
public DataPoint4TSDB() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataPoint4TSDB(long timestamp, String metric, Map<String, String> tags, Object value) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.metric = metric;
|
||||||
|
this.tags = tags;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(long timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMetric() {
|
||||||
|
return metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetric(String metric) {
|
||||||
|
this.metric = metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getTags() {
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTags(Map<String, String> tags) {
|
||||||
|
this.tags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(Object value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return JSON.toJSONString(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.conn;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.plugin.RecordSender;
|
||||||
|
import com.alibaba.datax.plugin.writer.util.TSDBUtils;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TSDB Connection
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
public class TSDBConnection implements Connection4TSDB {
|
||||||
|
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
public TSDBConnection(String address) {
|
||||||
|
if (StringUtils.isBlank(address)) {
|
||||||
|
throw new RuntimeException("TSDBConnection init failed because address is blank!");
|
||||||
|
}
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String address() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String version() {
|
||||||
|
return TSDBUtils.version(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String config() {
|
||||||
|
return TSDBUtils.config(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportVersionPrefix() {
|
||||||
|
return new String[]{"2.4.1", "2.4.2"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendDPs(String metric, Long start, Long end, RecordSender recordSender) {
|
||||||
|
throw new RuntimeException("Not support yet!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(DataPoint4TSDB dp) {
|
||||||
|
return TSDBUtils.put(address, dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(List<DataPoint4TSDB> dps) {
|
||||||
|
return TSDBUtils.put(address, dps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(String dps) {
|
||||||
|
return TSDBUtils.put(address, dps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
String versionJson = version();
|
||||||
|
if (StringUtils.isBlank(versionJson)) {
|
||||||
|
throw new RuntimeException("Cannot get the version!");
|
||||||
|
}
|
||||||
|
String version = JSON.parseObject(versionJson).getString("version");
|
||||||
|
if (StringUtils.isBlank(version)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String prefix : getSupportVersionPrefix()) {
|
||||||
|
if (version.startsWith(prefix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.tsdbwriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Key
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-18
|
||||||
|
*/
|
||||||
|
public final class Constant {
|
||||||
|
|
||||||
|
static final int DEFAULT_BATCH_SIZE = 100;
|
||||||
|
static final int DEFAULT_TRY_SIZE = 3;
|
||||||
|
static final boolean DEFAULT_IGNORE_WRITE_ERROR = false;
|
||||||
|
}
|
17
tsdbwriter/src/main/java/com/alibaba/datax/plugin/writer/tsdbwriter/Key.java
Executable file
17
tsdbwriter/src/main/java/com/alibaba/datax/plugin/writer/tsdbwriter/Key.java
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.tsdbwriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Key
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-18
|
||||||
|
*/
|
||||||
|
public class Key {
|
||||||
|
|
||||||
|
static final String ENDPOINT = "endpoint";
|
||||||
|
static final String BATCH_SIZE = "batchSize";
|
||||||
|
static final String MAX_RETRY_TIME = "maxRetryTime";
|
||||||
|
static final String IGNORE_WRITE_ERROR = "ignoreWriteError";
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.tsdbwriter;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.element.Record;
|
||||||
|
import com.alibaba.datax.common.exception.DataXException;
|
||||||
|
import com.alibaba.datax.common.plugin.RecordReceiver;
|
||||||
|
import com.alibaba.datax.common.spi.Writer;
|
||||||
|
import com.alibaba.datax.common.util.Configuration;
|
||||||
|
import com.alibaba.datax.common.util.RetryUtil;
|
||||||
|
import com.alibaba.datax.plugin.writer.conn.TSDBConnection;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TSDB Http Writer
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-18
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class TSDBWriter extends Writer {
|
||||||
|
|
||||||
|
public static class Job extends Writer.Job {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Job.class);
|
||||||
|
|
||||||
|
private Configuration originalConfig;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
this.originalConfig = super.getPluginJobConf();
|
||||||
|
|
||||||
|
String address = this.originalConfig.getString(Key.ENDPOINT);
|
||||||
|
if (StringUtils.isBlank(address)) {
|
||||||
|
throw DataXException.asDataXException(TSDBWriterErrorCode.REQUIRED_VALUE,
|
||||||
|
"The parameter [" + Key.ENDPOINT + "] is not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer batchSize = this.originalConfig.getInt(Key.BATCH_SIZE);
|
||||||
|
if (batchSize == null || batchSize < 1) {
|
||||||
|
originalConfig.set(Key.BATCH_SIZE, Constant.DEFAULT_BATCH_SIZE);
|
||||||
|
LOG.info("The parameter [" + Key.BATCH_SIZE +
|
||||||
|
"] will be default value: " + Constant.DEFAULT_BATCH_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer retrySize = this.originalConfig.getInt(Key.MAX_RETRY_TIME);
|
||||||
|
if (retrySize == null || retrySize < 0) {
|
||||||
|
originalConfig.set(Key.MAX_RETRY_TIME, Constant.DEFAULT_TRY_SIZE);
|
||||||
|
LOG.info("The parameter [" + Key.MAX_RETRY_TIME +
|
||||||
|
"] will be default value: " + Constant.DEFAULT_TRY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean ignoreWriteError = this.originalConfig.getBool(Key.IGNORE_WRITE_ERROR);
|
||||||
|
if (ignoreWriteError == null) {
|
||||||
|
originalConfig.set(Key.IGNORE_WRITE_ERROR, Constant.DEFAULT_IGNORE_WRITE_ERROR);
|
||||||
|
LOG.info("The parameter [" + Key.IGNORE_WRITE_ERROR +
|
||||||
|
"] will be default value: " + Constant.DEFAULT_IGNORE_WRITE_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Configuration> split(int mandatoryNumber) {
|
||||||
|
ArrayList<Configuration> configurations = new ArrayList<Configuration>(mandatoryNumber);
|
||||||
|
for (int i = 0; i < mandatoryNumber; i++) {
|
||||||
|
configurations.add(this.originalConfig.clone());
|
||||||
|
}
|
||||||
|
return configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Task extends Writer.Task {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Task.class);
|
||||||
|
|
||||||
|
private TSDBConnection conn;
|
||||||
|
private int batchSize;
|
||||||
|
private int retrySize;
|
||||||
|
private boolean ignoreWriteError;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
Configuration writerSliceConfig = getPluginJobConf();
|
||||||
|
String address = writerSliceConfig.getString(Key.ENDPOINT);
|
||||||
|
this.conn = new TSDBConnection(address);
|
||||||
|
this.batchSize = writerSliceConfig.getInt(Key.BATCH_SIZE);
|
||||||
|
this.retrySize = writerSliceConfig.getInt(Key.MAX_RETRY_TIME);
|
||||||
|
this.ignoreWriteError = writerSliceConfig.getBool(Key.IGNORE_WRITE_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startWrite(RecordReceiver recordReceiver) {
|
||||||
|
try {
|
||||||
|
Record lastRecord = null;
|
||||||
|
Record record;
|
||||||
|
int count = 0;
|
||||||
|
StringBuilder dps = new StringBuilder();
|
||||||
|
while ((record = recordReceiver.getFromReader()) != null) {
|
||||||
|
final int recordLength = record.getColumnNumber();
|
||||||
|
for (int i = 0; i < recordLength; i++) {
|
||||||
|
dps.append(record.getColumn(i).asString());
|
||||||
|
dps.append(",");
|
||||||
|
count++;
|
||||||
|
if (count == batchSize) {
|
||||||
|
count = 0;
|
||||||
|
batchPut(record, "[" + dps.substring(0, dps.length() - 1) + "]");
|
||||||
|
dps = new StringBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastRecord = record;
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(dps.toString())) {
|
||||||
|
batchPut(lastRecord, "[" + dps.substring(0, dps.length() - 1) + "]");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw DataXException.asDataXException(TSDBWriterErrorCode.RUNTIME_EXCEPTION, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void batchPut(final Record record, final String dps) {
|
||||||
|
try {
|
||||||
|
RetryUtil.executeWithRetry(new Callable<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer call() {
|
||||||
|
if (!conn.put(dps)) {
|
||||||
|
getTaskPluginCollector().collectDirtyRecord(record, "Put data points failed!");
|
||||||
|
throw DataXException.asDataXException(TSDBWriterErrorCode.RUNTIME_EXCEPTION,
|
||||||
|
"Put data points failed!");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}, retrySize, 60000L, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (ignoreWriteError) {
|
||||||
|
LOG.warn("Ignore write exceptions and continue writing.");
|
||||||
|
} else {
|
||||||
|
throw DataXException.asDataXException(TSDBWriterErrorCode.RETRY_WRITER_EXCEPTION, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void post() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.tsdbwriter;
|
||||||
|
|
||||||
|
import com.alibaba.datax.common.spi.ErrorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TSDB Http Writer Error Code
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-18
|
||||||
|
*/
|
||||||
|
public enum TSDBWriterErrorCode implements ErrorCode {
|
||||||
|
|
||||||
|
REQUIRED_VALUE("TSDBWriter-00", "Missing the necessary value"),
|
||||||
|
RUNTIME_EXCEPTION("TSDBWriter-01", "Runtime exception"),
|
||||||
|
RETRY_WRITER_EXCEPTION("TSDBWriter-02", "After repeated attempts, the write still fails");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
TSDBWriterErrorCode(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Code:[%s], Description:[%s]. ", this.code, this.description);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.util;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.http.client.fluent.Content;
|
||||||
|
import org.apache.http.client.fluent.Request;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:HttpUtils
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
public final class HttpUtils {
|
||||||
|
|
||||||
|
public final static Charset UTF_8 = Charset.forName("UTF-8");
|
||||||
|
public final static int CONNECT_TIMEOUT_DEFAULT_IN_MILL = (int) TimeUnit.SECONDS.toMillis(60);
|
||||||
|
public final static int SOCKET_TIMEOUT_DEFAULT_IN_MILL = (int) TimeUnit.SECONDS.toMillis(60);
|
||||||
|
|
||||||
|
private HttpUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String get(String url) throws Exception {
|
||||||
|
Content content = Request.Get(url)
|
||||||
|
.connectTimeout(CONNECT_TIMEOUT_DEFAULT_IN_MILL)
|
||||||
|
.socketTimeout(SOCKET_TIMEOUT_DEFAULT_IN_MILL)
|
||||||
|
.execute()
|
||||||
|
.returnContent();
|
||||||
|
if (content == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return content.asString(UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String post(String url, Map<String, Object> params) throws Exception {
|
||||||
|
return post(url, JSON.toJSONString(params), CONNECT_TIMEOUT_DEFAULT_IN_MILL, SOCKET_TIMEOUT_DEFAULT_IN_MILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String post(String url, String params) throws Exception {
|
||||||
|
return post(url, params, CONNECT_TIMEOUT_DEFAULT_IN_MILL, SOCKET_TIMEOUT_DEFAULT_IN_MILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String post(String url, Map<String, Object> params,
|
||||||
|
int connectTimeoutInMill, int socketTimeoutInMill) throws Exception {
|
||||||
|
return post(url, JSON.toJSONString(params), connectTimeoutInMill, socketTimeoutInMill);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String post(String url, String params,
|
||||||
|
int connectTimeoutInMill, int socketTimeoutInMill) throws Exception {
|
||||||
|
Content content = Request.Post(url)
|
||||||
|
.connectTimeout(connectTimeoutInMill)
|
||||||
|
.socketTimeout(socketTimeoutInMill)
|
||||||
|
.addHeader("Content-Type", "application/json")
|
||||||
|
.bodyString(params, ContentType.APPLICATION_JSON)
|
||||||
|
.execute()
|
||||||
|
.returnContent();
|
||||||
|
if (content == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return content.asString(UTF_8);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.util;
|
||||||
|
|
||||||
|
import com.alibaba.datax.plugin.writer.conn.DataPoint4TSDB;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TSDB Utils
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
public final class TSDBUtils {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TSDBUtils.class);
|
||||||
|
|
||||||
|
private TSDBUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String version(String address) {
|
||||||
|
String url = String.format("%s/api/version", address);
|
||||||
|
String rsp;
|
||||||
|
try {
|
||||||
|
rsp = HttpUtils.get(url);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return rsp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String config(String address) {
|
||||||
|
String url = String.format("%s/api/config", address);
|
||||||
|
String rsp;
|
||||||
|
try {
|
||||||
|
rsp = HttpUtils.get(url);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return rsp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean put(String address, List<DataPoint4TSDB> dps) {
|
||||||
|
return put(address, JSON.toJSON(dps));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean put(String address, DataPoint4TSDB dp) {
|
||||||
|
return put(address, JSON.toJSON(dp));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean put(String address, Object o) {
|
||||||
|
return put(address, o.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean put(String address, String s) {
|
||||||
|
String url = String.format("%s/api/put", address);
|
||||||
|
String rsp;
|
||||||
|
try {
|
||||||
|
rsp = HttpUtils.post(url, s);
|
||||||
|
// If successful, the returned content should be null.
|
||||||
|
assert rsp == null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Address: {}, DataPoints: {}", url, s);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
10
tsdbwriter/src/main/resources/plugin.json
Executable file
10
tsdbwriter/src/main/resources/plugin.json
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "tsdbwriter",
|
||||||
|
"class": "com.alibaba.datax.plugin.writer.tsdbwriter.TSDBWriter",
|
||||||
|
"description": {
|
||||||
|
"useScene": "往 TSDB 中摄入数据点",
|
||||||
|
"mechanism": "调用 TSDB 的 /api/put 接口,实现数据点的写入",
|
||||||
|
"warn": ""
|
||||||
|
},
|
||||||
|
"developer": "Benedict Jin"
|
||||||
|
}
|
6
tsdbwriter/src/main/resources/plugin_job_template.json
Normal file
6
tsdbwriter/src/main/resources/plugin_job_template.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "tsdbwriter",
|
||||||
|
"parameter": {
|
||||||
|
"endpoint": "http://localhost:8242"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.conn;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TSDBConnection Test
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class TSDBConnectionTest {
|
||||||
|
|
||||||
|
private static final String TSDB_ADDRESS = "http://localhost:8240";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVersion() {
|
||||||
|
String version = new TSDBConnection(TSDB_ADDRESS).version();
|
||||||
|
Assert.assertNotNull(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsSupported() {
|
||||||
|
Assert.assertTrue(new TSDBConnection(TSDB_ADDRESS).isSupported());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:Const
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
final class Const {
|
||||||
|
|
||||||
|
private Const() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String OPENTSDB_ADDRESS = "http://localhost:8242";
|
||||||
|
static final String TSDB_ADDRESS = "http://localhost:8240";
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.util;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:HttpUtils Test
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-03-29
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class HttpUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleCase() throws Exception {
|
||||||
|
String url = "https://httpbin.org/post";
|
||||||
|
Map<String, Object> params = new HashMap<String, Object>();
|
||||||
|
params.put("foo", "bar");
|
||||||
|
|
||||||
|
String rsp = HttpUtils.post(url, params);
|
||||||
|
System.out.println(rsp);
|
||||||
|
Assert.assertNotNull(rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGet() throws Exception {
|
||||||
|
String url = String.format("%s/api/version", Const.OPENTSDB_ADDRESS);
|
||||||
|
String rsp = HttpUtils.get(url);
|
||||||
|
System.out.println(rsp);
|
||||||
|
Assert.assertNotNull(rsp);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.alibaba.datax.plugin.writer.util;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright @ 2019 alibaba.com
|
||||||
|
* All right reserved.
|
||||||
|
* Function:TSDB Test
|
||||||
|
*
|
||||||
|
* @author Benedict Jin
|
||||||
|
* @since 2019-04-11
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class TSDBTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVersion() {
|
||||||
|
String version = TSDBUtils.version(Const.TSDB_ADDRESS);
|
||||||
|
Assert.assertNotNull(version);
|
||||||
|
System.out.println(version);
|
||||||
|
|
||||||
|
version = TSDBUtils.version(Const.OPENTSDB_ADDRESS);
|
||||||
|
Assert.assertNotNull(version);
|
||||||
|
System.out.println(version);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user