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

代码分析

调试过程中你会对代码结构渐渐的有清晰的认识,现在我们讲一下这中间的全局变量和函数
现在我们依旧是分析 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):函数和变量分析)