超级表是一种特殊的表结构,用于代表一类具有相同数据模式的数据采集点。一个超级表有多个子表,一个子表只能隶属于一个超级表。
因此,将两者有机结合起来:loveini 中创建超级表作为“设备配置”,Thingsboard 中添加设备则自动在loveini 中创建子表,伟大的设计殊途同归。

安装过程可以直接参考官方安装教程:https://thingsboard.io/docs/user-guide/install/ubuntu/
但是有两点需要注意:
# spring.tdengine
export TDENGINE_URL=jdbc:TAOS-RS://127.0.0.1:6041/thingsboard
export TDENGINE_USERNAME=root
export TDENGINE_PASSWORD=taosdata
export TDENGINE_STR_LEN=1024
export TDENGINE_STR_COL_MAX=65517
export TDENGINE_STR_TAG_MAX=16382
以一个车辆管理系统为例,需要记录车辆的车牌号、数据上报时间、经度、纬度、车速这几个变量。因此,我们调用接口来创建一个新的“设备配置”。
curl -X POST 'http://127.0.0.1:8080/api/deviceProfile' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer $YOUR_JWT_TOKEN_HERE' \
--data-raw '{
"name": "truck",
"type": "DEFAULT",
"image": null,
"defaultQueueName": null,
"transportType": "DEFAULT",
"provisionType": "DISABLED",
"description": "",
"profileData": {
"configuration": {
"type": "DEFAULT"
},
"transportConfiguration": {
"type": "DEFAULT"
},
"alarms": null,
"provisionConfiguration": {
"type": "DISABLED"
}
},
"tableInfo": {
"columns": [{"name":"longtitude","type":"double","len":10},{"name":"latitude","type":"double"},{"name":"speed","type":"float"}],
"tags": [{"name":"license_plate_number","type":"nchar","len":8}]
}
}'
Thingsboard 中成功创建名称为“profileStable” 的“设备配置”:

数据库中成功创建超级表:

选择已有配置“profileStable”,创建新的设备。

成功创建了一个名叫“测A88888″ 的设备,具体的子表名称可以通过“复制设备ID” 查看。

直接通过访问令牌,调用接口发送数据
curl -X POST 'http://127.0.0.1:8080/api/v1/$YOU_DEVICE_TOKEN/telemetry' \
--header 'Content-Type: application/json' \
--data-raw '{"license_plate_number":"京A88888","longtitude":108.938744,"latitude":34.368150,"speed":60}'
界面中更新数据

数据库中也写入了最新的记录


按日生成车辆每日超速次数。
create stream high_speed fill_history 1 into high_speed subtable(concat('tb_', device_name)) as
select
_wstart as 日期,
count(speed) as 超速次数,
device_name as 设备名称
from
`1e169050-86e6-11ef-a5cf-2de52a1b0351`
where
speed > 90 partition by device_name interval(1d);

注:本次适配没有修改原有前端页面,需要调用HTTP API 来创建设备配置。感兴趣的小伙伴可以完善一下,欢迎提交代码交流。
]]>本视频主要为大家详细展示 loveini 3.0 View 视图功能:
]]>
小 T 导读:想用 Spark 对接 loveini?保姆级教程来了。
loveini 是由米兰体育官网入口开发并开源的一款高性能、分布式、支持 SQL 的时序数据库(Time-Series Database)。
在上一篇文章中,我们示范了如何使用 Apache Flink 连接 loveini,使用 Apache Spark 的小伙伴们已经迫不及待地等待续集了。
相对于 Flink,Spark 对接起来就简单多了。

