Patroni的能力:管理两到三个节点的PostgreSQL/Opengauss,数据库退出能自动拉起,能发现关闭的主节点,并切换备节点为主节点。
实现这个能力,在单个节点上,Patroni执行流并不复杂,它的主体流是一个大循环,在这个大循环的主干下,某些代码,根据条件,会启动一些“异步任务”,所谓“异步任务”就是另起一个线程,执行一些操作,而主干的大循环仍在运行,并不等待。
仅仅着眼于编程实现(代码),这样使人看不到整体效果,局部即使代码层面搞明白在做些什么,也感觉莫名其妙,应该把重点放在设计思想,当然编程实现的基础理解力也必须要有。然而许多开源软件,分析局部代码易,分析整理设计思想,了解软件演进发展历史难,许多开源软件虽然代码开源了,可是并不提供详细的文档和演进历史,从局部代码反推整体设计思想和发展的历史就比较困难了。
不过,在不纠结细节的情况下,捋一遍软件执行流程,对理解软件还是很有用的,本文就是重点从执行流角度,分析一下Patroni这款开源软件,只分析执行流相关,例如总体流程,进程,线程等,其它细节忽略。
下面 “->” 表示调用:
1. abstract_main() -- patroni/daemon.py
2. -> Patroni.run() -- patroni/__main__.py
3. -> AbstractPatroniDaemon.run() -- patroni/daemon.py
4. -> Patroni._run_cycle() -- patroni/__main__.py
5. -> Ha.run_cycle() -- patroni/ha.py
6. ->Ha._run_cycle() -- patroni/ha.py
1 -- 入口函数,创建Patroni对象,调用run()。
2 -- Patroni对象是核心对象,它包含了所有用到的对象 —— 整个系统所有的对象,都是它的成员变量,当然这些成员变量内也可以引用Patroni对象。
3 -- Patroni.run() 调用 AbstractPatroniDaemon.run() 开始执行循环体,循环体while的代码在AbstractPatroniDaemon.run() 中。
4 -- 循环一次执行一次 Patroni._run_cycle(),执行完函数所有逻辑后,会等待一段时间,再进入下一次循环,等待时间以loop_wait参数配置。
5、6 -- Patroni._run_cycle() 进一步调用 Ha.run_cycle() -> Ha._run_cycle() 做实际的工作。 一次循环的核心逻辑,就在函数Ha._run_cycle()里,其它模块的函数基本上是被它调用。
有些任务,例如数据库初始化,数据库重启,rewind等,会开启一个线程来执行,而不是在主循环的执行流中执行,这个操作通过调用 Ha.AsyncExecutor.try_run_async() 来执行,有意思的是,使用这个函数,只允许系统中有一个正在运行的异步线程,这个异步线程的任务执行完之前,不允许启动新的异步线程,这样设计,估计是防止系统中的线程太多。
有没有可能,主线程的循环执行完一遍,异步任务还没执行完,完全有可能,不过这也没有关系。异步任务会改变一些状态,主线程循环体中会判断这些状态做相应的动作,有时候主线程要执行完几个循环,异步任务才能完成,这也没什么关系。
在Postgresql对象的方法中,对PG/OG进行SQL查询时,还会调用Retry对象的__call__函数,这并不开启新线程执行查询,仍然是在主循环线程内执行。
REST API server是单独开启了一个线程执行http server的。
心跳(获取PG/OG状态)的SQL是在主循环里执行的,不是另外开一个线程。