haproxy实现会话保持

HAProxy系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.htmlphp


1.反向代理为何须要设置cookie

任何一个七层的http负载均衡器,都应该具有一个功能:会话保持。会话保持是保证客户端对动态应用程序正确请求的基本要求。css

仍是那个被举烂了却最有说服力的例子:客户端A向服务端B请求将C商品加入它的帐户购物车,加入成功后,服务端B会在某个缓存区域中记录下客户端A和它的商品C,这个缓存的内容就是session上下文环境。而识别客户端的方式通常是设置session ID(如PHPSESSID、JSESSIONID),并将其做为cookie的内容交给客户端。客户端A再次请求的时候(好比将购物车中的商品下订单)只要携带这个cookie,服务端B就能够从中获取到session ID并找到属于客户端A的缓存内容(商品C),也就能够继续执行下订单部分的代码。html

假如这时使用负载均衡软件对客户端的请求进行负载,就必需要保证能将客户端A的请求再次引导到服务端B,而不能引导到服务端X、服务端Y,由于X、Y上并无缓存和客户端A对应的session内容,也就没法为客户端A下订单。linux

所以,反向代理软件必须具有将客户端和服务端"绑定"的功能,也就是所谓的提供会话保持,让客户端A后续的请求必定转发到服务端B上。nginx

这里讨论的对象是http的动态应用请求,它要求会话保持。更通用地,只要负载均衡软件负载的不是"无状态"的协议或服务,就应该提供会话保持能力,除非它是四层负载软件。git

haproxy提供了3种实现会话保持的方式:github

  • (1).源地址hash;
  • (2).设置cookie;
  • (3).会话粘性表stick-table;

本文只讨论haproxy在设置cookie上实现会话保持的方式,stick-table会话粘性的方式则在下一篇文章中单独讨论。而源地址hash是一种负载调度算法,没什么可讨论的,并且除非实在没办法,不建议使用这种调度算法。web

2.haproxy设置cookie的几种方式

设置cookie的方式是经过在配置文件中使用cookie指令进行配置的。因为haproxy设置cookie的目的是为了将某客户端引导到以前为其服务过的后端服务器上,简单地说,就是和后端某服务器保持联系,所以cookie指令不能设置在frontend段落。redis

首先看一个设置cookie的示例。算法

backend dynamic_servers
    cookie app_cook  insert nocache server app1 192.168.100.22:80 cookie server1 server app2 192.168.100.23:80 cookie server2 

这个示例配置中,cookie指令中指定的是insert命令,表示在将响应报文交给客户端以前,先插入一个属性名为"app_cook"的cookie,这个cookie在响应报文的头部将独占一个"Set-Cookie"字段(由于是插入新cookie),而"app_cook"只是cookie名称,它的值是由server指令中的cookie选项指定的,这里是"server1"或"server2"。

所以,若是这个请求报文分配给后端app2时,响应给客户端的响应报文中haproxy设置的"Set-Cookie"字段的样式为:

Set-Cookie:app_cook=server2; path=/ 

除了insert命令,cookie指令中还支持rewrite和prefix两种设置cookie的方式,这三种cookie的操做方式只能三选一。此外,还提供一些额外对cookie的功能设置。

首先看看指令的语法:

cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ] [ postonly ] [ preserve ] [ httponly ] [ secure ] [ domain <domain> ]* [ maxidle <idle> ] [ maxlife <life> ] 

本文详细分节讨论rewrite、insert、prefix的行为,并在讨论它们的时候会穿插说明indirect、nocache和preserve的行为,若是须要了解其余选项,请自翻官方手册。

下图是后文实验时使用的环境:

其中在后端提供的index.php内容大体以下,主要部分是设置了名为PHPSESSID的cookie

<h1>response from webapp 192.168.100.61</h1> <?php session_start(); echo "Server IP: "."<font color=red>".$_SERVER['SERVER_ADDR']."</font>"."<br>"; echo "Server Name: "."<font color=red>".$_SERVER['SERVER_NAME']."</font>"."<br>"; echo "SESSIONNAME: "."<font color=red>".session_name()."</font>"."<br>"; echo "SESSIONID: "."<font color=red>".session_id()."</font>"."<br>"; ?> 

