PostgreSQL 随读笔记 Connection的一些细节

psql/gsql连接数据库

Connection

pg:
postmaster的主函数一直在serverloop,当监听到一个会话(session)之后,会立马BackendStartup给他。
在这里插入图片描述
在BackendStartup中会起一个postgres线程(获得一个pid)服务这个session。

pid = fork_process();
内部: result = fork();

gs:
这里是有区别的,pg是来一个fork一个,gs自己维护了一个线程池,来一个先给这个session从线程池里找一个(这里有负载均衡的设置,占坑),但这个线程的生命周期不是和session一样长的,他和事务一样长(我的理解是完成了一个事务就会睡眠,但可以被别的session唤醒,如果被别的session唤醒,意味着需要重置一些资源)。

在这里插入图片描述
这里的 gs_thread_create

int gs_thread_create(gs_thread_t* th, void* (*taskRoutine)(void*), int argc, void* argv)

里面有一个void* argv参数其实也是ThreadArg*类型的,是从全局线程池(thread_args_pool)里分配出来的(gs_thread_get_args_slot),gs_thread_create内部让p_arg指向了他作为自身thread状态信息的入口:

ThreadArg* p_arg = NULL;
int error_code = 0;
bool need_free = false;

p_arg = (ThreadArg*)argv;  // 这里赋值 

深入一下这里的ThreadArg* p_arg :

typedef struct ThreadArg {
    void *(*m_taskRoutine)(void *); /* Task function caller passed in by the caller. */
    struct ThreadArg *next;         /* if next is INVALID_NEXT_ID, the ThreadArg is malloc when create a thread */
    knl_thread_arg m_thd_arg;
} ThreadArg;

中的 knl_thread_arg :

typedef struct knl_thread_arg {
    knl_thread_role role;
    char *save_para;
    void *payload;
    void *t_thrd;
    union {
        struct syslog_thread {
            int syslog_handle;
        } log_thread;
    } extra_payload;
} knl_thread_arg;

中的 t_thrd 就是线程级的,也就是事务级的,后面会经常用到,而这里t_thrd分配的空间是postmaster做的,线程池控制器管理的,在这里进行初始化。
实际创造线程:

pthread_create(&th->thid, &p_thread_attr, ThreadStarterFunc, p_arg);

ThreadStarterFunc会根据不同的thread role进行启动并调用对应的启动方法(都存在 p_arg 里面),即:

ThreadStarterFunc:
return (*t_thrd.port_cxt.m_pThreadArg->m_taskRoutine)(&(t_thrd.port_cxt.m_pThreadArg->m_thd_arg));

这里的m_taskRoutine是一个函数(前面传进来的:internal_thread_func),所以这个线程起起来之后,就开始运行internal_thread_func ,internal_thread_func 调用 get_thread_entry,即根据 role 找到对应的线程入口(GaussdbThreadGate[static_cast(role)].func中找到)并启动对应线程: gauss_db_thread_main 中分类 。

比如这里的辅助进程:

template <knl_thread_role thread_role>
int gauss_db_thread_main(knl_thread_arg* arg)
	-> gauss_db_auxiliary_thread_main<thread_role>(arg); // 下面接着 switch分类

在这里插入图片描述
而我们connection连接的的role是THREADPOOL_WORKER(线程池没开启就是worker),也就是执行sql:

template <knl_thread_role thread_role>
int gauss_db_thread_main(knl_thread_arg* arg)
	->backend_run(&port); 
	

注意这里的很多初始化,都是线程级的(t_thrd,u_sess):

mmgr_memprot_thread_init();
mmgr_memory_controller_init();
knl_thread_init(thread_role);  //初始化t_thd

mmgr_memory_controller_switch_to(MMGR_THREAD_GET_MEM_CTRL_GROUP(MEMORY_CONTEXT_DEFAULT));
MMGR_DISABLE_MEMORY_PROTECT();
t_thrd.fake_session = create_session_context(t_thrd.top_mem_cxt, 0);
MMGR_ENABLE_MEMORY_PROTECT();
t_thrd.fake_session->status = KNL_SESS_FAKE;
u_sess = t_thrd.fake_session;

ps:
t_thrd定义在

knl_thread.h
 *        Data stucture for thread level global variables.

u_sess定义在:

 * knl_session.h
 *        Data stucture for session level global variables.

初始化完成后正式启动服务:backend_run(&port) 拉起 PostgresMain

补充一些细节和遗留问题:

  1. 关于 ThreadPoolControler:
    这个内存是分配在存储引擎下的,也就是和整个存储引擎生命周期相同:
void ThreadPoolControler::init(bool enable_numa_distribute)
{
    m_thread_pool_context = mmgr_memory_set_controller_create(g_instance.instance_context,
        "ThreadPoolContext",
        MMGR_MEMORYSET_DEFAULT_MINSIZE,
        MMGR_MEMORYSET_DEFAULT_INITSIZE,
        MMGR_MEMORYSET_DEFAULT_MAXSIZE,
        SHARED_CONTEXT);
   		MmgrAutoControllerSwitch memSwitch(m_thread_pool_context);
   		这里分配内存在g_instance.instance_context中,下面初始化了一些变量,包括 m_sess_ctrl 等
   		  ....
 }

ThreadPoolControler 何时被分配:

server_loop中:

/* Init backend thread pool */
if (threadPoolActivated) {
    bool enable_numa_distribute = (g_instance.shmem_cxt.numaNodeNum > 1);
    g_threadPoolControler->init(enable_numa_distribute);
}

这里分配之后才会进入死循环,Wait for a connection request to arrive.

  1. 关于u_sess 和 t_thrd:

资源都保存在t_thrd中,由于一个t_thrd可以被切去服务别的session,一些公共系统资源,比如系统表的缓存可以放在t_thrd中,这样即使切去别的session也能继续使用(其实这里的逻辑)。即session连接断开或者异常退出,会清空u_sess和t_thrd.proc中的一些信息。
在这里插入图片描述

void ThreadPoolWorker::clean_thread() {
...
	struct ProcContext* thread_proc = t_thrd.proc;
    thread_proc->database_id = InvalidOid;
    thread_proc->role_id = InvalidOid;
    thread_proc->sessionid = t_thrd.fake_session->session_id;
    thread_proc->global_sessionid = t_thrd.fake_session->globalSessionId;
    thread_proc->working_version_num = pg_atomic_read_u32(&WorkingGrandVersionNum);
...
}
detach_session_from_thread:
u_sess = NULL;
  1. 简述一下 PostgresMain 流程

postgresMain是大部分 backend 的主循环。

开始做了一些初始化:

if (!IsUnderPostmaster) {
            (void)gs_signal_createtimer();

            (void)pqsignal(SIGALRM, SIG_IGN); /* ignored */
            (void)pqsignal(SIGPIPE, SIG_IGN); /* ignored */
            (void)pqsignal(SIGFPE, FloatExceptionHandler);
 }

这里 IsUnderPostmaster 是区别postmaster主线程还是普通backend线程,毕竟也得有个线程起起来(postmaster)去起别的线程。这里注册了一些信号,比如取消执行等(一种通信机制)。

还有一个比较重要的初始化:

proc_init_process();

这里主要是从g_instance.proc_base中获取一个唯一proc 结构体(get_proc_from_free_list()),并把这个entry写回g_instance结构体,注意这里有一个逻辑号pgprocno,是该proc在g_instance.proc_base的标识。
一堆的初始化完成之后,如果是线程池会这是ready:

    if (thrdpool_is_thread_pool_worker) {
        u_sess->proc_cxt.MyProcPort->sock = PGINVALID_SOCKET;
        t_thrd.threadpool_cxt.worker->notify_ready();
    }

然后进入服务循环(openGauss main processing loop begins here)。

中间有一段处理error堆栈的看不懂,占坑。

正常情况下,进入死循环处理non-error queries loop,这里2k行的代码。
在这里插入图片描述
里面分成7部分:
(1) If we’ve reached idle state, tell the frontend we’re ready for a new query.
这里有个很重要的wait_mission:
里面有个

while (true) {
clean_thread();
	-> detach_session_from_thread
再attach_session_to_thread
}

(2) Allow asynchronous signals to be executed immediately if they come in while we are waiting for client input. (This must be conditional since we don’t want, say, reads on behalf of COPY FROM STDIN doing the same thing.)

如果异步信号在我们等待客户端输入时出现,请允许立即执行。(这必须是有条件的,因为我们不希望,比如,代表COPY FROM STDIN进行读取。)暂时不深入

(3) read a command (loop blocks here)

  • 这里获得:firstchar = ReadCommand(&input_message); 根据这个执行不同种类的command

(4) disable async signal conditions again.

(5) check for any other interesting events that happened while we slept.

(6) process pooler reload before the next transaction begin.

(7) process the command. But ignore it if we’re skipping till Sync.
- 这里根据 firstchar 分类执行不同的 command,比如 ‘Q’ 就是 simple query 调用 exec_simple_query

一直循环处理任务,直到结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值