暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

openGauss 多线程架构

原创 小小亮 2021-12-21
1858

openGauss内核源自PostgreSQL,但在架构上进行了大量改造,其中一个调整就是将多进程架构修改为多线程架构。openGauss在启动后只有一个进程后台任务都是以一个进程中的线程来运行。对于客户端的新连接,在非线程池模式下也是以启动一个业务线程来处理。在多线程架构下更容易实现多个线程资源的共享,如并行查询、线程池等。


一、openGauss主要线程

openGauss的后台线程是不对等的,其中Postmaster是主线程,其他线程都是它创建出来的。openGauss后台线程的功能介绍如表所示

后台线程的功能

后台线程

功能介绍

Postmaster

openGauss数据库主线程。主要有两个功能:一是对连接进行监听,接收新的连接;二是监控所有子线程的状态,并且根据子线程退出状态进行处理,如果线程是FATAL退出则重新拉起子线程如果线程是PANIC退出则进行整个数据库重新初始化。保证数据库的正常运行

Startup

数据库启动线程。数据库启动时Postmaster主线程拉起的第一个子线程,主要完成数据库的日志REDO(重做)操作,进行数据库的恢复。日志REDO操作结束,数据库完成恢复后,如果不是备机,Startup线程就退出了。如果是备机,那么Startup线程一直在运行REDO备机接收到新的日志

Bgwriter

后台数据写线程。周期性的把数据库数据缓冲区的内容同步到磁盘上

Checkpointer

检查点线程。进行检查点操作,完成数据库的周期性检查点和执行检查点命令

Walwriter

后台WAL写线程。主要功能是周期性的把日志缓冲区的内容同步到磁盘上

Stat

数据库运行信息统计收集线程。主要功能是收集各个线程操作数据库数据的统计信息,进行汇总后写入数据库的统计文件中,供查询优化分析和垃圾清理使用

Sysloger

运行日志写线程。主要功能是把各个线程的运行日志信息写到运行日志文件中

Vacuum Launch

垃圾清理启动线程。主要有两个功能:一是通知Postmaster 启动一个垃圾清理线程;二是平衡各个垃圾清理线程的负载

Vacuum worker

垃圾清理线程。主要功能是对openGauss数据库的垃圾数据进行清理

Arch

日志归档线程。主要功能是完成归档操作,把在线日志拷贝到归档目录

Postgres

服务线程。在非线程池模式下,每个客户端连接对应一个服务线程,主要功能是接收客户端的操作请求,代表客户端在服务器完成数据库操作

 

二、线程间通信

openGauss后台线程之间紧密配合,共同完成了数据库的数据处理任务。这些后台线程之间需要交换信息来协调彼此的行为。openGauss多线程通信使用了原来的PostgreSQL的多进程通信方式。具体如表所示

多线程通信方式

通信方式

说明

共享内存

在数据库初始化时,Postmaster线程通过OS(操作系统)申请一块大的共享内存,并且完成初始化工作。openGauss使用到的所有共享内存都是这块内存的一部分。线程之间的一些信息交换就是通过共享内存完成的,共享内存的访问需要加锁保护

信号

对于一些紧急任务的处理,openGauss使用信号通知作为线程间通信的手段。因为信号可以中断处理线程当前的任务,立即响应信号对应的任务

TCP

客户端连接数据库服务器时,一般使用TCP进行通信

UNIX域套接字协议

如果是本地客户端,即客户端和服务器在同一个机器上,并且是UNIX操作系统,可以使用UNIX域套结字协议建立客户端和服务器进程的通信

UDP

UDPuser datagram protocol,用户数据报协议)是不可靠协议,主要用于后台线程向统计线程发送统计信息时使用

管道

管道可以是双向的,也可以是单向的。在openGauss中,主要使用了单向管道,用在后台线程向运行日志守护线程发送运行日志信息时使用

文件

主要用于一些不太重要的场合,并且通信量比较大。在openGauss中,主要用在统计线程汇总统计信息,写到统计文件,供垃圾清理线程和后台服务器线程成本优化使用

全局变量

一种线程间共享信息的机制openGauss对原来的PostgreSQL中进程内的全局变量添加THR_LOCAL定义为线程的局部变量避免线程之间误用

 

三、线程初始化流程

下面介绍线程的初始化流程。首先介绍openGauss进程的启动openGauss进程的主函数入口在\openGauss-server\src\gausskernel\process\main\main.cpp”文件main.cpp文件中主要完成实例Context上下文的初始化本地化设置根据main.cpp文件的入口参数调用BootStrapProcessMain函数、GucInfoMain函数、PostgresMain函数PostmasterMain函数。BootStrapProcessMain函数PostgresMain函数是在initdb场景下初始化数据库使用的GucInfoMain函数作用是显示GUCgrand unified configuration,大统一配置,在数据库中指的是运行参数)参数信息正常的数据库启动进入PostmasterMain函数。下面对这个函数进行更详细的介绍。