2.1 cookie insert

insert This keyword indicates that the persistence cookie will have to be inserted by haproxy in server responses if the client did not already have a cookie that would have permitted it to access this server. When used without the "preserve" option, if the server emits a cookie with the same name, it will be remove before processing. For this reason, this mode can be used to upgrade existing configurations running in the "rewrite" mode. The cookie will only be a session cookie and will not be stored on the client's disk. By default, unless the "indirect" option is added, the server will see the cookies emitted by the client. Due to caching effects, it is generally wise to add the "nocache" or "postonly" keywords (see below). The "insert" keyword is not compatible with "rewrite" and "prefix". 

其中大体说明了如下几个意思:

  1. 该关键词表示,haproxy将在客户端没有cookie时(好比第一次请求),在响应报文中插入一个cookie。
  2. 当没有使用关键词"preserve"选项时,若是后端服务器设置了一个和此处名称相同的cookie,则首先删除服务端设置的cookie。
  3. 该cookie只能做为会话保持使用,没法持久化到客户端的磁盘上(由于haproxy设置的cookie没有maxAge属性,没法持久保存,只能保存在浏览器缓存中)。
  4. 默认状况下,除非使用了"indirect"选项,不然服务端能够看到客户端请求时的全部cookie信息。
  5. 因为缓存的影响,建议加上"nocache"或"postonly"选项。

下面使用例子来解释insert的各类行为。

在haproxy以下配置后端。

backend dynamic_group
    cookie app_cook insert nocache server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

当使用浏览器第一次访问http://192.168.100.59/index.php时,响应结果和响应首部内容以下图:

从图中能够知道,此次浏览器的请求分配给了app2,并且响应首部中有两个"Set-Cookie"字段,其中带有PHPSESSID的cookie是app2服务器自身设置的,另外一个是haproxy设置的,其名和其值为"app_cook=app_server2"。

若是客户端再次访问(不关闭浏览器,cookie缓存还在),请求头中将携带该cookie,haproxy发现了该cookie中"app_cook=app_server2"部分,知道这个请求要交给app_server2这个后端。以下图:

这样就实现了会话保持,保证被处理过的客户端能被分配到同一个后端应用服务器上。

注意,客户端在第一次收到响应后就会把cookie缓存下来,之后每次http://192.168.100.59/index.php(根据域名进行判断)都会从缓存中取出该cookie放进请求首部。这样haproxy必定会将其分配给app_server2,除非app_server2下线了。但即便如此,客户端仍是会携带该cookie,只不过haproxy判断app_server2下线后,就为客户端从新分配app_server1,并设置"app_cook=app_server1",该cookie会替换客户端中的"app_cook=app_server2"。下图是app2下线后分配给app1的结果:

但注意,即便分配给了app1,PHPSESSID也不会改变(即app1设置的PHPSESSID无效),由于haproxy判断出这个重名cookie,会删除app1设置的PHPSESSID。所以上图中的PHPSESSID值和以前分配给app2时的PHPSESSID是同样的。

这样一来,app1不是就没法处理该客户端的请求了吗?确实如此,但没办法,除非后端设置了session共享。

若是将配置文件中的cookie名称也设置为PHPSESSID,即后端应用服务器和此处设置的cookie名称相同,那么haproxy将首先将后端的PHPSESSID删除,而后使用本身的值发送给客户端。也就是说,此时将只有一个"Set-Cookie"字段响应给客户端。

backend dynamic_group
    cookie PHPSESSID insert nocache server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

所以,在cookie指令中绝对不能设置cookie名称和后端的cookie名称相同,不然后端就至关于"盲人"。例如此处的PHPSESSID,此时后端虽然认识PHPSESSID是本身发送出去的cookie名称,可是没法获取ID为"app_server1"的session上下文。

若是不配合"indirect"选项,服务端能够看到客户端请求时的全部cookie信息。若是配合"indirect"选项,则haproxy在将请求转发给后端时,将删除本身设置的cookie,使得后端只能看到它本身的cookie,这样对后端来讲,整个过程是彻底透明的,它不知道前面有负载均衡软件。

