0. 进程架构

image.png

  1. postmaster(常驻进程)

管理后端的常驻进程。其默认监听UNIX Domain Socket和TCP/IP(Windows等,一部分的平台只监听tcp/ip)的5432端口,等待来自前端的的连接处理。监听的端口号可以在PostgreSQL的设置文件postgresql.conf里面可以改。
一旦有前端连接过来,postgres会通过fork(2)生成子进程。没有Fork(2)的windows平台的话,则利用createProcess()生成新的进程。这种情形的话,和fork(2)不同的是,父进程的数据不会被继承过来,所以需要利用共享内存把父进程的数据继承过来。

  1. postgres(子进程)

子进程根据pg_hba.conf定义的安全策略来判断是否允许进行连接,根据策略,会拒绝某些特定的IP及网络,或者也可以只允许某些特定的用户或者对某些数据库进行连接。
Postgres会接受前端过来的查询,然后对数据库进行检索,把结果返回,有时也会对数据库进行更新。更新的数据同时还会记录在WAL中, 还会把日志归档保存起来,可在需要进行恢复的时候使用。在PostgreSQL 9.0以后,通过把WAL日志传送其他的postgreSQL,可以实时得进行数据库复制.

  1. Writer process

这个进程一个重要的功能是定期执行检查点(checkpoint)。checkpoint会把共享内存上的缓存内容往数据库文件写,使得内存和文件的状态一致。

  1. WAL writer process

WAL writer process把共享内存上的WAL缓存在适当的时间点往磁盘写.

  1. Archive process

Archive process把WAL日志转移到归档日志里。如果保存了基础备份以及归档日志,即使实在磁盘完全损坏的时候,也可以回复数据库到最新的状态。

  1. stats collector process

统计信息的收集进程。收集好统计表的访问次数,磁盘的访问次数等信息。收集到的信息除了能被autovaccum利用,还可以给其他数据库管理员作为数据库管理的参考信息。

  1. Logger process

把postgresql的活动状态写到日志信息文件,在指定的时间间隔里面,对日志文件进行rotate.

  1. Autovacuum启动进程

autovacuum launcher process是依赖于postmaster间接启动vacuum进程。而其自身是不直接启动自动vacuum进程的。通过这样可以提高系统的可靠性。

9 自动vacuum进程
autovacuum worker process进程实际执行vacuum的任务。有时候会同时启动多个vacuum进程。

  1. wal sender / wal receiver

wal sender 进程和wal receiver进程是实现postgresql复制(streaming replication)的进程。Wal sender进程通过网络传送WAL日志,而其他PostgreSQL实例的wal receiver进程则接收相应的日志。Wal receiver进程的宿主PostgreSQL(也称为Standby)接受到WAL日志后,在自身的数据库上还原,生成一个和发送端的PostgreSQL(也称为Master)完全一样的数据库。

1. 总体目录

image.png
src/

DEVELOPERS 面向开发人员的注释 Makefile Makefile Makefile.global make 的设定值(从configure生成的) Makefile.global.in Configure使用的Makefile.global的雏形 Makefile.port 平台相关的make的设定值,实际是一个到makefile/Makefile的连接. (从configure生成的) Makefile.shlib 共享库用的Makefile backend/ 后端的源码目录 bin/ psql 等 UNIX命令的代码 include/ 头文件 interfaces/ 前端相关的库的代码 makefiles/ 平台相关的make 的设置值 nls-global.mk 信息目录用的Makefile文件的规则 pl/ 存储过程语言的代码 port/ 平台移植相关的代码 template/ 平台相关的设置值 test/ 各种测试脚本 timezone/ 时区相关代码 tools/ 各自开发工具和文档 tutorial/ 教程

backend/包括

Makefile makefile access/ 各种存储访问方法(在各个子目录下) common(共同函数)、gin (Generalized Inverted Index通用逆向索引)、gist (Generalized Search Tree通用索引)、 hash (哈希索引)、heap (heap的访问方法)、index (通用索引函数)、 nbtree (Btree函数)、transam (事务处理)