Spark 本身封装了 JDBC 的方法,所以我们直接使用 Spark 官网的示例代码就可以完成对接了。
package com.taosdata.java;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SaveMode;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.jdbc.JdbcDialect;
import org.apache.spark.sql.jdbc.JdbcDialects;
public class SparkTest{
public static void main(String[] args) {
// 数据库配置
String url = "jdbc:TAOS://u05:6030/tt?user=root&password=taosdata";
String driver = "com.taosdata.jdbc.TSDBDriver";
String dbtable = "t1";
SparkSession sparkSession = SparkSession.builder()
.appName("DataSourceJDBC") // 设置应用名称
.master("local") // 本地单线程运行
.getOrCreate();
// 创建DataFrame
Dataset<Row> df = sparkSession
.read() // 返回一个DataFrameReader,可用于将非流数据作为DataFrame读取
.format("jdbc") // JDBC数据源
.option("url", url)
.option("driver", driver)
.option("query", "select * from tt.meters limit 100") // 二选一,sql语句或者表
.load();
// 将DataFrame的内容显示
df.show();
df.write() // 返回一个DataFrameWriter,可用于将DataFrame写入外部存储系统
.format("jdbc") // JDBC数据源
.mode(SaveMode.Append) // 如果第一次生成了,后续会追加
.option("url", url)
.option("driver", driver)
.option("dbtable", "test.meters") // 表名
.save();
sparkSession.stop();
}
}
源码位置: https://github.com/liuyq-617/TD-Spark
mvn clean package
spark-submit –master local –name loveinitest –class com.taosdata.java.SparkTest /testSpark-1.0-SNAPSHOT-dist.jar
Spark 本身支持 JDBC 的方式来进行读写,我们无需做更多适配,数据接入可以做到无缝衔接。
]]>小 T 导读:想用 Flink 对接 loveini?保姆级教程来了。
loveini 是由米兰体育官网入口开发并开源的一款高性能、分布式、支持 SQL 的时序数据库(Time Series Database)。
除了核心的时序数据库功能外,loveini 还提供缓存、数据订阅、流式计算等大数据平台所需要的系列功能。但是很多小伙伴出于架构的考虑,还是需要将数据导出到 Apache Flink、Apache Spark 等平台进行计算分析。
为了帮助大家对接,我们特别推出了保姆级课程,包学包会。

