今天咱们对postmaster的如下细节进行讨论:html
backend的启动和client的链接请求的认证 客户端取消查询时的处理 接受pg_ctl的shutdown请求进行shutdown处理
##2.与前端的交互 ###2.1backend的启动和client的链接请求的认证 关于backend的启动,其函数调用栈以下:前端
PostmasterMain() |->ServerLoop() |->initMasks() |->for(;;) |->select() <--监听端口 |->ConnCreate() <--建立connection相关的数据结构 |->BackendStartup() <--创建后端进程backend process |->PostmasterRandom() |->canAcceptConnections() |->fork_process() |->InitPostmasterChild() |->ClosePostmasterPorts() |->BackendInitialize() |->ProcessStartupPacket() |->BackendRun() |->PostgresMain() |->ConnFree() <--释放connection相关的数据结构
简单来讲,在系统调用select()中咱们监听客户端的链接请求,当读到一个客户端请求时咱们将为其建立相关数据结构,作一下初始化。注意此时只是监听接受了请求,这个请求是否合法(例如password是否正确)在此时是不作判断的。判断是放在BackendStartup()中的。node
你可能会疑惑:BackendStartup()用来建立backend,可是建立了backend以后才作验证是否是有点晚?sql
咱们来看BackendStartup()的处理(具体的你们看上面的调用栈和source)。数据库
typedef struct bkend { pid_t pid; /* process id of backend */ long cancel_key; /* cancel key for cancels for this backend */ int child_slot; /* PMChildSlot for this backend, if any */ /* * Flavor of backend or auxiliary process. Note that BACKEND_TYPE_WALSND * backends initially announce themselves as BACKEND_TYPE_NORMAL, so if * bkend_type is normal, you should check for a recent transition. */ int bkend_type; bool dead_end; /* is it going to send an error and quit? */ bool bgworker_notify; /* gets bgworker start/stop notifications */ dlist_node elem; /* list link in BackendList */ } Backend;
(backend的数据结构比较重要,我先引用在这里)后端
首先,程序会调用PostmasterRandom()函数产生一个cancelkey。这个cancelkey是作什么的呢?它是用来标记前端发来的cancel指令的:即前端发来一个停止当前SQL文的操做的指令时(好比你按Crtl+C), postmaster会先经过backendPID先在后端的backendList中找到对应的backend,再利用这个cancelkey来和这个backend作验证(这个在下一节会提到)。服务器
而后调用canAcceptConnections来判断当前postmaster的状态是否能够接受链接?读者又疑惑了:前面不是已经接受链接了么?前面的select只是调用select()系统调用获取了这个链接请求(甚至连链接请求也算不上,只是收到了一个Client发来的packet,多是startup_packet,也多是cancel_request_packet),至因而不是能接受链接,咱们有两个判断:数据结构
若是不知足条件。咱们把将要启动的backend标记为dead_end。就是说这个后端只是用来向前端报错用的,报错以后当即退出。因此咱们就不给它分配Slot了。判断能够链接了以后,咱们就给它分配好slot。dom
继续往下走。调用fork_process()启动一个进程(固然就是用来做为backend的了)。backend启动起来了以后,咱们就能够脱离postmaster,把后面的一切交给backend本身处理了。随之而来的InitPostmasterChild()就是用来初始化backend进程,将环境句柄从postmaster切换到backend。而后调用ClosePostmasterPorts()关闭此时不须要的文件描述符。ide
而后调用BackendInitialize作进一步的初始化。这里咱们比较感兴趣的可能就是它调用了ProcessStartupPacket()获取前端发送的StartupPacket并为之分配内存,作一些简单的判断处理。验证部分放在了后面。
最后,咱们调用BackendRun()真正的运行这个backend。 从代码咱们可知,BackendRun()函数也只是一个壳子,他只是切换了内存上下文到TopMemoryContext而且获取postmaster的命令行上 -o参数指定的一些参数,而后将这些参数传给了PostgresMain()函数到这里,咱们看出PostgresMain()函PostmasterMain()很像。都是命令的入口。并且后面咱们看到PostgresMain()函数里面也有一个Loop。就是循读取客户端发来的SQL文。
InitPostgres()函数是PostgresMain()调用的一个很是重要的初始化函数,天然它的做用是作初始化。作哪些初始化呢?
我列举一些:
InitProcessPhase2() Add my PGPROC struct to the ProcArray SharedInvalBackendInit() shared cache invalidation communication(inval) ProcSignalInit() Register the current process in the procsignal array RegisterTimeout() Register timeout RelationCacheInitialize() Initialize relationcache InitCatalogCache() Initialize catalog cache InitPlanCache() Initialize callbacks for inval EnablePortalManager() Portals are objects representing the execution state of a query, This module provides memory management services for portals InitializeClientEncoding() initialize client encoding
关于PostgresMain()其它的我很少说,和PostmasterMain()很像,只不过处理的全部对象都是针对banckend的,具体看代码吧。
到这里咱们能够回答为啥要建立了backend以后才作验证:
postmaster只充当一个中介的角色,不过多地涉及共享内存和其余会引发错误的操做,使得postmaster主程序更健壮和稳定。同时若是在ServerLoop里面花时间作验证我以为也太费时间了。
哦,忘了,咱们还没说client的链接请求的认证。下面是函数调用栈:
BackendRun() |->PostgresMain() |->InitPostgres() |->PerformAuthentication() |->ClientAuthentication()
PerformAuthentication()在InitPostgres中是在EnablePortalManager()以后调用的,这个时候大部分backend进程自己的初始化工做都已完毕。
这里作Client验证的入口是ClientAuthentication()了。它的主要工做以下:
hba_getauthmethod() //获取hba文件中和该条请求匹配的auth method | v switch(auth_method)//根据auth_method和请求信息作出相应的处理 | v status == STATUS_OK ? sendAuthRequest() : auth_failed() //根据返回值决定向client发送验证packet仍是拒绝请求
须要说明的是当验证失败,拒绝client的请求后,程序在这里就报错退出了。这样,这个backend就是一个dead_end的backend,他会在postmaster的指挥下退出,具体细节见后面几节内容。
###2.2客户端取消查询时的中介
当咱们在client(例如psql命令行)中运行一个很长的SQL查询(并非说必定要很长的查询,只是若是时间过短的话你根原本不及cancel~)时,此时因为各类缘由你想停止这条查询,因而你按下了Crtl+C键。当即在客户端上显示:
postgres=# select * from test order by id asc ; Cancel request sent ERROR: canceling statement due to user request
那咱们来看一看postgres是如何处理这样的cancel吧。
先上图:
对应上图,咱们针对涉及的进程分别列出函数调用栈: client: psql命令在初始化的时候调用setup_cancel_handler()在psql的MainLoop以前注册了一个信号处理函数,在收到client的SIGINT(也就是你按下Ctrl+C)后,调用handle_sigint()处理这个信号。处理成功后,打印:
Cancel request sent
(src/bin/psql/startup.c) main() |->setup_cancel_handler() |->pqsignal(SIGINT, handle_sigint) |->successResult = MainLoop(stdin)
postmaster:processCancelRequest: postmaster在接收到client发来的packet后,创建一个后端进程(backend)去处理它,当发现它是一个cancel_request_packet后,调用processCancelRequest()函数处理这个packet,经过PID向对应的backend发送SIGINT信号。
PostmasterMain() |->ServerLoop() |->for(;;) |->BackendStartup() <--创建后端进程backend process |->BackendInitialize() |->ProcessStartupPacket() |->processCancelRequest() |->signal_child(bp->pid, SIGINT)
backend:pqsignal(SIGINT, StatementCancelHandler): postgresMain()函数上注册下面这个信号处理函数,它接受postmaster发来的SIGINT信号,进行对应的处理,设置两个全局变量:
pqsignal(SIGINT, StatementCancelHandler) { ... InterruptPending = true; QueryCancelPending = true; ... }
而这两个全局变量又决定了CHECK_FOR_INTERRUPTS()是否生效:
#define CHECK_FOR_INTERRUPTS() \ do { \ if (InterruptPending) \ ProcessInterrupts(); \ } while(0)
咱们进ProcessInterrupts()函数,发现他就是用来处理client的停止请求的:
ProcessInterrupts(){ ... InterruptPending = false; ... { LockErrorCleanup(); ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling statement due to user request"))); } ... }
报错消息就是咱们上面所见的那条了。
###2.3接受pg_ctl的shutdown请求
咱们常常会使用pg_ctl 来控制postgres服务器,好比start,stop和reload等等。start参数就是对应着服务器的启动,这个在Postgres中postmaster代码解析(上)中咱们已经讨论过。这里咱们来讨论下指定stop参数的处理。
在开始讨论以前,咱们先看下PMState这个枚举类型。
typedef enum { PM_INIT, /* postmaster starting */ PM_STARTUP, /* waiting for startup subprocess */ PM_RECOVERY, /* in archive recovery mode */ PM_HOT_STANDBY, /* in hot standby mode */ PM_RUN, /* normal "database is alive" state */ PM_WAIT_BACKUP, /* waiting for online backup mode to end */ PM_WAIT_READONLY, /* waiting for read only backends to exit */ PM_WAIT_BACKENDS, /* waiting for live backends to exit */ PM_SHUTDOWN, /* waiting for checkpointer to do shutdown * ckpt */ PM_SHUTDOWN_2, /* waiting for archiver and walsenders to * finish */ PM_WAIT_DEAD_END, /* waiting for dead_end children to exit */ PM_NO_CHILDREN /* all important children have exited */ } PMState;
这个枚举类型标注的是数据库当前的状态。其中PM_RUN是一个分水岭。从PM_INIT到PM_RUN,数据库逐渐从初始化状态转换为正常的运行状态。而从PM_RUN到PM_NO_CHILDREN,数据库逐渐由正常运行状态转换到能够关闭的状态。理解了这个有助于咱们理解数据库的启动和关闭的时序。上面每一个状态后面的注释已经能很好地解释每一个状态间的转换条件了,我这里不赘述了。
对于pg_ctl的stop参数,咱们有三种模式:
模式 | 发送的signal | signal的处理 |
---|---|---|
smart | SIGTERM | Wait for children to end their work, then shut down |
fast | SIGINT | Abort all children with SIGTERM (rollback active transactions and exit) and shut down when they are gone |
immediate | SIGQUIT | abort all children with SIGQUIT, wait for them to exit, terminate remaining ones with SIGKILL, then exit without attempt to properly shut down the database system. |
这里咱们就先以smart模式展开讨论,其余的模式其实也是相似的。
首先执行"pg_ctl stop -m smart",这个时候其实就是向postmaster发送了一个SIGTERM信号;
postmaster收到SIGTERM信号,触发pqsignal(SIGTERM, pmdie),调用pmdie()函数去处理SIGTERM信号;
pmdie |->SignalSomeChildren(SIGTERM,BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) 向autovacuum和bgworker子进程转发SIGTERM信号 |->PostmasterStateMachine() 更新数据库的状态PM_State
pmdie中的处理如上所示。pmdie调用SignalSomeChildren()向指定的进程发送SIGTERM信号,一样这些进程自己也有信号处理函数,在接收到postmaster的SIGTERM信号进行相关处理并终止。(子进程终止后会向父进程发送一个SIGCHLD信号,这是操做系统的固有处理)。PostmasterStateMachine()是一个工具函数,在postmaster不少的信号处理函数中都会调用该函数来根据数据库当前的PM_State和相关进程的死活来更新PM_State。
这个时候咱们看backend进程:
backend:pqsignal(SIGTERM, die); //die
backend进程自己的信号处理函数在收到SIGTERM信号后调用die函数进程exit处理。
话题再回到postmaster,当它收到子进程的SIGCHLD信号时,触发pqsignal(SIGCHLD, reaper),会调用reaper()函数处理子进程发来的SIGCHLD信号:
reaper() |->switch(PID) 根据PID类型判断子进程类型,分别进行处理 |->PostmasterStateMachine() 更新数据库的状态PM_State
这样postmaster会一直收到子进程的SIGCHLD信号,并进行相应处理后更新PM_State。
那何时肯定全部的子进程都结束了呢?仍是看PostmasterStateMachine()函数:
PostmasterStateMachine() 当最后一个backend(dead_end)结束时,reaper处理子进程经过调用PostmasterStateMachine更新当前状态, 将当前状态由PM_WAIT_DEAD_END转换为PM_NO_CHILDREN时: PM_WAIT_DEAD_END -> pmState = PM_NO_CHILDREN 说明说有子进程都已退出,postmaster调用ExitPostmaster结束自身: ExitPostmaster()
本节讨论就是这样,下次准备讨论:
后端process的管理 DB的shoutdown的处理 backend异常结束时的处理 BootstrapMain()的处理
先把flag立下来,省得本身忘了。欢迎你们点赞~