7月12日一款叫做TDengine的时序数据库项目在Github上开源了,这个项目一经发布就稳稳占据了Github排行榜的C位,目前TdEngine已经累积了5000多个star,并且连续一周排在上升榜首位。而且你要知道TdEngine的开发语言并不是火热的Python或JAVA,而是C语言。C语言无巧可取,虽见功夫,但是代码比较难读,能引发如此的关注绝对堪称奇迹,在我印象中即使是Mysql也没有达到如此的热度。

相信很多人也和笔者一样,是通过《比hadoop快至少10 倍的物联网大数据平台,我把它开源》的刷屏文才了解到陶老师与TdEngine的,当看到这位50岁的IT老兵老兵,依旧奋斗在编程一线,为TDengine开发贡献3万行代码时候,我就立刻四处向朋友打听,并最终要了陶老师的微信,做为一名80后程序员,我近不急待的想和陶老师直接沟通,想从他身上找到保持编程水平的秘决。

大神面对面-这才是10倍程序员该有的样子

2008年的时候笔者还是CSDN论坛WINDOWS MOBILE版的版主,从事手机导航软件的开发工作,而在彼时陶老师也创办了和信公司,并亲自开发了WindowsMobile版和信客户端,相同的开发平台经历也让我们迅速的拉进了彼此的距离。

在使用TdEngine的过程中我发现了两个小问题,一是数据库用户密码明文存放,二是数据文件权限设置不合理。让我十分震惊的是,这两个问题是我下午在和陶老师聊天时提出的,当晚发布版本就把问题全部解决了。后来沟通得知这些BUG都是陶老师自己动手修改的。我意识到TdEngine的效率应该来自于创始人对于代码的执着与热爱,而不是对员工996式的工作要求。

陶老师是真的爱编程,尤其对于代码运行效率有着近乎狂热的追求,我查阅了陶老师近年来的作品,其和信客户端只有18K大小,胎心算法的实现只用了600行代码,而TDengine这样一个数据库项目竟然只需要1.5M安装包就能搞定,在手机APP都动辙上百M的今天,TDengine体量甚至显得有些异类。如果没有深厚的功底和坚定的信念是绝对无法达到如此高度的。我想陶老师应该就是传说中10倍程序员的典范吧。

10倍程序员对于他周围亲友的影响也是非常巨大的,当我打开TdEngine的官网,其简洁明快的风格,一目了然的配图,实在让我无法把这一切和一位年近半百的老派IT士人联系到一起,当然后来我和陶老师聊到这件事的时候才知道,整个网站从设计、前端、后台、浏览器适配、数据分析到搜索引擎优化,都是由陶老师的儿子,一位刚刚高中毕业的00后操刀主持的,而且整个网站从无到有只用了三周时间,除了感叹一句后生可畏,由此也可以看出来和10倍程序员并肩作战的也都是10倍程序员,所以it团队的负责人在感叹自己没有18程序员相助时也要反思一下,自己是不是一位10程序员。

TdEngine为什么会火

传统数据库厂商的问题在于傲慢、自大,他们认为数据是零件,数据库则是各类零件的加中心,很多工序都是为数据的修改准备的,无论修改是否发生加工车间为了保证一致性,都会对流水线上的数据加上各种各样的锁。这些操作浪费了很多时间,而且几乎没有任何轻量级的框架,可供用户选择省略掉这些冗余操作。而且传统厂商为了解决数据库的性能问题不是从底层架构逻辑下手,而是不休止的在应用与数据库之间加入各种像REDIS,NGIX等等代理或者缓存层,这种方式其实是加大了各层级间的性能开销。传统厂商认为自己非常了解数据,但却忘了用户比厂商更加了解自己的数据,天下可谓苦秦久已

而TdEngine是认为数据是信息流,它要做的非常简单,只是数据的录像机而已,信息调阅只要找到对应的录像带即可,这样的设计思路从底层逻辑上决定了td会是一款性能极高的产品。它更加贴合物联网时代的数据模型,而且代码只有10万行的量级,非常适合从从头开始学习。

所以TdEngine精确的找到了数据库市场的细分战场。他可以在相同的硬件条件下达到其它产品10倍的速度,完美解决了很多物联网,量化交易等场景的痛点。

TdEngine代码导读

当笔者打TdEngine的代码时不由眼前一亮,其代码风格及规范性绝对堪称一流,于是我打开了久违的souce insight,,再一次开始了阅读C语言代码的美妙旅程,在这里强烈推荐各位读者也来读一下,绝对堪称享受。