从新修改haproxy的cookie指令,并修改nginx配置文件中日志格式,在其中加上"$http_cookie"变量,它表示请求报文中的cookie信息。

# haproxy cookie app_cook insert nocache # nginx log_format main '$http_cookie $remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; 

客户端再次访问时,nginx的日志中将记录如下信息(只贴出了前几个字段)。

PHPSESSID=47d0ina2m14gg67ovdf1d972d1; app_cook=app_server1 192.168.100.59 

加上"indirect"选项,再测试。

cookie app_cook insert indirect nocache 

结果以下:

PHPSESSID=bge3bh6sksu2ie91lsp8ep9oi2 192.168.100.59 

若是insert关键字配合"preserve"关键字,那么当后端设置了cookie时,haproxy将强制保留该cookie,不作任何修改。也就是说,若是将haproxy的cookie名称也设置为PHPSESSID,那么客户端第一次请求时收到的响应报文中将只有一个"Set-Cookie"字段,且这个字段的值是后端服务器设置的,和haproxy无关。

当客户端和HAProxy之间存在缓存时,建议将insert配合nocache一块儿使用,由于nocache确保若是须要插入cookie,则可缓存页面将被标记为不可缓存。这一点很重要,由于若是全部cookie都添加到可缓存的页面上,则全部客户都将从中间的缓存层(如cdn端的缓存层)获取页面,而且将共享同一个Cookie,从而致使某台后端服务器接收的流量远远超过其余后端服务器。

2.2 cookie prefix

prefix    This keyword indicates that instead of relying on a dedicated
          cookie for the persistence, an existing one will be completed. This may be needed in some specific environments where the client does not support more than one single cookie and the application already needs it. In this case, whenever the server sets a cookie named <name>, it will be prefixed with the server's identifier and a delimiter. The prefix will be removed from all client requests so that the server still finds the cookie it emitted. Since all requests and responses are subject to being modified, this mode doesn't work with tunnel mode. The "prefix" keyword is not compatible with "rewrite" and "insert". Note: it is highly recommended not to use "indirect" with "prefix", otherwise server cookie updates would not be sent to clients. 

大体意思是:haproxy将在已存在的cookie(例如后端应用服务器设置的)上添加前缀cookie值,这个前缀部分是server指令中的cookie设置的,表明的是服务端标识符。在客户端再次访问时,haproxy将会自动移除这部分前缀,使得服务端只能看到它本身发出的cookie。在一些特殊环境下,客户端不支持多个"Set-Cookie"字段,这时可使用prefix。

使用prefix的时候,cookie指令设置的cookie名必须和后端设置的cookie同样(在本文的环境中是PHPSESSID),不然prefix模式下的haproxy不会对响应报文作任何改变。

backend dynamic_group cookie PHPSESSID prefix server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

以下图:

从后端nginx上的日志上查看haproxy转发过来的请求,能够看到前缀已经被haproxy去掉了。

PHPSESSID=oses71hjr64dl6lputpkmdpg12 192.168.100.59 - - 

2.3 cookie rewrite

rewrite   This keyword indicates that the cookie will be provided by the
          server and that haproxy will have to modify its value to set the server's identifier in it. This mode is handy when the management of complex combinations of "Set-cookie" and "Cache-control" headers is left to the application. The application can then decide whether or not it is appropriate to emit a persistence cookie. Since all responses should be monitored, this mode doesn't work in HTTP tunnel mode. Unless the application behaviour is very complex and/or broken, it is advised not to start with this mode for new deployments. This keyword is incompatible with "insert" and "prefix". 

当后端服务器设置了cookie时,使用rewrite模式时,haproxy将重写该cookie的为后端服务器的标识符。当应用程序须要同时考虑"Set-Cookie"和"Cache-control"字段时,该模式很是方便,由于应用程序能够决定是否应该设置一个为了保持会话的cookie。除非后端应用程序的环境很是复杂,不然不建议使用该模式。

一样,rewrite模式下的haproxy设置的cookie必须和后端服务器设置的cookie名称一致,不然不会作任何改变。

