Postgres中postmaster代码解析(中)
今天我们对postmaster的以下细节进行讨论:
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()中的。
你可能会疑惑:BackendStartup()用来创建backend,但是创建了backend之后才做验证是不是有点晚?
我们来看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),至于是不是能接受连接,我们有两个判断:
- 当前数据库是不是处于可以接受连接的状态?(startup/shutdown/inconsistent recovery状态不可接受连接);
- 当前数据连接数是不是已经满了(超过MaxConnections)
如果不满足条件。我们把将要启动的backend标记为dead_end。就是说这个后端只是用来向前端报错用的,报错之后立即退出。所以我们就不给它分配Slot了。判断可以连接了之后,我们就给它分配好slot。
继续往下走。调用fork_process()启动一个进程(当然就是用来作为backend的了)。backend启动起来了之后,我们就可以脱离postmaster,把后面的一切交给backend自己处理了。随之而来的InitPostmasterChild()就是用来初始化backend进程,将环境句柄从postmaster切换到backend。然后调用ClosePostmasterPorts()关闭此时不需要的文件描述符。
然后调用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立下来,免得自己忘了。欢迎大家点赞~
Postgres中postmaster代码解析(中)的更多相关文章
- Postgres中postmaster代码解析(上)
之前我的一些文章都是在说Postgres的一些查询相关的代码.但是对于Postgres服务端是如何启动,后台进程是如何加载,服务端在哪里以及如何监听客户端的连接都没有一个清晰的逻辑.那么今天我来说说P ...
- 捕捉WPF应用程序中XAML代码解析异常
原文:捕捉WPF应用程序中XAML代码解析异常 由于WPF应用程序中XAML代码在很多时候是运行时加载处理的.比如DynamicResource,但是在编译或者运行的过程中,编写的XAML代码很可能有 ...
- Winform 中写代码布局中遇到的控件遮盖问题
在winform中编程时,需要动态向主窗体中增加菜单栏和用户控件,菜单栏需要设置DockStyle为Top ,而设置用户控件的DockStyle为Fill,之后在加载的时候,出现了菜单栏遮盖用户控件的 ...
- 关于怎么在CSDN中修改代码行中字体的颜色
先吐槽一下自己的心路历程吧,自己现在也是在CSDN中发表了自己好几篇的原创博文,但每一篇博文自己总感觉怪怪的,就是说不出自己哪里有毛病呢,知道今天恍然大悟,原来自己的代码行真心丑的要死,没有呈现出在编 ...
- 如何在“代码”视图中工作并充分利用 Dreamweaver 的编码功能。如 Emmet 缩写
可通过多种方式在 Dreamweaver 中处理代码. 您可以使用“新建文档”对话框打开新的代码文件,然后开始键入您的代码. 在 Dreamweaver 中创建新的代码文件 键入时,会显示代码提示以帮 ...
- Python中sort和sorted函数代码解析
Python中sort和sorted函数代码解析 本文研究的主要是Python中sort和sorted函数的相关内容,具体如下. 一.sort函数 sort函数是序列的内部函数 函数原型: L.sor ...
- javaScript中的小细节-script标签中的预解析
首先介绍预解析,虽然预解析字面意思很好理解,但是却是出坑出的最多的地方,也是bug经常会有的地方,利用好预解析的特性可以解决很多问题,并且提高代码的质量及数量,浏览器在解析代码前会把变量的声明和函数( ...
- java中采用dom4j解析xml文件
一.前言 在最近的开发中用到了dom4j来解析xml文件,以前听说过来解析xml文件的几种标准方式:但是从来的没有应用过来,所以可以在google中搜索dmo4j解析xml文件的方式,学习一下dom4 ...
- 转:在java中使用dom4j解析xml
JAVA 使用Dom4j 解析XML Java DOM4J Parser - Parse XML Document Dom4j下载及使用Dom4j读写XML简介 在java中使用dom4j解析xml ...
随机推荐
- Struts2之Struts2的下载与安装
Struts2的下载 登陆struts的官网 下载Full Distribution这个选项的struts2的包. 这是Struts2的完整版,里面包括Struts2的实例应用,空实例应用,核心库,源 ...
- 通过URL传递PDF名称参数显示PDF
1 <%@ page language="java" import="java.util.*,java.io.*" 2 pageEncoding=&quo ...
- react-native-image-picker 运用launchCamera直接调取摄像头的缺陷及修复
在前几天用react-native进行android版本开发当中,用到了"react-native-image-picker"的插件:根据业务的需求:点击按钮-->直接调取摄 ...
- 快速搭建fabric-v1.1.0的chaincode开发环境
本文参考了fabric官方文档:http://hyperledger-fabric.readthedocs.io/en/latest/peer-chaincode-devmode.html?highl ...
- 【原创】公司各个阶段 CTO 需要做什么?(上篇)
CTO 是企业内技术最高负责人,对企业的发展起到至关重要的作用.但随着公司的不断发展,CTO 的工作重心也会不断变化.只有在正确的阶段做正确的事,才能更好地为公司做出贡献.我是空中金融 CTO ,TG ...
- ELK学习总结(2-3)Mget获取多个文档
mget 获取多个文档 1.curl 命令格式:mget获取多个文档: curl 'localhost:9200/_mget' -d '{ "docs":[ { " ...
- 大数据学习总结(6)what is our foucus
1.搜索业务 2.画像业务 3.关系图谱 借助es构建搜索.画像和关系图谱
- mysql自带的example测试数据库导入Centos6.5
1.下载数据库 下载地址: [test数据库] (https://github.com/datacharmer/test_db) 不出意外的话,下载下来是个unzip文件. 2.上传到数据库服务器 r ...
- Oracle:如何使用PL/SQL 11.0连接远程Oracle12c服务器?
背景: 如何实现远程连接服务器上的oracle12c? 1.安装一个oracle12c空库,使用oracle12c中集成的oracle pl/sql developer工具实现连接远程服务器上的ora ...
- hdu2050 折线分割平面---递推
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2050 题目大意: 求n条折线分割平面的最大数目 思路: 先看n条直线的时候 一条直线 2个平面 两条 ...