Citus的restart详解
Citus的restart详解
1. 命令行restart
在ctl.py
的restart
方法中,获取到集群的信息,然后再获取到要重启节点的信息。
cluster = get_dcs(cluster_name, group).get_cluster()
members = get_members(cluster, cluster_name, member_names, role, force, 'restart', False, group=group)
然后进行一些计划启动时间、确定是否启动、启动数据库版本最低限制等等设置,最后对每一个节点,如果是计划重新启动,就会发送一个delete
的restart
请求和一个post
的restart
请求,如果不是则是单独发一个post
的restart
请求。
if 'schedule' in content:
if force and member.data.get('scheduled_restart'):
r = request_patroni(member, 'delete', 'restart')
check_response(r, member.name, 'flush scheduled restart', True)
r = request_patroni(member, 'post', 'restart', content)
2. 构造请求路径
在request.py
的__call__
方法中来构造请求的路径
url = member.get_endpoint_url(endpoint)
return self.request(method, url, data, **kwargs)
构建的url如下所示:
http://192.168.198.186:8008/restart
3. 接收restart请求
在发送了restart
请求之后,在对应的成员的patroni
进程中接收restart
请求,这里以post请求为例:在解析请求体,对集群状态和请求参数进行验证和和判断之后,然后执行或安排重启。
else:
if 'schedule' not in request:
try:
status, data = self.server.patroni.ha.restart(request)
status_code = 200 if status else 503
except Exception:
logger.exception('Exception during restart')
status_code = 400
else:
if self.server.patroni.ha.schedule_future_restart(request):
data = "Restart scheduled"
status_code = 202
else:
data = "Another restart is already scheduled"
status_code = 409
然后返回执行结果。
4. 执行restart
这里以执行restart为例,在ha.py
的restart
方法中,执行重启操作。在这个方法中进行一些类型和重启条件的检查,设置重启前后的回调函数:
def before_shutdown() -> None:
self.notify_mpp_coordinator('before_demote')
def after_start() -> None:
self.notify_mpp_coordinator('after_promote')
然后在讲着两个回调函数放入重启操作中,再执行重启操作:
# For non async cases we want to wait for restart to complete or timeout before returning.
do_restart = functools.partial(self.state_handler.restart, timeout, self._async_executor.critical_task,
before_shutdown=before_shutdown if self.has_lock() else None,
after_start=after_start if self.has_lock() else None)
if self.is_synchronous_mode() and not self.has_lock():
do_restart = functools.partial(self.while_not_sync_standby, do_restart)
if run_async:
self._async_executor.run_async(do_restart)
return (True, 'restart initiated')
4.1 before_demote
def notify_mpp_coordinator(self, event: str) -> None:
"""Send an event to the MPP coordinator.
:param event: the type of event for coordinator to parse.
"""
mpp_handler = self.state_handler.mpp_handler
if mpp_handler.is_worker():
coordinator = self.dcs.get_mpp_coordinator()
if coordinator and coordinator.leader and coordinator.leader.conn_url:
try:
data = {'type': event,
'group': mpp_handler.group,
'leader': self.state_handler.name,
'timeout': self.dcs.ttl,
'cooldown': self.patroni.config['retry_timeout']}
timeout = self.dcs.ttl if event == 'before_demote' else 2
# Fbase(fdd): request fdd api
if mpp_handler.type == 'Citus':
endpoint = 'citus'
elif mpp_handler.type == 'Fdd':
endpoint = 'fdd'
else:
endpoint = 'mpp'
self.patroni.request(coordinator.leader.member, 'post', endpoint, data, timeout=timeout, retries=0)
except Exception as e:
logger.warning('Request to %s coordinator leader %s %s failed: %r', mpp_handler.type,
coordinator.leader.name, coordinator.leader.member.api_url, e)
在数据库关闭之前会执行这个函数,这个函数会先获取到mpp
处理器(citus
),然后判断当前是否为cn
节点,如果是cn
节点则不进行操作,如果是dn
节点则会发送路径如下的post
请求(在cn
的处理器上进行处理):
'http://192.168.198.188:8008/citus'
4.2 after_promote
在数据库关闭之前会执行这个函数,这个函数会先获取到mpp
处理器(citus
),然后判断当前是否为cn
节点,如果是cn
节点则不进行操作,如果是dn
节点则会发送post
请求,与before_demote
类似。
5. 执行citus有关请求
在4中对发送了有关的before_demote
和after_promote
两个事件,统一发送到citus
路径下,在api.py
的do_POST_citus
方法中交由citus
的CitusHandler
来进行事件的处理(在cn
上进行处理)。
if patroni.postgresql.mpp_handler.is_coordinator() and patroni.ha.is_leader():
cluster = patroni.dcs.get_cluster()
patroni.postgresql.mpp_handler.handle_event(cluster, request)
5.1 citus添加事件
在citus.py
中的handle_event
函数中,添加对事件的处理,如果事件是before_demote
,则还会等待该任务完成。
def handle_event(self, cluster: Cluster, event: Dict[str, Any]) -> None:
if not self.is_alive():
return
worker = cluster.workers.get(event['group'])
if not (worker and worker.leader and worker.leader.name == event['leader'] and worker.leader.conn_url):
return logger.info('Discarding event %s', event)
task = self.add_task(event['type'], event['group'], worker,
worker.leader.name, worker.leader.conn_url,
event['timeout'], event['cooldown'] * 1000)
if task and event['type'] == 'before_demote':
task.wait()
对于add_task函数,则是在集群中添加一个分布式的PostgreSQL任务,获取主节点,创建任务对象并添加从节点,最后将任务添加到任务队列中。
def add_task(self, event: str, groupid: int, cluster: Cluster, leader_name: str, leader_url: str,
timeout: Optional[float] = None, cooldown: Optional[float] = None) -> Optional[PgDistTask]:
primary = self._pg_dist_node('demoted' if event == 'before_demote' else 'primary', leader_url)
if not primary:
return
task = PgDistTask(groupid, {primary}, event=event, timeout=timeout, cooldown=cooldown)
for member in cluster.members:
secondary = self._pg_dist_node('secondary', member.conn_url)\
if member.name != leader_name and not member.noloadbalance and member.is_running and member.conn_url\
else None
if secondary:
task.add(secondary)
return task if self._add_task(task) else None
在这个方法中调用内部函数_add_task
来完成任务的添加。
5.2 事件处理
在process_tasks
中对单个任务进行接取和处理,调用process_task
函数来处理相关的事件。
- after_promote
处理after_promote
事件。
if task.event == 'after_promote':
self.update_group(task, self._in_flight is not None)
if self._in_flight:
self.query('COMMIT')
task.failover = False
return True
- before_demote / before_promote
处理before_demote / before_promote
事件。
else: # before_demote, before_promote
if task.timeout:
task.deadline = time.time() + task.timeout
if not self._in_flight:
self.query('BEGIN')
self.update_group(task, True)
return False
这些事件的处理都是在update_group
函数中统一进行处理,用于更新 pg_dist_group
表中的数据。在这个函数首先获取当前的状态,然后调用transition
函数计算状态转换,最后处理状态转换。
def update_group(self, task: PgDistTask, transaction: bool) -> None:
current_state = self._in_flight\
or self._pg_dist_group.get(task.groupid)\
or PgDistTask(task.groupid, set(), 'after_promote')
transitions = list(task.transition(current_state))
if transitions:
if not transaction and len(transitions) > 1:
self.query('BEGIN')
for node in transitions:
self.update_node(task.groupid, node, task.cooldown)
if not transaction and len(transitions) > 1:
task.failover = False
self.query('COMMIT')
在transition
函数中用来比较原先的拓扑结构和现在的拓扑结构的差异并进行记录,最后调用update_node
函数来更新这些差异。
update_node
函数用于更新或添加/删除 pg_dist_node
表中的节点信息,在这个函数中会检查节点角色、更新现有节点和添加新节点。
def update_node(self, groupid: int, node: PgDistNode, cooldown: float = 10000) -> None:
if node.role not in ('primary', 'secondary', 'demoted'):
self.query('SELECT pg_catalog.citus_remove_node(%s, %s)', node.host, node.port)
elif node.nodeid is not None:
host = node.host + ('-demoted' if node.role == 'demoted' else '')
self.query('SELECT pg_catalog.citus_update_node(%s, %s, %s, true, %s)',
node.nodeid, host, node.port, cooldown)
elif node.role != 'demoted':
node.nodeid = self.query("SELECT pg_catalog.citus_add_node(%s, %s, %s, %s, 'default')",
node.host, node.port, groupid, node.role)[0][0]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统