backend dynamic_group cookie PHPSESSID rewrite server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

结果以下图:

可是,当客户端持着"PHPSESSID=app_server1"再去请求服务器时,haproxy将其分配给app1,app1此时收到的cookie将是重写后的,可是app1根本就不认识这个cookie,后面的代码可能所以而失去逻辑没法进行正确处理。

3.haproxy如何使用cookie实现会话保持以及如何忽略会话保持

在haproxy中,haproxy会监控、修改、增长cookie,这都是经过内存中的cookie表实现的。

cookie表中记录了它本身增、改的cookie记录,包括cookie名和对应server的cookie值,经过这个cookie记录,haproxy就能知道请求该交给哪一个后端。

例如,当haproxy插入一个cookie的时候。即在haproxy配置以下后端。

backend dynamic_group
    cookie app_cook insert nocache server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

那么,从客户端第一次请求到第二次请求被处理的整个过程,大体以下:

当haproxy成功修改了响应报文中的cookie时,将在cookie表中插入一条记录,这条记录是维持会话的依据。

其实,经过cookie表保持和后端的会话只是默认状况,haproxy容许"即便使用了cookie也不进行会话绑定"的功能。这能够经过ignore-persist指令来实现。当知足该指令的要求时,表示不将该cookie插入到cookie表中,所以没法实现会话保持,即便haproxy设置了cookie也没用。

例如,在backend中指定以下配置:

backend dynamic_group
    acl  url_dynamic   path_end  -i .php
    ignore-persist if  url_dynamic
    cookie app_cook insert nocache server app1 192.168.100.60:80 cookie app_server1 server app2 192.168.100.61:80 cookie app_server2 

这表示当请求uri以".php"结尾时,将忽略会话保持功能。这表示,对于php结尾的请求,app_cook这个cookie从头至尾都是摆设。

固然,上面的设置是不合理的,更合理的应该是这样的。

acl url_static  path_beg         /static /images /img /css acl url_static path_end .gif .png .jpg .css .js ignore-persist if url_static 

ignore-persist相对的是force-persist,但不建议使用该选项,由于它和option redispatch冲突。

haproxy实现会话保持(2):stick table

 
分类:  网站架构

HAProxy系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html


在上一篇文章中,分析了haproxy如何经过cookie实现会话保持,本文讨论haproxy另外一种实现会话保持的方式:stick table。

1.stickiness和stick table简介

stick table是haproxy的一个很是优秀的特性,这个表里面存储的是stickiness记录,stickiness记录了客户端和服务端1:1对应的引用关系。经过这个关系,haproxy能够将客户端的请求引导到以前为它服务过的后端服务器上,也就是实现了会话保持的功能。这种记录方式,俗称会话粘性(stickiness),即将客户端和服务端粘连起来。

stick table中使用key/value的方式映射客户端和后端服务器,key是客户端的标识符,可使用客户端的源ip(50字节)、cookie以及从报文中过滤出来的部分String。value部分是服务端的标识符。

stick table实现会话粘性的过程以下图:

除了存储key/value实现最基本的粘性,stick table还能够额外存储每一个stickiness记录对应的状态统计数据。好比stickiness记录1目前创建了多少和客户端的链接、平均创建链接的速度是多少、流入流出了多少字节的数据、创建会话的数量等等。

stick table能够在"双主模型"下进行复制(replication)。只要设置好对端haproxy节点,haproxy就会自动将新插入的、刚更新的记录经过TCP链接推送到对端节点上。这样一来,粘性记录不会丢失,即便某haproxy节点出现了故障,其余节点也能将客户端按照粘性映射关系引导到正确的后端服务器上。并且每条stickiness记录占用空间都很小(平均最小50字节,最大166字节,由是否记录额外统计数据以及记录多少来决定占用空间大小),使得即便在很是繁忙的环境下在几十个节点之间推送都不会出现压力瓶颈和网络阻塞(能够按节点数量、stickiness记录的大小和平均并发量来计算每秒在网络间推送的数据流量)。

