TDengine上关于Lua连接器开发的一些总结和思考
首先,TDengine符合我的审美。尽管我一直在关注时序数据库这个领域,但直到遇见TDengine才算找到符合预期的产品。理想中的产品所采用的语言应该自带运行时,属于强类型的编译语言,这样打包编译后对环境的依赖比较小,用起来比较方便;代码工程应该尽可能精巧,便于理解掌握;产品运行起来要快,资源消耗相对较小。如果一个产品像一辆卡车,载重10吨自重5吨,它显然不是理想选项。以上解释都是先射箭后画靶,当然C语言才是决定项,这是我在emacs环境中配置完善的语言。TDengine的每个Dnode节点既负责存储也负责计算,在时兴“计算与存储分离”的当下,这个技术方案显得有些另类。不过实践才是检验真理的标准,目前还没看到明显问题,我们会在K8s的CSI方案中继续测试。我为TDengine编写了Lua版的连接器,主要面向两个用户群体,一是OpenResty(Nginx+Lua),另一个是Skynet。这两个产品也是我格外欣赏的两款开源产品。经过测试,在我的个人笔记本电脑(SSD硬盘、8G RAM)上,基于TDengine 2.0.18,用我本人编写的Lua连接器单线程写入只有3个列的记录(时间戳、整数、tag),平均每秒可写入1万条,见下图。我已提交了benchmark测试代码,大家可以在自己的工作环境下测试感兴趣的条目。目前社区里看到的Lua连接器分别基于Lua5.1和Lua5.3,详见https://github.com/taosdata/TDengine/tree/develop/tests/examples/lua,这主要是因为OpenResty采用的是基于5.1版本的LuaJIT,而skynet虽然跟随Lua社区升级到了Lua5.4,但我并没用遇到兼容问题,所以只在本地针对Lua5.4的兼容性进行了验证,并没有提交。从Lua角度来看,Lua5.1和Lua5.2及以上版本属于两个世界。这主要是因为Lua5.2版本上的一个重要改进——“yieldable pcall and metamethods”。Lua5.2这个改进允许调用C函数时不马上返回,而这是异步操作必须的特性。所以大家在基于Lua5.1API的OpenResty社区里经常看到有人在问如何解决“attempt to yield across C-call boundary”问题。我在实现连接器时也必须面对这个问题,当然也有其他API差异,所以编写了两个版本的代码,而不是用编译开关控制。事实上,有别于OpenResty过度依赖LuaJIT,我更赞同云风的观点,跟随主流社区升级版本带来的综合收益应该高于在某个版本上定制。OpenResty用户可以直接在http请求处理中通过Lua连接器访问TDengine数据库,如同使用MySQL的体验一样,不需要交给服务器处理,整体架构非常简洁。由于在此Lua版本上只能实现同步访问数据库,出于性能考虑,我试验了一个连接池,避免频繁地建立然后释放数据库连接。很遗憾的是,Lua的非抢占特性导致一段代码未执行完时不会释放CPU,所以并没有机会处理其他请求,观察到的WaterMark也一直是1,这个问题如何解决,暂时还没有结论。如果在目前基础上想尽可能地多榨出一点性能来,我的建议是尽可能推迟从连接池里申请连接,并尽可能提前归还连接。
Skynet用户也可以仿照MySQL的使用方式来使用Lua连接器,不过我建议遵从Skynet的建议,在simpledb这样的服务中保持连接,做具体的请求处理工作。因为Skynet本身实现了比较完善的Actor模型,所以我并不确定目前的方案有没有带来瓶颈。如果同步访问带来瓶颈,可以尝试一下异步调用,但因为TDengine关于异步的设计中要求前一个访问结果返回后才能执行下一个访问,所以异步调用带来的瓶颈会向哪里转移,目前也不清楚。尽管可能性很小,但是连接器不能回避数据库连接失效的问题。幸好TDengine提供了一个心跳机制,用来检测连接是否有效。目前实现的连接器还没有完善这个功能,所以如果通往数据库的链路失效,需要应用重建连接。
在实际生产环境中,我们发现TDengine Dnode向Client端返回的字符串类型的数据并没有附加结尾符,而出于性能考虑,在向连接器返回数据时也没有重新申请内存做一次拷贝以追加结束符,进而导致往数据库中存入网络类型4G和WIFI,在查询时返回“4GFI”这样的结果。这是一个隐蔽性很强的默认规则,其他语言连接器的设计者注意提前规避潜在问题。自定义函数(UDF)能降低应用的复杂度,或者实现预置查询函数无法实现的功能,我判断Lua是适合承担这个使命的语言。因为Lua的设计初衷就是与C语言集成,两者堪称倚天屠龙。目前TDengine官方已经实现了用户自定义函数的框架,基于Lua5.1实现了基础模型,并集成了Lua5.1。因为Lua社区的分裂状态,预置的Lua开发库给我的开发工作带来一些小麻烦,用户肯定不能同时使用两个Lua版本,终还是要在TDengine中同时集成Lua5.1和一个高版本(即将发布版本为Lua5.4.4),靠宏开关选择一个Lua版本参与编译。具体实现起来,需要分别为两个版本的C API设计接口,Lua每个版本升级都会带来一些功能上的变化和升级,因此能否抽象出一套通用接口对Lua用户屏蔽差异,对于这个问题的答案我是持比较悲观的态度的。
以上就是我在使用TDengine时的经验汇总。目前,连接器已部署在我们的生产环境上,并经历了两次较大规模的生产活动,轻松完成了使命。接下来会在项目应用中继续完善上面提到的问题,支持用Lua实现UDF是我的下一个工作重点,这将进一步降低应用的复杂度。
来源 https://www.modb.pro/db/324395