1) 进行PostmasterContext初始化初始化GUC参数解析命令行参数
2调用StreamServerPort函数启动服务器监听和双机监听(如果配置了双机),调用reset_shared函数初始化共享内存和LWLock,调用gs_signal_monitor_startup函数注册信号处理线程,调用InitPostmasterDeathWatchHandle函数注册Postmaster死亡监控管道openGauss进程信息写入pid_file文件中调用gspqsignal函数注册Postmaster的信号处理函数
3) 根据配置初始化黑匣子,调用pgstat_init函数初始化统计信息传递使用的UDP套接字通信调用InitializeWorkloadManager函数初始化负载管理器调用InitUniqueSQL函数初始化UniqueSQL,调用SysLogger_Start函数初始化运行日志的通信管道和SYSLOGGER线程调用load_hba函数加载hba鉴权文件
4调用initialize_util_thread函数启动STARTUP线程调用ServerLoop函数进入一个周期循环ServerLoop函数的周期循环中进行客户端请求监听,如果有客户端连接请求,在非线程池模式下,则调用BackendStartup函数创建一个后台线程worker处理客户请求。在线程池模式下,把新的链接加入一个线程池组中。ServerLoop函数的周期循环中,检查其他线程的运行状态。如果数据库是第一次启动,则调用initialize_util_thread函数启动其他后台线程如果有后台线程FATAL级别错误退出则调用initialize_util_thread函数重新启动该线程。如果是PANIC级别错误退出则整个实例进行重新初始化

PostmasterMain完成了线程之间的通信初始化和线程的启动,无论是后台线程的启动函数initialize_util_thread还是工作线程的启动函数initialize_worker_thread,最后都是调用initialize_thread函数完成线程的启动下面进行initialize_thread函数的介绍

initialize_thread函数调用gs_thread_create函数创建线程,调用InternalThreadFunc函数处理线程。它的相关代码如下所示

ThreadId initialize_thread(ThreadArg* thr_argv)
{
    gs_thread_t thread;
    if (0 != gs_thread_create(&thread, InternalThreadFunc, 1, (void*)thr_argv)) {
        gs_thread_release_args_slot(thr_argv);
        return InvalidTid;
    }
    return gs_thread_id(thread);
}


InternalThreadFunc函数的代码如下该函数根据角色调用GetThreadEntry函数,GetThreadEntry函数直接以角色为下标返回对应GaussdbThreadEntryGate数组对应的元素。数组的元素是处理具体任务的回调函数指针,指针指向的函数为GaussDbThreadMain。相关代码如下所示

static void* InternalThreadFunc(void* args)
{
    knl_thread_arg* thr_argv = (knl_thread_arg*)args;
    gs_thread_exit((GetThreadEntry(thr_argv->role))(thr_argv));
    return (void*)NULL;
}
GaussdbThreadEntry GetThreadEntry(knl_thread_role role)
{
    Assert(role > MASTER && role < THREAD_ENTRY_BOUND);
    return GaussdbThreadEntryGate[role];
}
static GaussdbThreadEntry GaussdbThreadEntryGate[] = {GaussDbThreadMain<MASTER>,
    GaussDbThreadMain<WORKER>,
    GaussDbThreadMain<THREADPOOL_WORKER>,
    GaussDbThreadMain<THREADPOOL_LISTENER>,
  ......};


GaussDbThreadMain函数首先初始化线程基本信息Context和信号处理函数,接着就是根据thread_role角色的不同调用不同角色的处理函数,进入各个线程的main函数比如GaussDbAuxiliaryThreadMain函数、AutoVacLauncherMain函数、WLMProcessThreadMain函数等其中GaussDbAuxiliaryThreadMain函数是后台辅助线程处理函数。该函数的处理也类似GaussDbThreadMain函数,根据thread_role角色的不同调用不同角色的处理函数进入各个线程的main函数比如StartupProcessMain函数、CheckpointerMain函数、WalWriterMain函数、walrcvWriterMain函数

总结上面整个过程openGauss多线程架构主要包括3个方面

1多线程之间的通信由主线程在初始化阶段完成
2多线程的启动由主线程创建各个角色线程调用不同角色的处理函数完成
3) 主线程负责监控各个线程的运行,异常退出和重新拉起。
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论