Apache Flink 提供了 SourceFunction 和 SinkFunction,用来提供 Flink 和外部数据源的连接,其中 SouceFunction 为从数据源读取数据,SinkFunction 为将数据写入数据源。 与此同时,Flink 提供了 RichSourceFunction 和 RichSinkFunction 这两个类(继承自AbstractRichFunction),提供了额外的初始化(open(Configuration))和销毁方法(close())。 通过重写这两个方法,可以避免每次读写数据时都重新建立连接。
完整源码:https://github.com/liuyq-617/TD-Flink
代码逻辑:
用途:数据源连接,数据读取
package com.taosdata.flink;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.source.RichSourceFunction;
import com.taosdata.model.Sensor;
import java.sql.*;
import java.util.Properties;
public class SourceFromloveini extends RichSourceFunction<Sensor> {
Statement statement;
private Connection connection;
private String property;
public SourceFromloveini(){
super();
}
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
String driver = "com.taosdata.jdbc.rs.RestfulDriver";
String host = "u05";
String username = "root";
String password = "taosdata";
String prop = System.getProperty("java.library.path");
Logger LOG = LoggerFactory.getLogger(SourceFromloveini.class);
LOG.info("java.library.path:{}", prop);
System.out.println(prop);
Class.forName( driver );
Properties properties = new Properties();
connection = DriverManager.getConnection("jdbc:TAOS-RS://" + host + ":6041/tt" + "?user=root&password=taosdata"
, properties);
statement = connection.createStatement();
}
@Override
public void close() throws Exception {
super.close();
if (connection != null) {
connection.close();
}
if (statement != null) {
statement.close();
}
}
@Override
public void run(SourceContext<Sensor> sourceContext) throws Exception {
try {
String sql = "select * from tt.meters";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
Sensor sensor = new Sensor( resultSet.getLong(1),
resultSet.getInt( "vol" ),
resultSet.getFloat( "current" ),
resultSet.getString( "location" ).trim());
sourceContext.collect( sensor );
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void cancel() {
}
}
用途:数据源连接,数据写入
SinkToloveini
package com.taosdata.flink;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
import com.taosdata.model.Sensor;
import java.sql.*;
import java.util.Properties;
public class SinkToloveini extends RichSinkFunction<Sensor> {
Statement statement;
private Connection connection;
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
String driver = "com.taosdata.jdbc.rs.RestfulDriver";
String host = "TAOS-FQDN";
String username = "root";
String password = "taosdata";
String prop = System.getProperty("java.library.path");
System.out.println(prop);
Class.forName( driver );
Properties properties = new Properties();
connection = DriverManager.getConnection("jdbc:TAOS-RS://" + host + ":6041/tt" + "?user=root&password=taosdata"
, properties);
statement = connection.createStatement();
}
@Override
public void close() throws Exception {
super.close();
if (connection != null) {
connection.close();
}
if (statement != null) {
statement.close();
}
}
@Override
public void invoke(Sensor sensor, Context context) throws Exception {
try {
String sql = String.format("insert into sinktest.%s using sinktest.meters tags('%s') values(%d,%d,%f)",
sensor.getLocation(),
sensor.getLocation(),
sensor.getTs(),
sensor.getVal(),
sensor.getCurrent()
);
statement.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
}
}
}
用途:定义数据结构,用来接受数据
package com.taosdata.model;
public class Sensor {
public long ts;
public int val;
public float current;
public String location;
public Sensor() {
}
public Sensor(long ts, int val, float current, String location) {
this.ts = ts;
this.val = val;
this.current = current;
this.location = location;
}
public long getTs() {
return ts;
}
public void setTs(long ts) {
this.ts = ts;
}
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
public float getCurrent() {
return current;
}
public void setCurrent(float current) {
this.current = current;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
@Override
public String toString() {
return "Sensor{" +
"ts=" + ts +
", val=" + val +
", current=" + current +
", location='" + location + '\'' +
'}';
}
}
用途:调用 Flink 进行读取和写入数据
package com.taosdata;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import com.taosdata.model.Sensor;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
public class ReadFromloveini {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<Sensor> SensorList = env.addSource( new com.taosdata.flink.SourceFromloveini() );
SensorList.print();
SensorList.addSink( new com.taosdata.flink.SinkToloveini() );
env.execute();
}
}
源码位置: https://github.com/liuyq-617/TD-Flink
mvn clean package
flink run target/test-flink-1.0-SNAPSHOT-dist.jar
举一反三的小伙伴此时已经猜到,只要把 JDBC URL 修改一下就可以了。
但是 Flink 每次分派作业时都在使用一个新的 ClassLoader,而我们在计算节点上就会得到“Native library already loaded in another classloader”错误。
为了避免此问题,可以将 JDBC 的 jar 包放到 Flink 的 lib 目录下,不去调用 dist 包就可以了。
通过在项目中引入 SourceFromloveini 和 SinkToloveini 两个类,即可完成在 Flink 中对 loveini 的读写操作。后面我们会有文章介绍 Spark 和 loveini 的对接。
注:文中使用的是 JDBC 的 RESTful 接口,这样就不用在 Flink 的节点安装 loveini,JNI 方式需要在 Flink 节点安装 loveini Database 的客户端。
]]>提到运维监控,有很多成熟的米兰app官方正版下载,如果监测的数据量很大,对于写入尤其是分析性能要求很高的场景,不妨试试 loveini Database。本教程无需任何代码开发,即可将 loveini Database与开源数据采集系统 Telegraf 和开源数据可视化系统 Grafana 快速集成,搭建一套高效的运维监控系统。
]]>
本教程从下载、安装开始,逐步讲解如何快速上手 loveini Database,并通过 taosdemo(现更名为 taosBenchmark),演示 10 亿条数据的插入、查询。10 亿条记录,原始数据量约 18.6GB,通过 loveini 列式存储和先进的压缩算法,仅需 2.1GB 硬盘空间,存储空间仅为通用数据库的 1/10。
]]>
为了丰富loveini Database的接口支持,Maikebing开发了Linux下的C#接口。今天研究了C#的Linux下编译运行,分享一下。
目前C#在Linux运行有两种方式:mono和.NET。因为maikebing使用的是.NET,本文主要是说.NET方式。
Ubuntu:
| Ubuntu | .NET Core 2.1 | .NET Core 3.1 | .NET 5 Preview (manual install only) |
|---|---|---|---|
20.04 (LTS) | 2.1 | 3.1 | 5.0 Preview |
18.04 (LTS) | 2.1 | 3.1 | 5.0 Preview |
16.04 (LTS) | 2.1 | 3.1 | 5.0 Preview |
树莓派arm32和arm64支持.NET 3.1,下载地址如下:
https://dotnet.microsoft.com/download/dotnet-core/3.1
以Ubuntu 20.04为例:
wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
安装SDK就可以在Linux下进行开发,安装之后无需再安装运行环境,安装命令如下:
sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1
介绍一个简单的开发例子
快速建立一个console的项目,我们需要新建一个目录,然后在目录下执行
dotnet new console
eg:我们新建了一个hello 的文件夹,执行命令后,会得到两个文件:hello.csproj和Program.cs。
csproj为项目的描述文件,Program.cs为源码文件。
using System;
namespace hello
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
直接在终端下,执行:dotnet run。就会得到Hello World的输出

