• pgpoolII中是如何实现进程池的


    看了pgpool-II的代码后,对其扒皮抽筋,大体了解了其思路:

    首先有一般网络通信中使用的 scoket/bind/listen函数设置:

    /*                    
    * create inet domain socket                    
    */                    
    static int create_inet_domain_socket(const char *hostname, const int port)
    {                    
        struct sockaddr_in addr;                
        int fd;                
        int status;                
        int one = 1;                
        int len;                
        int backlog;                
                        
        fd = socket(AF_INET, SOCK_STREAM, 0);                
        ……                
        status = bind(fd, (struct sockaddr *)&addr, len);                
        ……                
        status = listen(fd, backlog);                
        if (status < 0)                
        {                
            pool_error("listen() failed. reason: %s", strerror(errno));            
            myexit(1);            
        }                
        return fd;                
    }                    

    父进程调用上述函数,把 fd 传递给各个子进程,并且获得了各个子进程的进程ID:

    /*                    
    * pgpool main program                    
    */                    
    int main(int argc, char **argv)                    
    {                    
        ……                
        /* create unix domain socket */                
        unix_fd = create_unix_domain_socket(un_addr);                
                        
        /* create inet domain socket if any */                
        if (pool_config->listen_addresses[0])                
        {                
            inet_fd = create_inet_domain_socket
    (pool_config->listen_addresses, pool_config->port); } …… /* * We need to block signal here. Otherwise child might send some * signals, for example SIGUSR1(fail over). Children will inherit * signal blocking but they do unblock signals at the very beginning * of process. So this is harmless. */ POOL_SETMASK(&BlockSig); /* fork the children */ for (i=0;i<pool_config->num_init_children;i++){ process_info[i].pid = fork_a_child(unix_fd, inet_fd, i); process_info[i].start_time = time(NULL); } /* set up signal handlers */ pool_signal(SIGTERM, exit_handler); pool_signal(SIGINT, exit_handler); pool_signal(SIGQUIT, exit_handler); pool_signal(SIGCHLD, reap_handler); pool_signal(SIGUSR1, failover_handler); pool_signal(SIGUSR2, wakeup_handler); pool_signal(SIGHUP, reload_config_handler); /* create pipe for delivering event */ if (pipe(pipe_fds) < 0){ pool_error("failed to create pipe"); myexit(1); } pool_log("%s successfully started. version %s (%s)",
    PACKAGE, VERSION, PGPOOLVERSION); ……
    ////main loop is here pool_shmem_exit(0); }

    而下面的声称子进程的函数里面,子进程一生成,就开始调用 do_child:

    /*                    
    * fork a child                    
    */                    
    pid_t fork_a_child(int unix_fd, int inet_fd, int id)                    
    {                    
        pid_t pid;                
                        
        pid = fork();                
                        
        if (pid == 0)                
        {                
            /* Before we unconditionally closed pipe_fds[0] and pipe_fds[1]            
             * here, which is apparently wrong since in the start up of            
             * pgpool, pipe(2) is not called yet and it mistakenly closes            
             * fd 0. Now we check the fd > 0 before close(), expecting            
             * pipe returns fds greater than 0.  Note that we cannot            
             * unconditionally remove close(2) calls since fork_a_child()            
             * may be called *after* pgpool starting up.            
             */            
            if (pipe_fds[0] > 0)            
            {            
                close(pipe_fds[0]);        
                close(pipe_fds[1]);        
            }            
                        
            myargv = save_ps_display_args(myargc, myargv);            
                        
            /* call child main */            
            POOL_SETMASK(&UnBlockSig);            
            reload_config_request = 0;            
            my_proc_id = id;            
            run_as_pcp_child = false;            
            do_child(unix_fd, inet_fd);            
        }else if (pid == -1){                
            pool_error("fork() failed. reason: %s", strerror(errno));            
            myexit(1);            
        }                
        return pid;                
    }

    再来看 do_child的逻辑:

    /*                    
    * child main loop                    
    */                    
    void do_child(int unix_fd, int inet_fd)                    
    {                    
        ……                
        /* set up signal handlers */                
        signal(SIGALRM, SIG_DFL);                
        signal(SIGTERM, die);                
        signal(SIGINT, die);                
        signal(SIGHUP, reload_config_handler);                
        signal(SIGQUIT, die);                
        signal(SIGCHLD, SIG_DFL);                
        signal(SIGUSR1, close_idle_connection);                
        signal(SIGUSR2, wakeup_handler);                
        signal(SIGPIPE, SIG_IGN);                
                        
        ……                
        for (;;){                       
            ……            
            accepted = 0;            
                        
            /* perform accept() */            
            frontend = do_accept(unix_fd, inet_fd, &timeout);            
                        
            pool_log("I am %d", getpid());            
                        
            if (frontend == NULL)  /* connection request from frontend timed out */
            {            
                ……        
                continue;        
            }            
            ……            
                        
            /*            
             * Ok, negotiaton with frontend has been done. Let's go to the            
             * next step.  Connect to backend if there's no existing            
             * connection which can be reused by this frontend.            
             * Authentication is also done in this step.            
             */            
            ……                    
            /*            
             * if there's no connection associated with user and database,            
             * we need to connect to the backend and send the startup packet.          
             */            
                        
            /* look for existing connection */            
            found = 0;            
            backend = pool_get_cp(sp->user, sp->database, sp->major, 1);            
                        
            if (backend != NULL){            
                ……        
            }            
                        
            if (backend == NULL){            
                /* create a new connection to backend */        
                if ((backend = connect_backend(sp, frontend)) == NULL){        
                    connection_count_down();    
                    continue;    
                }        
            }else{            
                /* reuse existing connection */        
                if (!connect_using_existing_connection(frontend, backend, sp))        
                    continue;    
            }                    
            ……            
            /*            
             * Initialize per session context            
             */            
            pool_init_session_context(frontend, backend);            
                        
            /* Mark this connection pool is conncted from frontend */            
            pool_coninfo_set_frontend_connected(
    pool_get_process_context()
    ->proc_id, pool_pool_index()); /* query process loop */ for (;;){ …… } /* Destroy session context */ pool_session_context_destroy(); /* Mark this connection pool is not conncted from frontend */ pool_coninfo_unset_frontend_connected(
    pool_get_process_context()
    ->proc_id, pool_pool_index()); …… } child_exit(0); }

    do_child中要调 do_accept,看do_accept的逻辑,高度概括,去掉无关代码后,大致是这样:

    /*                    
    * perform accept() and return new fd                    
    */                    
    static POOL_CONNECTION *do_accept(int unix_fd, int inet_fd, struct timeval *timeout){                    
        ……                
        fds = select(Max(unix_fd, inet_fd)+1, &readmask, NULL, NULL, timeoutval);      
        ……                
    }

    结合上述代码,如果站在子进程的角度来看,就是:
    它进行了 socket/bind/listen(由父进程代劳), 又进行了 select 操作,并在select出阻塞或超时。

    这就是pgpool-II的 进程池实现的方式。我觉得可以借鉴之,用在自己的程序上面。

  • 相关阅读:
    【大数据面试之对线面试官】MapReduce/HDFS/YARN面试题70连击
    2021年最新版大数据面试题全面总结-持续更新
    Hbase性能优化百科全书
    【大数据面试题】Flink第一弹60连发
    DockerFile
    Docker容器数据卷
    Docker入门
    八、Sentinel服务保护框架
    七、Gateway高可用集群与动态网关
    五、Nacos集群部署实现原理
  • 原文地址:https://www.cnblogs.com/gaojian/p/2623778.html
Copyright © 2020-2023  润新知