bootstrap/ 数据库的初始化处理(initdb的时候) catalog/ 系统目录 commands/ SELECT/INSERT/UPDATE/DELETE以为的SQL文的处理 executor/ 执行器(访问的执行) foreign/ FDW(Foreign Data Wrapper)处理 lib/ 共同函数 libpq/ 前端/后端通信处理 main/ postgres的主函数 nodes/ 构文树节点相关的处理函数 optimizer/ 优化器 parser/ SQL构文解析器 port/ 平台相关的代码 postmaster/ postmaster的主函数 (常驻postgres) replication/ streaming replication regex/ 正则处理 rewrite/ 规则及视图相关的重写处理 snowball/ 全文检索相关(语干处理) storage/ 共享内存、磁盘上的存储、缓存等全部一次/二次记录管理(以下的目录)buffer/(缓存管理)、 file/(文件)、 freespace/(Fee Space Map管理) ipc/(进程间通信)、large_object /(大对象的访问函数)、 lmgr/(锁管理)、page/(页面访问相关函数)、 smgr/(存储管理器) tcop/ postgres (数据库引擎的进程)的主要部分 tsearch/ 全文检索 utils/ 各种模块(以下目录) adt/(嵌入的数据类型)、cache/(缓存管理)、 error/(错误处理)、fmgr/(函数管理)、 hash/(hash函数)、 init/(数据库初始化、postgres的初期处理)、 mb/(多字节文字处理)、 misc/(其他)、mmgr/(内存的管理函数)、 resowner/(查询处理中的数据(buffer pin及表锁)的管理)、 sort/(排序处理)、time/(事务的 MVCC 管理)

2. PostgreSQL执行原理

2.1 bootstrap&initdb

initdb是bin/initdb/下源码编译的,最终是postgresql的bin目录下的可执行文件
initdb用于在启动PG之前,初始化存储目录,模板文件等等,包括userDB一系列元数据

2.2 前后端交互

对于存储引擎postgres,无论是客户端还是其他进程比如postmaster,都是前端.而与存储引擎交互的方式就是依靠CMD,通过C库的popen,pwrite,pread来执行bash命令,结合fork等操作来让存储引擎工作
注意,上述方式仅IPC的其中一种,其他IPC方式诸如共享内存本篇暂不研究

2.3 backend/main/main.c

main是PG的一个进程入口,从常驻进程(postmaster)开始,然后常驻进程会fork很多子进程包括但不限于WAL,Logger,postgres等等.

2.3.1 [重点]流程解析

我们将main函数分为几个部分来看

  1. 初始化基本参数 ```c main(int argc, char argv[]) { bool do_check_root = true; //1. 拿出启动的可执行文件名称 progname = get_progname(argv[0]); /
    • 平台兼容,PG吐槽了跨平台很”brain-dead” */ startup_hacks(progname); //2. 存储参数并返回一个副本,避免init_ps_display将参数修改 argv = save_ps_display_args(argc, argv); //3. 初始化内存和错误管理的上下文 MemoryContextInit(); //4. 根据progname初始化将要启动的进程的工作路径和语言环境 set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN(“postgres”));
  1. - 首先,PG要确认启动的服务名称,正如上所说,main()是一个**通用入口**根据传入参数_分化_成不同子进程
  2. - 首当其冲的初始化时MemoryContextlocalte_information,这两个是全局的重要上下文,其中MemoryContextpostmaster完整初始化,然后fork子进程时继承它,并分化子进程自己的上下文
  3. 2. 清空本地化设置,没啥好解释的,PG要保证本地化的一些东西不被系统设置受影响
  4. ```c
  5. /*
  6. * In the postmaster, absorb the environment values for LC_COLLATE and
  7. * LC_CTYPE. Individual backends will change these later to settings
  8. * taken from pg_database, but the postmaster cannot do that. If we leave
  9. * these set to "C" then message localization might not work well in the
  10. * postmaster.
  11. */
  12. init_locale("LC_COLLATE", LC_COLLATE, "");
  13. init_locale("LC_CTYPE", LC_CTYPE, "");
  14. /*
  15. * LC_MESSAGES will get set later during GUC option processing, but we set
  16. * it here to allow startup error messages to be localized.
  17. */
  18. #ifdef LC_MESSAGES
  19. init_locale("LC_MESSAGES", LC_MESSAGES, "");
  20. #endif
  21. /*
  22. * We keep these set to "C" always, except transiently in pg_locale.c; see
  23. * that file for explanations.
  24. */
  25. init_locale("LC_MONETARY", LC_MONETARY, "C");
  26. init_locale("LC_NUMERIC", LC_NUMERIC, "C");
  27. init_locale("LC_TIME", LC_TIME, "C");
  28. /*
  29. * Now that we have absorbed as much as we wish to from the locale
  30. * environment, remove any LC_ALL setting, so that the environment
  31. * variables installed by pg_perm_setlocale have force.
  32. */
  33. unsetenv("LC_ALL");
  1. check_strxfrm_bug(); : 他们检测自己9.5以前版本的BUG
  2. 判断arg[0]是否是基本操作: 很简单,就是诸如 —help —version等基本命令就直接处理并exit(0)了

    1. if (argc > 1)
    2. {
    3. if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
    4. {
    5. help(progname);
    6. exit(0);
    7. }
    8. if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
    9. {
    10. fputs(PG_BACKEND_VERSIONSTR, stdout);
    11. exit(0);
    12. }
    13. if (strcmp(argv[1], "--describe-config") == 0)
    14. do_check_root = false;
    15. else if (argc > 2 && strcmp(argv[1], "-C") == 0)
    16. do_check_root = false;
    17. }
  3. 最后一步: 判断arg[0]是需要启动哪个服务

    1. if (argc > 1 && strcmp(argv[1], "--check") == 0)
    2. BootstrapModeMain(argc, argv, true);
    3. else if (argc > 1 && strcmp(argv[1], "--boot") == 0)
    4. BootstrapModeMain(argc, argv, false);
    5. #ifdef EXEC_BACKEND
    6. else if (argc > 1 && strncmp(argv[1], "--fork", 6) == 0)
    7. SubPostmasterMain(argc, argv);
    8. #endif
    9. else if (argc > 1 && strcmp(argv[1], "--describe-config") == 0)
    10. GucInfoMain();
    11. else if (argc > 1 && strcmp(argv[1], "--single") == 0)
    12. PostgresSingleUserMain(argc, argv,
    13. strdup(get_user_name_or_exit(progname)));
    14. else
    15. PostmasterMain(argc, argv);
    16. /* the functions above should not return */
    17. abort();
    18. }
  • 根据传入参数,分发到不同服务的main入口
  • 其中 —single就是以单用户模式启动存储引擎
  • PostmasterMain是postmaster的核心,postmaster负责fork并初始化各种子进程,那PostmasterMain()就是初始化它自己,对于主进程的原理我们放在 “PostgreSQL多进程原理“ 中叙述,此处不赘述
  • 那么我们可以简单地从PostgresSingleUserMain进入看看存储引擎初始化原理