这里将给我启示最大的一段代码其链接在https://github.com/taosdata/TDengine/blob/master/src/util/src/tsched.c,向大家分享一下。鉴于本文肯定会分享给陶老师,所以估计会有作者亲答的环节:-),以下代码是一个典型的consumer-producer消息传递功能的实现,也就是有多个生产者(producer)生成并不断向队列中传递消息,也有多个消费者(consumer)不断从队列中取消息,而在java等高级语言中类似的功能已经被封装好了,这其实也让程序员无法了解线程间的同步和互斥机制。在正式进入到代码之前我想请大家思考这样的一个,互斥体( mutex)和信号量(semaphore)的使用是如何做到多线程安全的。

先来看结构体设计,具体我已经注释好了:

typedef struct {char            label[16];//消息内容sem_t           emptySem;//此信号量代表队列的可写状态sem_t           fullSem;//此信号量代表队列的可读状态pthread_mutex_t queueMutex;//此互斥体为保证消息不会被误修改,保证线程程安全int             fullSlot;//队尾位置int             emptySlot;//队头位置int             queueSize;#队列长度int             numOfThreads;//同时操作的线程数量pthread_t *     qthread;//线程指针SSchedMsg *     queue;//队列指针
} SSchedQueue;

再来看初始化函数,这里需要特别说明的是,两个信号量的创建,其中emptySem是队列的可写状态,初始化时其值为queueSize,即初始时队列可写,可接受消息长度为队列长度,fullSem是队列的可读状态,初始化时其值为0,即初始时队列不可读。具体代码及我的注释如下:

void *taosInitScheduler(int queueSize, int numOfThreads, char *label) {pthread_attr_t attr;SSchedQueue *  pSched = (SSchedQueue *)malloc(sizeof(SSchedQueue));memset(pSched, 0, sizeof(SSchedQueue));pSched->queueSize = queueSize;pSched->numOfThreads = numOfThreads;strcpy(pSched->label, label);if (pthread_mutex_init(&pSched->queueMutex, NULL) < 0) {pError("init %s:queueMutex failed, reason:%s", pSched->label, strerror(errno));goto _error;}//emptySem是队列的可写状态,初始化时其值为queueSize,即初始时队列可写,可接受消息长度为队列长度。if (sem_init(&pSched->emptySem, 0, (unsigned int)pSched->queueSize) != 0) {pError("init %s:empty semaphore failed, reason:%s", pSched->label, strerror(errno));goto _error;}//fullSem是队列的可读状态,初始化时其值为0,即初始时队列不可读if (sem_init(&pSched->fullSem, 0, 0) != 0) {pError("init %s:full semaphore failed, reason:%s", pSched->label, strerror(errno));goto _error;}if ((pSched->queue = (SSchedMsg *)malloc((size_t)pSched->queueSize * sizeof(SSchedMsg))) == NULL) {pError("%s: no enough memory for queue, reason:%s", pSched->label, strerror(errno));goto _error;}memset(pSched->queue, 0, (size_t)pSched->queueSize * sizeof(SSchedMsg));pSched->fullSlot = 0;//实始化时队列为空,故队头和队尾的位置都是0pSched->emptySlot = 0;//实始化时队列为空,故队头和队尾的位置都是0pSched->qthread = malloc(sizeof(pthread_t) * (size_t)pSched->numOfThreads);pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);for (int i = 0; i < pSched->numOfThreads; ++i) {if (pthread_create(pSched->qthread + i, &attr, taosProcessSchedQueue, (void *)pSched) != 0) {pError("%s: failed to create rpc thread, reason:%s", pSched->label, strerror(errno));goto _error;}}pTrace("%s scheduler is initialized, numOfThreads:%d", pSched->label, pSched->numOfThreads);return (void *)pSched;_error:taosCleanUpScheduler(pSched);return NULL;
}

再来看读消息的taosProcessSchedQueue函数,这个主要逻辑是

1.使用无限循环,只要队列可读即sem_wait(&pSched->fullSem)不再阻塞就继续向下处理
2.在操作msg前,加入互斥体防止msg被误用。
3.读操作完毕后修改fullSlot的值,注意这为避免fullSlot溢出,需要对于queueSize取余。同时退出互斥体。
4.对emptySem进行post操作,即把emptySem的值加1,如emptySem原值为5,读取一个消息后,emptySem的值为6,即可写状态,且能接受的消息数量为6

具体代码及注释如下:

void *taosProcessSchedQueue(void *param) {SSchedMsg    msg;SSchedQueue *pSched = (SSchedQueue *)param;//注意这里是个无限循环,只要队列可读即sem_wait(&pSched->fullSem)不再阻塞就继续处理while (1) {if (sem_wait(&pSched->fullSem) != 0) {pError("wait %s fullSem failed, errno:%d, reason:%s", pSched->label, errno, strerror(errno));if (errno == EINTR) {/* sem_wait is interrupted by interrupt, ignore and continue */continue;}}//加入互斥体防止msg被误用。if (pthread_mutex_lock(&pSched->queueMutex) != 0)pError("lock %s queueMutex failed, reason:%s", pSched->label, strerror(errno));msg = pSched->queue[pSched->fullSlot];memset(pSched->queue + pSched->fullSlot, 0, sizeof(SSchedMsg));//读取完毕修改fullSlot的值,注意这为避免fullSlot溢出,需要对于queueSize取余。pSched->fullSlot = (pSched->fullSlot + 1) % pSched->queueSize;//读取完毕修改退出互斥体if (pthread_mutex_unlock(&pSched->queueMutex) != 0)pError("unlock %s queueMutex failed, reason:%s\n", pSched->label, strerror(errno));//读取完毕对emptySem进行post操作,即把emptySem的值加1,如emptySem原值为5,读取一个消息后,emptySem的值为6,即可写状态,且能接受的消息数量为6if (sem_post(&pSched->emptySem) != 0)pError("post %s emptySem failed, reason:%s\n", pSched->label, strerror(errno));if (msg.fp)(*(msg.fp))(&msg);else if (msg.tfp)(*(msg.tfp))(msg.ahandle, msg.thandle);}
}

最后来看写消息的taosScheduleTask函数,其基本逻辑是

1.写队列前先对emptySem进行减1操作,如emptySem原值为1,那么减1后为0,也就是队列已满,必须在读取消息后,即emptySem进行post操作后,队列才能进行可写状态。
 2.加入互斥体防止msg被误操作,写入完成后退出互斥体
3.写队列完成后对fullSem进行加1操作,如fullSem原值为0,那么加1后为1,也就是队列可读,咱们上面介绍的读取taosProcessSchedQueue中sem_wait(&pSched->fullSem)不再阻塞就继续向下。

int taosScheduleTask(void *qhandle, SSchedMsg *pMsg) {SSchedQueue *pSched = (SSchedQueue *)qhandle;if (pSched == NULL) {pError("sched is not ready, msg:%p is dropped", pMsg);return 0;}#在写队列前先对emptySem进行减1操作,如emptySem原值为1,那么减1后为0,也就是队列已满,必须在读取消息后,即emptySem进行post操作后,队列才能进行可写状态。if (sem_wait(&pSched->emptySem) != 0) pError("wait %s emptySem failed, reason:%s", pSched->label, strerror(errno));
#加入互斥体防止msg被误操作if (pthread_mutex_lock(&pSched->queueMutex) != 0)pError("lock %s queueMutex failed, reason:%s", pSched->label, strerror(errno));pSched->queue[pSched->emptySlot] = *pMsg;pSched->emptySlot = (pSched->emptySlot + 1) % pSched->queueSize;if (pthread_mutex_unlock(&pSched->queueMutex) != 0)pError("unlock %s queueMutex failed, reason:%s", pSched->label, strerror(errno));#在写队列前先对fullSem进行加1操作,如fullSem原值为0,那么加1后为1,也就是队列可读,咱们上面介绍的读取函数可以进行处理。if (sem_post(&pSched->fullSem) != 0) pError("post %s fullSem failed, reason:%s", pSched->label, strerror(errno));return 0;
}

当然以上只是TdEngine优美代码的一小部分,而且笔者解读的功力也十分有限,这里再次强烈建议大家下载全部源码仔细学习,定能受益匪浅。