此外,stick table还能够在haproxy重启时,在新旧两个进程间进行复制,这是本地复制。当haproxy重启时,旧haproxy进程会和新haproxy进程创建TCP链接,将其维护的stick table推送给新进程。这样新进程不会丢失粘性信息,和其余节点也能最大程度地保持同步,使得其余节点只须要推送该节点重启过程当中新增长的stickiness记录就能彻底保持同步。

2.使用stick table

下图是本文测试时的环境:

2.1 建立stick table

首先看建立stick table的语法:

stick-table type {ip | integer | string [len <length>] | binary [len <length>]} size <size> [expire <expire>] [nopurge] [peers <peersect>] [store <data_type>]* 

其中

  • type ip | integer | string:使用什么类型的key做为客户端标识符。能够是客户端的源IP,能够是一个整数ID值,也能够是一段从请求报文或响应报文中匹配出来的字符串。
  • size:表中容许的最大stickiness记录数量。单位使用k、m和g表示,分别表示102四、2^20和2^30条记录。
  • expire:stickiness记录的过时时长。当某记录被操做后,过了一段时间就会过时,过时的记录会自动从stick table中移除,释放表空间。
  • nopurge:默认状况下,当表满后,若是还有新的stickiness记录要插入进来,haproxy会自动将一部分老旧的stickiness记录flush掉,以释放空间存储新纪录。指定nopurge后,将不进行flush,只能经过记录过时来释放表空间,所以该选项必须配合expire选项同时使用。
  • peers:指定要将stick table中的记录replication到对端haproxy节点。
  • store:指定要存储在stick table中的额外状态统计数据。其中表明后端服务器的标识符server ID(即key/value的value部分)会自动插入,无需显式指定。

注意,每一个后端组只能创建一张stick table,每一个stick table的id或名称等于后端组名。例如在backend static_group后端建立stick table,则该表的id为"static_group"。也有特殊方法创建多张,但无必要,可翻官方手册找方法。

例如,建立一个以源IP地址为key的stick table,该表容许100W条记录,5分钟的记录过时时长,而且不记录任何额外数据。

stick-table type ip size 1m expire 5m 

这张表因为没有记录额外的统计数据,每条stickiness记录在内存中只占用50字节左右的空间,表满后整张表在内存中占用50MB(2^20*50/1024/1024=50MB)。看上去很大,但检索速度是极快的,彻底不用担忧性能问题。

若是还要存储和客户端创建的链接数量计数器(conn_cnt),则:

stick-table type ip size 1m expire 5m store conn_cnt 

conn_cnt占用32个bit位,即4字节,所以每条stickiness记录占用54字节,100W条记录占用54M内存空间。

2.2 查看stick table

haproxy没有直接的接口能够显示stick table的相关信息,只能经过stats socket进行查看。该指令表示开启一个本地unix套接字监听haproxy的信息,经过这个套接字能够查看haproxy的不少信息,且能动态调整haproxy配置。

首先在haproxy的配置文件中开启"stats socket"状态信息,以下:

global stats socket /var/run/haproxy.sock mode 600 level admin stats timeout 2m 

默认stats timeout的过时时长为10s,建议设置长一点。上面还设置了socket的权限级别,表示能访问(600)这个套接字的人具备全部权限(admin)。level还有两种权限级别更低一点的值"read"和"operator"(默认),前者表示只有读取信息的权限,不能设置或删除、清空某些信息,后者表示具有读和某些设置权限。

本地套接字监听haproxy后,能够经过"socat"工具(socket cat,很强大的工具,在epel源中提供)从套接字来操做haproxy。

# 方式一:直接传递要执行的操做给套接字 echo "help" | socat unix:/var/run/haproxy.sock - # 方式二:进入交互式模式,而后在交互式模式下执行相关操做 socat readline unix:/var/run/haproxy.sock 

若是要监控某些状态信息的实时变化,可使用watch命令。

watch -n 1 '"echo show table" | socat unix:/var/run/haproxy.sock -' 

haproxy支持如下列出的全部操做命令:

