美文网首页HAProxy
HAProxy源码探索(5):函数和变量分析

HAProxy源码探索(5):函数和变量分析

作者: chenj23986526 | 来源:发表于2019-01-09 00:13 被阅读0次

    代码分析

    调试过程中你会对代码结构渐渐的有清晰的认识,现在我们讲一下这中间的全局变量和函数
    现在我们依旧是分析 v1.0.0 版本

    全局变量

    • proxy 变量
      该变量是一个结构指针 struct proxy * ,实际上是作为链表的形式存在,保存了所有代理的结构
      readcfgfile 函数读配置时赋值
      同时 task 成员也是非常重要的,它保存着当前 proxy 的所有请求和响应信息,相当于 session 的功能

      struct task {
        struct task *next, *prev;       /* chaining ... */
        struct task *rqnext;        /* chaining in run queue ... */
        int state;              /* task state : IDLE or RUNNING */
        struct timeval expire;      /* next expiration time for this task, use only for fast sorting */
        /* application specific below */
        struct timeval crexpire;        /* expiration date for a client read  */
        struct timeval cwexpire;        /* expiration date for a client write */
        struct timeval srexpire;        /* expiration date for a server read  */
        struct timeval swexpire;        /* expiration date for a server write */
        struct timeval cnexpire;        /* expiration date for a connect */
        char res_cr, res_cw, res_sr, res_sw;/* results of some events */
        struct proxy *proxy;        /* the proxy this socket belongs to */
        int cli_fd;             /* the client side fd */
        int srv_fd;             /* the server side fd */
        int cli_state;          /* state of the client side */
        int srv_state;          /* state of the server side */
        int conn_retries;           /* number of connect retries left */
        int conn_redisp;            /* allow reconnection to dispatch in case of errors */
        struct buffer *req;         /* request buffer */
        struct buffer *rep;         /* response buffer */
        struct sockaddr_in cli_addr;    /* the client address */
        struct sockaddr_in srv_addr;    /* the address to connect to */
        char cookie_val[SERVERID_LEN+1];    /* the cookie value, if present */
      };
      
      struct proxy {
        int listen_fd;          /* the listen socket */
        int state;              /* proxy state */
        struct sockaddr_in listen_addr; /* the address we listen to */
        struct sockaddr_in dispatch_addr;   /* the default address to connect to */
        struct server *srv;         /* known servers */
        char *cookie_name;          /* name of the cookie to look for */
        int clitimeout;         /* client I/O timeout (in milliseconds) */
        int srvtimeout;         /* server I/O timeout (in milliseconds) */
        int contimeout;         /* connect timeout (in milliseconds) */
        char *id;               /* proxy id */
        int nbconn;             /* # of active sessions */
        int maxconn;            /* max # of active sessions */
        int conn_retries;           /* number of connect retries left */
        int conn_redisp;            /* allow to reconnect to dispatch in case of errors */
        int mode;               /* mode = PR_MODE_TCP or PR_MODE_HTTP */
        struct task task;           /* active sessions (bi-dir chaining) */
        struct task *rq;            /* sessions in the run queue (unidir chaining) */
        struct proxy *next;
        struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */
        char logfac1, logfac2;      /* log facility for both servers. -1 = disabled */
        struct timeval stop_time;       /* date to stop listening, when stopping != 0 */
        int nb_cliexp, nb_srvexp;
        struct hdr_exp cli_exp[MAX_REGEXP]; /* regular expressions for client headers */
        struct hdr_exp srv_exp[MAX_REGEXP]; /* regular expressions for server headers */
        int grace;              /* grace time after stop request */
      };
      
      struct proxy *proxy  = NULL;  /* list of all existing proxies */
      
      
    • fdtab 变量
      该变量同样是一个结构指针 struct fdtab *,作为一个数组存储着所有 fd 关联的信息

      /* info about one given fd */
      struct fdtab {
        int (*read)(int fd);    /* read function */
        int (*write)(int fd);   /* write function */
        struct task *owner;     /* the session (or proxy) associated with this fd */
        int state;          /* the state of this fd */
      };
      
      struct fdtab *fdtab = NULL;   /* array of all the file descriptors */
      
    • fd_set * 类型变量
      ReadEventWriteEvent 用于 select 函数,因为执行完后会修改里面的值,所以作为临时存储 fd 的空间
      StaticReadEventStaticWriteEvent 则是存储所有原始的 fd 信息

    函数

    • init 函数
      这个函数主要干这么些事:

      1. 命令行参数的读取
      2. 配置文件的分析
      3. 预分配一些空间给一些关键变量并初始化这些变量
      4. 执行过程中若失败就退出程序

      我们可以看到初期的代码还是很简陋的,处理命令行参数的基本全是手写的 if else 代码,对后期维护会带来一定挑战。同时读配置文件的函数 readcfgfile 里面也存在大量硬编码的逻辑,没有把配置语义化规则化。未来规则复杂后,恐怕不能再使用大量的条件判断去实现了,否则维护过程会很难。

      另外第 2968 行到第 2983 行是用来切换到 daemon 模式的,我觉得可以独立出一个函数会比较干净

    • dump 函数
      dump 函数捕获了 SIGQUIT 信号,打印了一些当前的状态变量

      void dump(int sig) {
        struct proxy *p;
      
        for (p = proxy; p; p = p->next) {
        struct task *t, *tnext;
        tnext = ((struct task *)LIST_HEAD(p->task))->next;
        while ((t = tnext) != LIST_HEAD(p->task)) { /* we haven't looped ? */
            tnext = t->next;
            fprintf(stderr,"[dump] wq: task %p, still %ld ms, "
                "cli=%d, srv=%d, cr=%d, cw=%d, sr=%d, sw=%d, "
                "req=%d, rep=%d, clifd=%d\n",
                t, tv_remain(&now, &t->expire),
                t->cli_state,
                t->srv_state,
                FD_ISSET(t->cli_fd, StaticReadEvent),
                FD_ISSET(t->cli_fd, StaticWriteEvent),
                FD_ISSET(t->srv_fd, StaticReadEvent),
                FD_ISSET(t->srv_fd, StaticWriteEvent),
                t->req->l, t->rep?t->rep->l:0, t->cli_fd
                );
        }
        }
      }
      
    • sig_soft_stop 函数
      sig_soft_stop 函数捕获了 SIGUSR1 信号

      void sig_soft_stop(int sig) {
        soft_stop();
        signal(sig, SIG_IGN);
      }
      
      static void soft_stop(void) {
        struct proxy *p;
      
        stopping = 1;
        p = proxy;
        while (p) {
        if (p->state != PR_STDISABLED)
            tv_delayfrom(&p->stop_time, &now, p->grace);
        p = p->next;
        }
      }
      

      大致的意思就是使用比较优雅的方式关闭连接
      在停之前设置标志位 stopping
      然后设置每个节点要停止的时间点,等待主线程把所有相关连接全部关闭
      可是整个进程并没有终止,还是需要发送停止信号才会关闭

    • start_proxies 函数
      遍历 proxy 链表,并启动所有的未禁用的代理,成功返回 0,否则退出程序
      在对 socket 绑定和监听前经过了以下设置

      • 无阻塞(O_NONBLOCK
      • TCP 无延时(TCP_NODELAY
      • 可重用地址(SO_REUSEADDR

      最后设置 fdtab

    • select_loop 函数
      我们看到这个版本的 HAProxy 因为是在 2000~2001年开发的,所以这时候还没有 epoll,使用 select 遍历的技术是比较普遍的
      select 前会调用 maintain_proxies 函数来更新代理的状态
      该函数逻辑很简单

      • 若设置了 stopping,则停止所有端口监听
      • 若监听总连接数没达到连接数最大限制,则把空闲状态(PR_STIDLE)的代理变为运行中(PR_STRUN
      • 若达到了最大连接数限制,则把所有运行中的代理设置为空闲(停止接收新连接)

      接着执行 select 函数, 对所有激活的 fd 进行读写
      最后把可以执行的 task 放入执行队列 run queue
      批量处理这些 task,这里会涉及到 3 个函数 process_cli, process_srv, process_task

      说明:这里的 client 是指请求方,server 是后端响应方

    • 事件相关函数
      event_accept: 监听到新的连接时触发
      event_cli_read: 请求数据到达 HAProxy 时触发
      event_cli_write: 把数据发回请求方时触发
      connect_server: 连接后端服务器
      event_srv_read: 后端服务响应到达 HAProxy 时触发
      event_srv_write: 把数据发送到后端服务时触发

    总结

    我们看到 1.0.0 版本的 HAProxy 还是相当简陋的,连 acl 也还没开发,好处是可以很容易的读懂,为后面复杂的版本打下基础
    后面我们将对版本迭代中的关键代码变更进行讲解

    相关文章

      网友评论

        本文标题:HAProxy源码探索(5):函数和变量分析

        本文链接:https://www.haomeiwen.com/subject/pqujlqtx.html