Citus的restart详解

Citus的restart详解

1. 命令行restart

ctl.pyrestart方法中,获取到集群的信息,然后再获取到要重启节点的信息。

    cluster = get_dcs(cluster_name, group).get_cluster()

    members = get_members(cluster, cluster_name, member_names, role, force, 'restart', False, group=group)

然后进行一些计划启动时间、确定是否启动、启动数据库版本最低限制等等设置,最后对每一个节点,如果是计划重新启动,就会发送一个deleterestart请求和一个postrestart请求,如果不是则是单独发一个postrestart请求。

        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.pyrestart方法中,执行重启操作。在这个方法中进行一些类型和重启条件的检查,设置重启前后的回调函数:

        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_demoteafter_promote两个事件,统一发送到citus路径下,在api.pydo_POST_citus方法中交由citusCitusHandler来进行事件的处理(在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]

6. 思考

posted @   零の守墓人  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示