[root@xuexi ~]# echo "help" | socat unix:/var/run/haproxy.sock - help : this message prompt : toggle interactive mode with prompt quit : disconnect show tls-keys [id|*]: show tls keys references or dump tls ticket keys when id specified set ssl tls-key [id|keyfile] <tlskey>: set the next TLS key for the <id> or <keyfile> listener to <tlskey> set maxconn global : change the per-process maxconn setting set rate-limit : change a rate limiting value set timeout : change a timeout setting show env [var] : dump environment variables known to the process show resolvers [id]: dumps counters from all resolvers section and associated name servers add acl : add acl entry clear acl <id> : clear the content of this acl del acl : delete acl entry get acl : report the patterns matching a sample for an ACL show acl [id] : report available acls or dump an acl's contents add map : add map entry clear map <id> : clear the content of this map del map : delete map entry get map : report the keys and values matching a sample for a map set map : modify map entry show map [id] : report available maps or dump a map's contents show pools : report information about the memory pools usage show sess [id] : report the list of current sessions or dump this session shutdown session : kill a specific session shutdown sessions server : kill sessions on a server clear counters : clear max statistics counters (add 'all' for all counters) show info : report information about the running process show stat : report counters for each proxy and server show errors : report last request and response errors for each proxy clear table : remove an entry from a table set table [id] : update or create a table entry's data show table [id]: report table usage stats or dump this table's contents disable frontend : temporarily disable specific frontend enable frontend : re-enable specific frontend set maxconn frontend : change a frontend's maxconn setting show servers state [id]: dump volatile server information (for backend <id>) show backend : list backends in the current running config shutdown frontend : stop a specific frontend disable agent : disable agent checks (use 'set server' instead) disable health : disable health checks (use 'set server' instead) disable server : disable a server for maintenance (use 'set server' instead) enable agent : enable agent checks (use 'set server' instead) enable health : enable health checks (use 'set server' instead) enable server : enable a disabled server (use 'set server' instead) set maxconn server : change a server's maxconn setting set server : change a server's state, weight or address get weight : report a server's current weight set weight : change a server's weight (deprecated) 

其中和stick table相关的命令有:

clear table    : remove an entry from a table
  set table [id] : update or create a table entry's data show table [id]: report table usage stats or dump this table's contents 

例如:

# on haproxy backend static_group stick-table type ip size 5k expire 1m backend dynamic_group stick-table type ip size 5k expire 1m [root@xuexi ~]# echo "show table" | socat unix:/var/run/haproxy.sock - # table: static_group, type: ip, size:5120, used:0 # table: dynamic_group, type: ip, size:5120, used:0 

本文只是引入stats socket的操做方式,至于各命令的做用,参见官方手册:http://cbonte.github.io/haproxy-dconv/1.7/management.html#9.3

2.3 使用客户端源IP做为客户端标识符

配置文件部份内容以下:

frontend http-in
    bind             *:80
    mode             http
    log              global

    acl url_static   path_beg  -i /static /images /stylesheets
    acl url_static   path_end  -i .jpg .jpeg .gif .png .ico .bmp .html

    use_backend      static_group   if url_static
    default_backend  dynamic_group

backend dynamic_group
    stick-table type ip size 5k expire 1m
    stick on src
    balance roundrobin
    option http-server-close
    option httpchk  GET /index.php
    http-check expect status 200 server app1 192.168.100.60:80 check rise 1 maxconn 3000 server app2 192.168.100.61:80 check rise 1 maxconn 3000 backend static_group stick-table type ip size 5k expire 1m stick on src balance roundrobin option http-keep-alive http-reuse safe option httpchk GET /index.html http-check expect status 200 server staticsrv1 192.168.100.62:80 check rise 1 maxconn 5000 server staticsrv2 192.168.100.63:80 check rise 1 maxconn 5000 

上面的配置中,设置了acl,当知足静态访问时,使用static_group后端组,不然使用dynamic_group后端组。在两个后端组中,都设置了stick-tablestick on,其中stick on是存储指定内容,并在请求到达时匹配该内容,它的具体用法见后文。只有配置了stick on后,haproxy才能根据匹配的结果决定是否存储到stick table中,以及如何筛选待分派的后端。

