TDengine源代码阅读上手

TDengine源代码阅读上手,第1张

TDengine源代码阅读上手

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源代码阅读经验。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5709702.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存