GaussDB(DWS)通信库libpq重构介绍(一)
通信库libpq重构解决两点问题:
- 针对大并发短查询场景下,CN与DN建立/复用/归还/释放连接时争抢全局锁PoolerLock,造成的性能问题进行Pooler锁优化;
- 针对当前代码仓中libpq代码冗余度高、复用性差、可维护性差、可读性差问题,对libpq代码进行整合以符合clean code要求。
在现网多个局点,在高并发短查询场景下,CN与DN建连时等待PoolerLock锁耗时,导致环境CPU资源无法进一步使用,严重影响此场景下查询性能。Poller锁优化旨在高并发短查询场景下优化CN与DN建连性能,pooler建连不再制约环境CPU资源使用,全面提升高并发短查询性能。
libpq代码合并旨在提高libpq代码的复用性、可读性、可维护性,符合clean code要求。
一、pooler锁优化
Pooler连接总体分两大类:一类空闲连接,存储在某个DBPool的NodePool中;一类是使用中的连接,存储在每个线程独立的poolAgent数据结构中,因为遍历需求,所有线程的poolAgent指针又存储在全局的poolAgents数组中。
Pooler锁优化总体方案是使用无锁ringbuffer存储空闲连接和可用的agent index,实现并发空闲连接的存取不需加锁,并发agent index的存取不需加锁。
针对全局的DBPool链表和NodePool Hash表,仅在新增时加互斥锁,正常查找时加共享锁。
针对CleanConnection、pg_pooler_status、pv_total_memory_detail、pgxc_pool_reload、pg_pool_validate需遍历NodePool中所有空闲连接的场景,使用ringbuffer的pop接口将连接取到本线程,操作完后再push回ringbuffer,实现无锁遍历。
针对以上视图接口需遍历poolAgents数组中所有正在使用连接的场景,在Agents数组中增加status标志位,做短暂的读保护。
二、libpq代码合并
libpq代码从功能角度可以划分为3部分:
- 基础公共模块
- 通信建连、断连
- 应用层定义的通信协议
除去通信协议部分,基础公共模块、通信建连及断连模块的代码实现大体相同,完全可以合并成1份,以动态库的形式提供给其他模块使用,对外只提供必要的通信建连、断连等接口,隐藏内部具体实现。
首先梳理并确定3个模块公用的建连、断连流程,如图 3‑1、图 3‑2所示。在此基础上,确定建连、断连流程的内部接口与外部接口、使用的数据结构,外部接口声明、结构体前置声明位于libpq-fe.h头文件中,内部接口声明、结构体定义位于libpq-int.h头文件中。其他模块使用libpq时,只需要libpq-fe.h头文件与libpq.so,不需要关注libpq内部实现。
合并过程中,对于相似的代码,提炼出公共函数,提高代码的复用性、可读性;对于差异较大的代码,采用注册回调函数的方式(startupPacket构建、result成员内存释放),保留差异逻辑。
合并完成后,3个模块各自目录下的冗余代码或者冗余文件都被删除。合并以后的代码实现存放在interfaces/libpq目录下,头文件存放在include/libpq目录下。
图 2‑1 公用建连流程
图 2‑2 公用断连流程
图 2‑3 libpq合并文件结构
libpq重构的详细设计,将在后面具体介绍。
想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料~
- 点赞
- 收藏
- 关注作者
评论(0)