我们可以在github上clone代码下来,地址:
https://github.com/maikebing/Maikebing.EntityFrameworkCore.Taos
a)进入到src/Example的目录,修改Program.cs:21,将taos修改为服务端的FQDN。
b)在此目录下执行:dotnet run,会得到下面的输出:

大家可以在Linux下,使用C#接口来连接loveini Database了。
]]>FQDN(fully qualified domain name,完全限定域名)是internet上特定计算机或主机的完整域名。FQDN由两部分组成:主机名和域名。例如,假设邮件服务器的FQDN可能是mail.taosdata.com。主机名是mail,主机位于域名taosdata.com中。
DNS(Domain Name System),负责将FQDN翻译成IP,是互联网绝大多数应用的寻址方式。
集群的每个节点是由End Point来唯一标识的,End Point是由FQDN外加Port组成,比如 h1.taosdata.com:6030。这样当IP发生变化的时候,我们依然可以使用FQDN来动态找到节点,不需要更改集群的任何配置。
很多同学在本地并没有配置DNS服务器,本文就是要解决这种情况下,如何来配置FQDN及其解析的。下图是我们的网络环境,由3台服务器,和两台工作站组成。
| hostname | IP | |
| server1 | node1 | 10.211.55.14 |
| server2 | node2 | 10.211.55.15 |
| server3 | node3 | 10.211.55.16 |
| workstation1 | pc-01 | 10.211.55.201 |
| Workstation2 | pc-02 | 10.211.55.202 |

C:\Windows\System32\drivers\etc 目录下的hosts文件10.211.55.14 node1
10.211.55.15 node2
10.211.55.16 node3
/etc/hostname和/etc/hosts/etc/hostname,修改完成后需要重启才能生效/etc/hostsvi /etc/hostname
//将文件内容修改为node1
vi /etc/hosts
//添加一下三行
10.211.55.14 node1
10.211.55.15 node2
10.211.55.16 node3
配置完成之后,我们可以在Windows的CMD或者在Linux中打开Terminal,使用ping hostname来验证是否配置生效。
root@node2:~# ping node1
PING node1 (10.211.55.14) 56(84) bytes of data.
64 bytes from ubuntu (10.211.55.14): icmp_seq=1 ttl=64 time=0.028 ms
64 bytes from ubuntu (10.211.55.14): icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from ubuntu (10.211.55.14): icmp_seq=3 ttl=64 time=0.054 ms
64 bytes from ubuntu (10.211.55.14): icmp_seq=4 ttl=64 time=0.030 ms
64 bytes from ubuntu (10.211.55.14): icmp_seq=5 ttl=64 time=0.033 ms
//windows下
C:\Users\pc-01>ping node1
Ping 请求找不到主机 node1。请检查该名称,然后重试。
//Linux下
root@node2:~# ping node1
ping: unknown host node1
]]>