3. [重点]存储引擎: 底层入口—tcop

  • tcop主要工作是初始化postgres子进程,该进程有postmaster fork出来,每个客户端连接到postmaster都会激发其创建postgres子进程.
  • tcop是traffic cop就是交警,是后端执行各种命令的API,对接底层是存储引擎
  • 从main()中的init开始看起

3.0 PostgresSingleUserMain

  • 单用户和多用户其实没什么大的区别
  • 单用户会初始化dbName = userName
  • 然后做一些文件,目录的存在检查,文件锁检查等
  • 最后就进入了PostgresMain
    1. PostgresSingleUserMain(int argc, char *argv[],
    2. const char *username)
    3. {
    4. const char *dbname = NULL;
    5. if (dbname == NULL) dbname = username;
    6. if (!SelectConfigFiles(userDoption, progname))
    7. proc_exit(1);
    8. checkDataDir();
    9. ChangeToDataDir();
    10. CreateDataDirLockFile(false);
    11. LocalProcessControlFile(false);
    12. InitializeMaxBackends();
    13. CreateSharedMemoryAndSemaphores();
    14. PgStartTime = GetCurrentTimestamp();
    15. InitProcess();
    16. PostgresMain(dbname, username);
    17. }

    Note: 其实将PostgresMain()逆向看其引用,可以发现进入PostgresMain()除了单用户这里, 之前的PostmasterMain()也会有一个调用链: ** PostmasterMain-->**ServerLoop-->BackendStartup-->BackendRun-->**PostgresMain** 所以Postmaster在初始化自己这个主进程后,也会fork出后台进程也就是存储引擎Postgres的

3.1 PostgresMain

  1. 参数:

PostgresMain(const char dbname, const char username)
传入要连接的db名和用户名,在单用户模式(见PostgresSingleUserMain())用户名与db名一样

  1. 设置状态

