HAProxy 是一款性能优异的高可用proxy 软件,在抽空整理其源代码与文档之后,感叹作者编码水平之高,项目注释之清晰、文档之齐全,阅读代码过程中让人欲罢不能,往往回首已经2、3个小时过去了。特此记录学习路线在此,供其他朋友借鉴。
你值得在HAProxy的源码中徜徉200个小时!
你可以在HAProxy的源代码中学习到如何写一个优秀的高性能软件!
你可以在HAProxy的源代码中学习到各种内存模型数据结构的使用!
…
HAProxy 是一个巨大的宝藏,值得真对其各种特性反复阅读分析!
万事开头难,如果要了解HAProxy的背景知识以及官方文档,可以访问
官方网站 HAProxy 了解官方文档,从
HAProxy git: clone 代码到本地阅读、分析
首先从git上 clone代码到本地之后,只需要关注以下几个目录:
- doc //文档目录,值得所有文档读一遍
- internals //实现原理
- design-thoughts //设计中的一些思路想法
- include
- proto //协议相关,包括proto_tcp proto_http proto_udp session等
- common
- import
- types //对应entites.pdf 中的HAProxy 各种实体定义
- src //所有的c文件
建议从 doc/intro.txt开始,intro.txt 主要介绍LoadBanlance的基本原理、HAProxy能干啥不能干啥,HAProxy 总体在各方面的设计思想以及具体的使用指引
management.txt 主要讲如何多节点部署组成集群,如何实现无缝升级
configuration.txt 主要讲解配置文件的使用
architechure LB的最佳架构以及如何与其他第三方产品交互
proxy-proto 讲解什么是proxy 协议以及需要注意的要点
HAProxy支持的负载均衡算法以及适用的场景如下:
算法 | 场景 | 特点 |
---|---|---|
round-robin | 短链接 | 服务器轮流接收 |
last-conn | 长链接 | 在来源服务比较少的情况下,可能会一直都在一台机器上 |
source | SSL/Terminal | 来源ip,在来源较少的或者有上层代理的情况下有可能都压在一台机器上 |
URI | http cache | 根据http URI 设置 |
HDR | http header | 根据http header请求,适用于蓝绿、金丝雀等测试 |
first | short_lived_virtual_machine | 根据第一次访问的分配,适用于快速释放的虚拟机后端 |
HAProxy 是使用纯C实现的面相对象设计的典范~至少我是这么认为的。为什么这么说,因为以下几点:
1、HAProxy 在整体设计中,完全是面相对象的设计,可以看下HAProxy的entities 图设计
2、HAProxy 的代码实现中,所有的所有的entities基本都是头文件中申明 struts中包含callback 函数,然后在.c文件中实现对应的callback函数,对应到一些其他的面向对象语言中,就是class
3、在HAProxy的源代码中,使用了部分g++的特性,比如 attribute(“constructor”)等
4、HAProxy在实现多种网络协议的解析和处理的时候、设计和抽象Task 、Connector等多种组件等时候,基本都是面相对象的设计,所有的组件都设计成了相同的接口,在filter的设计中,更是标准的接口化、插件化设计
HAProxy 的主要实体见下图 其主要的对象都在 include/types/中声明,具体的用途方法在entities.txt ,主要的对象有:
思路:按照自己对图的理解,按照总分总的结构讲,先总体纵览,然后具体组件分析,最后以事件序列进行汇总
HAProxy本质上来说就是一个单线程、事件驱动的流分发器,其中蓝色部分的Session 和Stream是HAProxy 最主要的对象。整体上来说HAProxy主要思想就是流设计,为每个session的前端connection和对应的backend target connection 建立一个双向流,通过poll_event 从前端的connection fd中拉取数据,经过req channel 到判断target 之后选择 backend的connection fd 写出。后端的Response 数据,经过后端的connection、res channel 反馈给前端frontend.如果是http 协议的话另外增加最下方的http 交易 http txn 管理。保证在后端无响应的情况下返回相应的错误代码。
HAProxy 在启动的时候读取配置文件中 frontend 下的listener 配置,在启动的时候解析配置文件、实例化 Listener监听,每个HAProxy必须有一个以上Listener。
Listener 是HAProxy对于自身启动的Socket监听做的抽象,实现Socket端口的bind、accept,之后的应用层协议握手、session管理、数据处理分发由Session、Stream 完成。
Initiator:当Listener有网络连接进来的时候,创建Initiator,或者由后端主动发起connect 其他Server的后端Task发起,Initiator 的主要作用是链接listener,回调上一层的accept函数,实现session、stream的创建
Connection:是HAProxy对网络链接的抽象,包括监听的端口和作为客户端链接后段而随机打开的端口,都按照UNIX网络抽象为文件描述符fd,为了管理fd ,HAProxy建立了一个fdtab的list进行fd的管理。
Session: Session主要是为了做状态的关联管理。我们常见的session 一般来自于http session,比如区分用户状态等。而HAProxy 中为了实现在向后端分发请求的时候不丢session,也需要对session进行管理。 HAProxy中Session对象包含:stkctr、Listener、frontend、origin 四个主要对象,其中stkctr 主要是session标示字段,一般是http header;frontend 按照配置文件生成,frontend 在启动时会创建具体等listener;网络请求建立之后,生成了 connection、stream对象,session对象通过origin指针,指向具体的connection。
stream 是HAProxy中的核心对象,简而言之 要构成一个stream ,至少要两点、一线。两点是指一个流的两端,在HAProxy中不管是Listener accept之后创建的链接还是HAproxy 根据配置文件主动connect 创建的链接,HAProxy 都抽象为connection对象以及对应等fd READ|WRITE操作,Stream中,使用两个Stream Interface对象 si[2] ,si[0] 用做指向输入,si[1] 指向输出流,Stream 中创建了两个单向的 channel ,分别处理request 、response 数据,在两个channel之上增加filter 来实现具体的过滤器。
store ?:TODO 待补充
target ?:TODO 待补充
task : 负责处理相关的定时任务,比如server 的connection 的心跳检查、断线重连、超时处理等。
http txn是http协议代理中的特有对象,依附与req channel与res channel ,主要是为了保持http request-responses的交易完整性,比如在后段无响应的情况下主动返回404 错误等, 为了保持http txn的跟踪,需要hdridx (http头保存)、http 验证信息状态等,另外为了实现txn的跟踪,还需要保留cookie信息、uri信息等。
HAProxy 的主流程参考haproxy.c 中的 main函数,具体流程:
int main(int argc,char ** argv){
1. 调用init(),实现配置文件读取、frontend、backend、listener等对象的创建,辅助的内存池、task创建
2. sigquit、USR1、HUP、PIPE等操作系统信号回调函数设置,其中PIPE信号不做处理
3. 设置ulimits\maxmem 等内核参数
4. while(retry>0): 最多100次重试 StartProxy 启动代理,之后检查是否Listener是否Ready,如果没有则报错退出
5. proto_bind_all 做proto的accept绑定,之后listener的回调函数指向具体的proto
6. SIGOUT\TTIN信号处理函数注册、写pid文件
7. chroot\setgid pid Deamon 处理,关掉不必要的stdin、stdout,
8. proxy 与process unbind ,peers 与process unbind //TODO 待补充
9. proto_enable_all 启动所有协议的accept
10. run_poll_loop 进入主事件循环,主要是session_accept_fd创建session\stream 链接握手处理、process_stream 完成数据的stream流转
11.deinit\exit(0) 一旦退出主事件循环,则整体程序退出
}
haproxy 的初始化流程比较冗长,涉及了配置文件读取、日志配置初始化、默认数据结构(task、stream、session、connection)初始化、对象创建、协议对象创建、lua脚本解释器创建、输入参数的解析处理、server state读取、server初始化、filter创建、初始化poller 等,最后清理内存。详见 haproxy.c init() 函数
HAProxy 处理新链接的主要逻辑步骤包括:
1. 接收配置文件里等frentend 配置信息,创建listener监听
2. frontend预处理、包括阻塞设置、修改具体的请求头等
3. 传递给后端等server实体
4. 后端预处理
5. 决定发给后段的哪一个server
6. 后端对response 进行过滤处理
7. 前端对response 数据处理
8. 向监控发送日志log
9. 如果是http则从第二步继续循环(http 有keeplive等情况),否则关闭链接。
具体的函数调用过程:
//todo
//Todo: 待续