总之,上面的两个后端组都已经指定了要向stick table中存储源ip地址做为key。当客户端请求到达时,haproxy根据调度算法分配一个后端,但请求交给后端成功后,Haproxy当即向stick table表中插入一条stickiness记录。当客户端请求再次到达时,haproxy发现能匹配源ip,因而按照该stickiness记录,将请求分配给对应的后端。

如下是分别使用两台机器测试192.168.100.59/index.html192.168.100.59/index.php后,stick table记录的数据。

[root@xuexi ~]# echo "show table static_group" | socat unix:/var/run/haproxy.sock - # table: static_group, type: ip, size:5120, used:2 0x1bc0024: key=192.168.100.1 use=0 exp=48013 server_id=2 0x1bbec14: key=192.168.100.59 use=0 exp=27994 server_id=1 [root@xuexi ~]# echo "show table dynamic_group" | socat unix:/var/run/haproxy.sock - # table: dynamic_group, type: ip, size:5120, used:2 0x1bc00c4: key=192.168.100.1 use=0 exp=53686 server_id=2 0x1bbeb04: key=192.168.100.59 use=0 exp=34309 server_id=1 

其中server_id默认是从1自增的,它能够在server指令中用"id"选项进行显式指定。例如:

server staticsrv1 192.168.100.62:80 id 111 check rise 1 max conn 6500 

若是,在使用stickiness的同时,haproxy还设置了cookie,谁的优先级高呢?

2.4 使用cookie做为客户端标识符

通常会话保持考虑的对象是应用程序服务器,所以此处咱们忽略后端的静态服务器,只考虑php应用服务器。在dynamic_group两个后端server app1和app2的index.php中分别设置好PHPSESSID做为测试。例如:

<h1>response from webapp 192.168.100.60</h1> <?php session_start(); echo "Server IP: "."<font color=red>".$_SERVER['SERVER_ADDR']."</font>"."<br>"; echo "Server Name: "."<font color=red>".$_SERVER['SERVER_NAME']."</font>"."<br>"; echo "SESSIONNAME: "."<font color=red>".session_name()."</font>"."<br>"; echo "SESSIONID: "."<font color=red>".session_id()."</font>"."<br>"; ?> 

cookie是string的一种特殊状况,所以建立stick table时,指定type为string。如下是在haproxy上的配置:

backend dynamic_group
    stick-table type string len 32 size 5k expire 2m
    stick on req.cook(PHPSESSID)
    stick store-response res.cook(PHPSESSID)
    balance roundrobin
    option http-server-close
    option httpchk  GET /index.php
    http-check expect status 200 server app1 192.168.100.60:80 check rise 1 maxconn 3000 server app2 192.168.100.61:80 check rise 1 maxconn 3000 

stick store-response指令表示从响应报文中匹配某些数据出来,而后存储到stick table中,此处表示截取响应报文中"Set-Cookie"字段中名为"PHPSESSID"的cookie名进行存储。stick on req.cook(PHPSESSID)表示从请求报文的"Cookie"字段中匹配名为PHPSESSID的cookie。若是能和存储在stick table中的PHPSESSID匹配成功,则表示该客户端被处理过,因而将其引导到对应的后端服务器上。严格地说,这里不是识别客户端,而是经过PHPSESSID来识别后端。

某次浏览器的请求获得以下结果:以后每次请求也都是分配到192.168.100.61上。注意,不要使用curl命令来测试,由于这里是根据PHPSESSID匹配的,curl每次接收到响应后进程就直接退出了,没法缓存cookie,所以curl每次请求都至关于一次新请求。

在haproxy上查看stick table。

[root@xuexi ~]# echo "show table dynamic_group" | socat unix:/var/run/haproxy.sock - # table: dynamic_group, type: string, size:5120, used:1 0x12163d4: key=g5ossskspc96aecp4hvmsehoh4 use=0 exp=50770 server_id=2 

2.5 使用string做为客户端标识符

上面的cookie是string的一种特殊用法。使用string筛选内容进行存储,灵活性很是大,能够经过它实现某些复杂、特殊的需求。

例如,从请求报文中截取Host字段的值做为key存储起来。

