本文首发于 2015-11-21 20:02:26
引言
PostgreSQL 在 9.0 之后引入了主备流复制机制,通过流复制,备库一直的从主库同步相应的数据,并在备库 apply 每个 WAL record
,这里的流复制每次传输单位是 WAL 日志的 record。而 PostgreSQL 9.0 之前提供的办法是主库写完一个 WAL 日志文件后,才把 WAL 日志文件传送到备库,这样的形式导致主备提早特地大。同时,PostgreSQL 9.0 之后提供了 Hot Standby
,备库在利用 WAL record
的同时也可能提供只读服务,大大晋升了用户体验。
主备总体构造
PostgreSQL 主备流复制的外围局部由 walsender
,walreceiver
和 startup
三个过程组成。
walsender 过程是用来发送 WAL 日志记录的,执行程序如下:
PostgresMain()->exec_replication_command()->StartReplication()->WalSndLoop()->XLogSendPhysical()
walreceiver 过程是用来接管 WAL 日志记录的,执行程序如下:
sigusr1_handler()->StartWalReceiver()->AuxiliaryProcessMain()->WalReceiverMain()->walrcv_receive()
startup 过程是用来 apply 日志的,执行程序如下:
PostmasterMain()->StartupDataBase()->AuxiliaryProcessMain()->StartupProcessMain()->StartupXLOG()
walsender 和 walreceiver 过程流复制过程
walsender 和 walreceiver 交互次要分为以下几个步骤:
- walreceiver 启动后通过
recovery.conf
文件中的primary_conninfo
参数信息连向主库,主库通过连贯参数replication=true
启动 walsender 过程; - walreceiver 执行
identify_system
命令,获取主库systemid/timeline/xlogpos
等信息,执行TIMELINE_HISTORY
命令拉取 history 文件; - 执行
wal_startstreaming
开始启动流复制,通过walrcv_receive
获取 WAL 日志,期间也会回应主库发过来的心跳信息(接管位点、flush 位点、apply 位点),向主库发送 feedback 信息(最老的事务 id),防止 vacuum 删掉备库正在应用的记录; - 执行
walrcv_endstreaming
完结流复制,期待 startup 过程更新receiveStart
和receiveStartTLI
,一旦更新,进入步骤 2。
walreceiver 和 startup 过程
startup 过程进入 standby 模式和 apply 日志次要过程:
- 读取
pg_control
文件,找到 redo 位点;读取recovery.conf
,如果配置standby_mode=on
则进入 standby 模式。 - 如果是 Hot Standby 须要初始化 clog、subtrans、事务环境等。初始化 redo 资源管理器,比方
Heap、Heap2、Database、XLOG
等。 - 读取 WAL record,如果 record 不存在须要调用
XLogPageRead->WaitForWALToBecomeAvailable->RequestXLogStreaming
唤醒walreceiver 从 walsender
获取 WAL record。 - 对读取的 WAL record 进行 redo,通过
record->xl_rmid
信息,调用相应的 redo 资源管理器进行 redo 操作。比方heap_redo
的XLOG_HEAP_INSERT
操作,就是通过 record 的信息在 buffer page 中减少一个 record:
MemSet((char *) htup, 0, sizeof(HeapTupleHeaderData));
/* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
memcpy((char *) htup + offsetof(HeapTupleHeaderData, t_bits),
(char *) xlrec + SizeOfHeapInsert + SizeOfHeapHeader,
newlen);
newlen += offsetof(HeapTupleHeaderData, t_bits);
htup->t_infomask2 = xlhdr.t_infomask2;
htup->t_infomask = xlhdr.t_infomask;
htup->t_hoff = xlhdr.t_hoff;
HeapTupleHeaderSetXmin(htup, record->xl_xid);
HeapTupleHeaderSetCmin(htup, FirstCommandId);
htup->t_ctid = xlrec->target.tid;
offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
if (offnum == InvalidOffsetNumber)
elog(PANIC, "heap_insert_redo: failed to add tuple");
freespace = PageGetHeapFreeSpace(page); /* needed to update FSM below */
PageSetLSN(page, lsn);
if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED)
PageClearAllVisible(page);
MarkBufferDirty(buffer);
还有局部 redo 操作 (vacuum 产生的 record) 须要查看在 Hot Standby 模式下的查问抵触,比方某些 tuples 须要 remove,而存在正在执行的 query 可能读到这些 tuples,这样就会毁坏事务隔离级别。通过函数 ResolveRecoveryConflictWithSnapshot
检测抵触,如果发生冲突,那么就把这个 query 所在的过程 kill 掉。
- 查看一致性,如果统一了,Hot Standby 模式能够承受用户只读查问;更新共享内存中
XLogCtlData
的 apply 位点和工夫线;如果复原到工夫点,工夫线或者事务 id 须要查看是否复原到以后指标; - 回到步骤 3,读取 next WAL record。
本文转自:http://mysql.taobao.org/month…
欢送关注我的微信公众号【数据库内核】:分享支流开源数据库和存储引擎相干技术。
题目 | 网址 |
---|---|
GitHub | https://dbkernel.github.io |
知乎 | https://www.zhihu.com/people/… |
思否(SegmentFault) | https://segmentfault.com/u/db… |
掘金 | https://juejin.im/user/5e9d3e… |
开源中国(oschina) | https://my.oschina.net/dbkernel |
博客园(cnblogs) | https://www.cnblogs.com/dbkernel |