这位创造Github冠军项目的老男人,堪称10倍程序员本尊相关推荐

  1. 这位创造GitHub冠军项目的“老男人”,堪称10倍程序员本尊

    作者 | 马超,CSDN博客专家,金融科技从业者 来源 | CSDN博客 7月12日一款叫做TDengine的时序数据库项目在GitHub上开源了,这个项目一经发布就稳稳占据了GitHub排行榜的C位 ...

  2. 这位 GitHub 冠军项目背后的“老男人”,堪称 10 倍程序员本尊!

    作者 | 马超,CSDN博客专家,金融科技从业者 来源 | CSDN博客 7月12日一款叫做TDengine的时序数据库项目在GitHub上开源了,这个项目一经发布就稳稳占据了GitHub排行榜的C位 ...

  3. GitHub 断供危机来了!权威解读程序员应对指南 | CSDN 独家

    作者 | 郭芮&伍杏玲 出品 | CSDN(ID:CSDNnews) GitHub 断供危机还是来了. 美国贸易限制的影响正在逐渐扩大,并且毫无意外地蔓延到了开发者社区.据多家外媒报道,世界上 ...

  4. GitHub断供危机来了!权威解读程序员应对指南

    作者 | 郭芮&伍杏玲 来源 | CSDN(ID:CSDNnews) GitHub 断供危机还是来了. 美国贸易限制的影响正在逐渐扩大,并且毫无意外地蔓延到了开发者社区.据多家外媒报道,世界上 ...

  5. 一位身价10亿程序员的传奇一生

    点击上方"Python大本营",选择"置顶公众号" python大本营  IT人的职业提升平台 他的前半生,值得我们每一个人深思. 在普通人眼里,他寂寂无名,只 ...

  6. GitHub 断供危机来了!权威解读程序员应对指南

    作者 | 郭芮&伍杏玲 出品 | CSDN(ID:CSDNnews) GitHub 断供危机还是来了. 美国贸易限制的影响正在逐渐扩大,并且毫无意外地蔓延到了开发者社区.据多家外媒报道,世界上 ...

  7. 李笑来python自学_李笑来登顶 GitHub TOP 榜!币圈大佬要教程序员如何自学编程

    原标题:李笑来登顶 GitHub TOP 榜!币圈大佬要教程序员如何自学编程 币圈红人李笑来,何许人也? --从英语老师.作家,到比特币首富.投资人,李笑来的身上有着众多广为人知的标签.因" ...

  8. 一位10年程序员生涯的总结与经验忠告分享

    展望未来,总结过去10年的程序员生涯,给程序员小弟弟小妹妹们的一些总结性忠告 走过的路,回忆起来是那么曲折,把自己的一些心得体会分享给程序员兄弟姐妹们,虽然时代在变化,但是很可能你也会走我已经做过的1 ...

  9. 200行代码为大家解读这个Github冠军项目背后的定时器

    最近几周笔者几篇有关陶建辉老师最新的创业项目-TdEngine代码解读文章(https://blog.csdn.net/BEYONDMA/article/details/97495298和https: ...

最新文章

  1. 临平职高计算机专业高职考大学,临平职高高考再传捷报 本科连续四年蝉联杭州市第一...
  2. 2018.3.6 11周1次课
  3. 简述RHEL7新特性(一)
  4. 2021年春季学期-信号与系统-第六次作业参考答案-第三小题
  5. yolov5转tensorrt c++
  6. 【企业管理】企业创造的要素
  7. 【机器视觉学习笔记】伽马变换(C++)
  8. 解决:Module not found: node_modules\sass-loader\package.json (directory description file)
  9. spring map使用annotation泛型注入问题分析
  10. Php循环函数嵌套javascript,JS循环嵌套问题?
  11. 关于封装的一个小问题和TA的例子
  12. conn (php)
  13. 跨浏览器resize事件分析
  14. JDBC13 ORM02 Map封装
  15. 卸载计算机flash,Flash Player卸载器(Flash Player Uninstaller)
  16. 小觅相机运行VINS-Fusion(三)——IMU的标定
  17. 人生若只如初见,何事秋风悲画扇
  18. OSError: [WinError 193] %1 不是有效的 Win32 应用程序(完整的解决方案)
  19. 索尼手机android怎么连,索尼WI-SP500耳机连接安卓手机蓝牙的方法讲解
  20. HDU4609 3-idiots(母函数 + FFT)

热门文章

  1. QQ授权,接收代码发送的电子邮件
  2. Prometheus+Grafana监控安装及使用
  3. 如果心酸就会心动也应行动
  4. 2021年美容师(高级)考试及美容师(高级)最新解析
  5. java dms_奥点云-DMS Java API 文档
  6. 苹果mac下载了dms文件怎么办?
  7. 携程 最短路径的代价
  8. Mybatis-Plus 新手入门,一篇足以
  9. 63. 请简述构造方法和析构方法的作用?
  10. android紫禁城一日游的代码,故宫旅游app下载-故宫旅游 安卓版v3.3.6-PC6安卓网