backend dynamic_group
    stick-table type string size 5k expire 2m
    stick on req.hdr(Host)
    balance roundrobin
    option http-server-close
    option httpchk  GET /index.php
    http-check expect status 200 server app1 192.168.100.60:80 check rise 1 maxconn 3000 server app2 192.168.100.61:80 check rise 1 maxconn 3000 

找一台linux客户端使用curl进行测试,发现全部请求都将引导到同义后端服务器上。

[root@xuexi ~]# for i in `seq 1 5`;do grep "response" <(curl 192.168.100.59/index.php 2>/dev/null);done <h1>response from webapp 192.168.100.60</h1> <h1>response from webapp 192.168.100.60</h1> <h1>response from webapp 192.168.100.60</h1> <h1>response from webapp 192.168.100.60</h1> <h1>response from webapp 192.168.100.60</h1> 

查看stick table也只能看到一条记录,并且其key部分正是捕获到的Host字段的值。

[root@xuexi ~]# echo "show table dynamic_group" | socat unix:/var/run/haproxy.sock - # table: dynamic_group, type: string, size:5120, used:1 0xf0d904: key=192.168.100.19 use=0 exp=46308 server_id=1 

2.6 stick on、stick match、stick store

在前面haproxy的配置中出现过stick onstick store-response,除此以外,还有两个指令stick matchstick store-request。语法以下:

stick store-request <pattern> [table <table>] [{if | unless} <condition>] stick store-response <pattern> [table <table>] [{if | unless} <condition>] stick match <pattern> [table <table>] [{if | unless} <cond>] stick on <pattern> [table <table>] [{if | unless} <condition>] 

其中stick store指令是从请求或响应报文中截取一部分字符串出来,并将其做为stickiness的key存储到stick table中。例如:

# 截取响应报文中名为PHPSESSID的cookie做为key stick store-response res.cook(PHPSESSID) # 截取请求报文中Host字段的值做为key stick store-request req.hdr(Host) # 对请求的源ip地址进行匹配,若不是兄弟网络中的主机时,就写入stick table中,且该table名为dynamic_group stick store-request src table dynamic_group if !my_brother 

stick match是将请求报文中的指定部分和stick table中的记录进行匹配。例如:

# 截取请求报文中名为PHPSESSID的cookie,去stick table中搜索是否存在对应的记录 stick match req.cook(PHPSESSID) # 当源IP不是本机时,去dynamic_group表中搜索是否有能匹配到源IP地址的记录 stick match src table dynamic_group if !localhost 

stick on等价于stick store+stick match,是它们的简化写法。例如:

# 存储并匹配源IP地址 stick on src #1 = #2 + #3 stick match src #2 stick store-request src #3 # 存储并匹配源IP地址 stick on src table dynamic_group if !localhost #1 = #2 + #3 stick match src table dynamic_group if !localhost #2 stick store-request src table dynamic_group if !localhost #3 # 存储并匹配后端服务器设置的PHPSESSID stick on req.cook(PHPSESSID) #1 +#2 = #3 + #4 stick store-response res.cook(PHPSESSID) #2 stick match req.cook(PHPSESSID) #3 stick store-response res.cook(PHPSESSID) #4 

2.7 使用stick table统计状态信息

stick table除了存储基本的粘性信息,还能存储额外的统计数据,这实际上是haproxy提供的一种"采样调查"功能。它能采集的数据种类有如下几种:

每一个stickiness记录中能够同时存储多个记录类型,使用逗号分隔或屡次使用store关键字便可。但注意,后端服务器的server id会自动记录,其它全部额外信息都须要显式指定。

须要注意,每一个haproxy后端组只能有一张stick table,但却不建议统计太多额外的状态信息,由于每多存一个类型,意味着使用更多的内存。

若是存储全部上述列出的数据类型,须要116字节,100W条记录要用116M,这不是能够忽略的大小。此外还有50M的key,共166M。

例以下面的示例中,使用了通用计数器累计,并记录了每30秒内的平均链接速率。

stick-table type ip size 1m expire 5m store gpc0,conn_rate(30s) 

 

转载请注明出处:http://www.cnblogs.com/f-ck-need-u/p/8558514.html

相关文章
相关标签/搜索