TDengine源代码阅读上手
TDengine是由涛思数据开发的一款时序数据库,核心代码已经在GitHub上全部开源:https://github.com/taosdata/TDengine/。
TDengine的影响力从世界知名数据库排行网站DB-engines的统计来看,过去的24个月,时序数据库的流行度非常高。
(图片来源为DB-engines网站:https://db-engines.com/en/ranking_categories)
再来看一下目前时序数据库的最新排名,可以看到以国内团队为主的两个开源时序数据库TDengine和Apache IoTDB都榜上有名。
最近几年,中国在软件基础设施方面已经有很多不错的成果,数据库领域就有好几款,已经开始开拓国际市场了。
TDengine是用C语言实现的,采用AGPL 3.0协议开源,从GitHub上的数据看,开发和讨论都是比较活跃的。接下来,我们就读一读TDengine的代码,了解一下其核心设计。
阅读代码的一般策略阅读好的代码,是优秀程序员的必备技能。一般来说,阅读代码有两种思路,从大处着眼和从小处着眼。
从大处着眼,我们可以先根据相关的设计文档,了解一下整个系统的结构,知道各个模块的大致功能,然后深入研究自己真正感兴趣的模块,可以辅以断点调试,真正理解程序的执行路径。比如看Linux内核代码,几百万行代码,一头扎进去很容易迷失,而且容易有挫败感。但是根据相关文档,了解内存管理、进程管理、文件管理等模块的一些基本设计,了解代码分布在哪些目录之下,再去看相关代码就相对好一些了。
从小处着眼,这时候目的性更强,比如我们看MySQL的代码,想看看其中的字符串管理是怎么实现的,这类功能一般会实现为基础库,和其他模块解藕,我们只要找到对应的源文件,阅读相关函数的实现即可。我们可以在里面加一些输出信息,然后自己编译一个版本,看看执行相关函数时的表现,有助于深入理解相应功能,相对于从大处着眼,这样更容易有成就感。
理解TDengine的代码结构我们就按照第一种思路,从大处着眼,先来看一下GitHub上src目录的整体结构:
命名还是相当清晰的,比如dnode、vnode、mnode,相关设计文档中都有介绍,实现单独组织在一起,这就降低了理解的难度。像tsdb应该就是实现核心时序数据处理功能的代码了。balance则是负载均衡相关的实现。util目录下则是一些基本的常用功能,比如压缩、哈希、数组、缓存、链表、队列、线程、定时器、池等,这样可以保证基本功能的代码复用。connector下则是各种连接器,比如支持Java的JDBC,还有支持Go、Node.js、Python等语言的接口,方便用户使用自己习惯的接口来读写TDengine数据库。
TDengine的架构设计在深入代码之前,我们先来了解TDengine的两个核心创新之处。
“一个数据采集点一张表”,以下内容为文档中对数据模型的介绍:为充分利用其数据的时序性和其他数据特点,TDengine 要求对每个数据采集点单独建表(比如有一千万个智能电表,就需创建一千万张表,上述表格中的 d1001, d1002, d1003, d1004 都需单独建表),用来存储这个采集点所采集的时序数据。
如果采用传统的方式,将多个设备的数据写入一张表,由于网络延时不可控,不同设备的数据到达服务器的时序是无法保证的,写入 *** 作是要有锁保护的,而且一个设备的数据是难以保证连续存储在一起的。采用一个数据采集点一张表的方式,能最大程度的保证单个数据采集点的插入和查询的性能是最优的。
超级表:同一类型数据采集点的集合
由于一个数据采集点一张表,导致表的数量巨增,难以管理,而且应用经常需要做采集点之间的聚合 *** 作,聚合的 *** 作也变得复杂起来。为解决这个问题,TDengine 引入超级表(Super Table,简称为 STable)的概念。
理解了这个数据模型,后面的工作就事半功倍了。
再来看架构的设计。从架构图中可以看到,dnode是一个基本的运行实例,多个数据节点组成一个TDengine集群。数据节点中又有很多的vnode,用于支持负载均衡和数据分片,它具有独立的运行线程、内存空间与持久化存储的路径。管理节点(mnode),顾名思义,就是负责管理功能的,比如负责所有数据节点运行状态的监控和维护,再就是管理各种元数据,比如数据库、表的信息,静态标签的信息,等等。
比如我们从dnode入手来理解代码,首先看dnode目录下的头文件,我们可以看到基本函数的声明。比如src/dnode/inc/dnodeMain.h:
#ifdef __cplusplus
extern "C" {
#endif
#include "dnodeInt.h"
int32_t dnodeInitSystem();
void dnodeCleanUpSystem();
#ifdef __cplusplus
}
#endif
从名字可以看出来,dnodeInitSystem()负责执行初始化,而dnodeCleanUpSystem()负责执行清理工作。再来看初始化方法的实现(https://github.com/taosdata/TDengine/blob/develop/src/dnode/src/dnodeMain.c):
int32_t dnodeInitSystem() {
dnodeSetRunStatus(TSDB_RUN_STATUS_INITIALIZE);
tscEmbedded = 1;
taosIgnSIGPIPE();
taosBlockSIGPIPE();
taosResolveCRC();
taosInitGlobalCfg();
taosReadGlobalLogCfg();
dnodeInitTmr();
if (dnodeCreateDir(tsLogDir) < 0) {
printf("failed to create dir: %s, reason: %sn", tsLogDir, strerror(errno));
return -1;
}
char temp[TSDB_FILENAME_LEN];
sprintf(temp, "%s/taosdlog", tsLogDir);
if (taosInitLog(temp, tsNumOfLogLines, 1) < 0) {
printf("failed to init log filen");
}
if (!taosReadGlobalCfg()) {
taosPrintGlobalCfg();
dError("TDengine read global config failed");
return -1;
}
taosSetCoreDump();
dInfo("start to initialize TDengine");
taosInitNotes();
if (dnodeInitComponents() != 0) {
return -1;
}
dnodeSetRunStatus(TSDB_RUN_STATUS_RUNING);
moduleStart();
tsDnodeStartTime = taosGetTimestampMs();
dnodeReportStep("TDengine", "initialized successfully", 1);
dInfo("TDengine is initialized successfully");
return 0;
}
我们就可以看到dnode初始化过程中的各项 *** 作了。上手并不是很难,接下来我们可以通过一些函数调用图来帮我们理解整个初始化过程,理解从TDengine启动到进入就绪状态,等待执行SQL语句,要执行哪些工作。篇幅所限,本文只是一个开头。后续我会分享更多TDengine源代码阅读经验。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)