SetProcessingMode(InitProcessing);
PG的所有进程都有三种状态之一:

  1. typedef enum ProcessingMode
  2. {
  3. BootstrapProcessing, /* bootstrap creation of template database */
  4. InitProcessing, /* initializing system */
  5. NormalProcessing /* normal processing */
  6. } ProcessingMode;
  1. 设置信号处理

    1. if (am_walsender)
    2. WalSndSignals();
    3. else
    4. {
    5. pqsignal(SIGHUP, SignalHandlerForConfigReload);
    6. pqsignal(SIGINT, StatementCancelHandler); /* cancel current query */
    7. pqsignal(SIGTERM, die); /* cancel current query and exit */
    8. if (IsUnderPostmaster)
    9. pqsignal(SIGQUIT, quickdie); /* hard crash time */
    10. else
    11. pqsignal(SIGQUIT, die); /* cancel current query and exit */
    12. InitializeTimeouts(); /* establishes SIGALRM handler */
    13. pqsignal(SIGPIPE, SIG_IGN);
    14. pqsignal(SIGUSR1, procsignal_sigusr1_handler);
    15. pqsignal(SIGUSR2, SIG_IGN);
    16. pqsignal(SIGFPE, FloatExceptionHandler);
    17. pqsignal(SIGCHLD, SIG_DFL); /* system() requires this on some
    18. * platforms */
    19. }

    父子多进程自然需要控制好信号,不然出现僵尸进程就不好了,还有其他作用此处不赘述

  2. 预初始化

    1. /* Early initialization */
    2. BaseInit();
  • 这个初始化可以开启PG最基本的功能诸如基本写入,刷盘等等,单独分离出来是用于常驻进程或其他进程调用后台进程做简单写入操作时用,那么存储引擎初始化自然也要调用一下
  1. 初始化存储引擎
    1. /*
    2. * General initialization.
    3. *
    4. * NOTE: if you are tempted to add code in this vicinity, consider putting
    5. * it inside InitPostgres() instead. In particular, anything that
    6. * involves database access should be there, not here.
    7. */
    8. InitPostgres(dbname, InvalidOid, username, InvalidOid, NULL, false);

    3.2 InitPostgres

    参数
    InitPostgres(const char indbname, Oid dboid, _const char username, Oid useroid, char *out_dbname,
    bool override_allow_connections)
  • in_dbname: 指定连接的数据库名
  • dboid: 如果不指定数据库名,可以指定Id
  • username:用户名
  • useroid: 如果不指定用户名,可以指定Id
  • out_dbname: 如果不指定数据库名,这里会返回数据库Id对应的名字,可以为空(匿名数据库)
  • override_allow_connections:

流程

  1. 将本进程元信息结构体(PGPROC)指针加入到共享内存的进程元信息结构体数组中,这样就可以被其他进程发现 ```c /*
    • Add my PGPROC struct to the ProcArray. *
    • Once I have done this, I am visible to other backends! */ InitProcessPhase2();
  1. 2. 初始化当前进程的后台进程Id和本进程Id,这个Id是唯一标识符
  2. ```c
  3. MyBackendId = InvalidBackendId; //先将本后台进程Id赋值-1
  4. SharedInvalBackendInit(false);//此函数会获取到一个后台进程Id,并赋值给本后台进程Id,
  5. //然后更新到PGPROC
  6. if (MyBackendId > MaxBackends || MyBackendId <= 0)
  7. elog(FATAL, "bad backend ID: %d", MyBackendId);
  8. ProcSignalInit(MyBackendId);//将本后台进程Id加入到信号控制中
  1. 如果不是bootstrap进程则需要设置各种后台操作超时时间

    1. if (!bootstrap)
    2. {
    3. RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLockAlert);
    4. RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
    5. RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
    6. RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
    7. IdleInTransactionSessionTimeoutHandler);
    8. RegisterTimeout(IDLE_SESSION_TIMEOUT, IdleSessionTimeoutHandler);
    9. RegisterTimeout(CLIENT_CONNECTION_CHECK_TIMEOUT, ClientCheckTimeoutHandler);
    10. }
  2. 初始化XLog,根据进程性质注册不同的进程退出时XLog关闭回调

    1. if (!IsUnderPostmaster)
    2. {
    3. //...
    4. StartupXLOG();
    5. //...
    6. before_shmem_exit(ShutdownXLOG, 0);
    7. }
  3. 初始化: 关系缓存; 目录缓存; 执行计划缓存; 查询执行状态接口; 全局后端进程状态;
    ```c RelationCacheInitialize(); InitCatalogCache(); InitPlanCache(); EnablePortalManager();

pgstat_beinit();

RelationCacheInitializePhase2(); ```

  1. 最后: 执行每次请求的Query

待完善

3.3 BaseInit

待完善

4 存储引擎初始化总结

多进程管理

  1. 基于共享内存让所有进程可见其他所有进程的状态
  2. 通过信号进行进程间通信
  3. postmaster是主进程,每个Client连接会fork一个postgres子进程
    1. postgres子进程负责处理查询,写脏数据到缓存
    2. postmaster作为异步进程定期或按策略Apply WAL以及缓存刷盘
  4. 其他种类的子进程如bootstrap等等均是瞬时态的,在启动或自检等特殊场景下启动,任务执行完毕就马上退出