全部博文(356)
分类: LINUX
2018-11-29 14:22:48
原文地址:Squid的长连接,短连接,半连接 作者:xiaosi_sw
小斯教你玩squid第4弹,应该是2013年春节前无心干活期间写下的龙年最后一篇博客。
先说说长/短连接的问题,所谓长连接,就是指在一个tcp连接上服务多个http请求。这样做的好处是,可以避免频繁的tcp建连/断开的开销,提高响应速度,提高服务器性能等。
Squid当然也对长连接有较好的支持。而且squid在客户端与回源端同时都支持长连接。配置中与长连接相关的选项有以下几个:
client_persistent_connections #是否支持客户端的长连接 server_persistent_connections #是否支持原站端的长连接 persistent_connection_after_error #在给客户端发过4xx/5xx之后是否继续保持长连接 persistent_request_timeout #长连接的超时时间(下一个请求多少秒还不到来,就断开) |
并不是每一个连接都可以在squid里作为长连接的。如果把一些不该保持的连接保持了,可能会消耗squid宝贵的资源,如文件描述符,内存等。客户端的连接要经过一个判断流程来决定是否可以与这个客户端保持长连接。
当然,squid为客户端保持了长连接,但客户端可能会“不领情”,而直接关闭连接。这样的话,squid会在clientReadRequest中检测到客户端关闭连接,然后自己也关闭连接。
另外,即使在squid决定于客户端保持长连接之后,也有一些特殊情况使得squid不得已关闭连接的。包括:
1. 客户端socket读写出现报错
2. 回源连接中断,导致原站给的内容长度达不到Content-Length
3. 响应body长度大于reply_body_max_size
等等
由于squid要向很多个原站回源,因此回源的长连接的管理不像客户端长连接那么简单。
例如,回原站A的连接,就不能给原站B的请求用,但可以给其他的回原站A的请求用。
因此,squid内部管理者一个回源长连接hash表,用每一个表项(struct _pconn)是一个动态数组。用请求的 域名 + 端口 + peer_ip(如果有peer的话) + 客户端ip/端口(如果连接带认证信息的话)来计算hash的key。
struct _pconn { hash_link hash; /* must be first */ int *fds; int nfds_alloc; int nfds; }; |
一个回源请求开始时,squid会按照一定的流程判断这个请求是否可以使用先前的请求留下的连接,如果可以用,就从上述hash表中找到相应的表项,并从表项的动态数组fds的最后拿出一个fd来给这个请求用,同时nfds--。
一个回源请求结束后,squid会按照一定的流程判断出来这个请求所使用的连接是否可以被后面的请求所复用,如果可以,就把这个fd放到上述hash表的相应表项的动态数组中fds的最后,同时nfds++。
判断一个请求是否可以复用其他请求留下的长连接比较简单,只要是以下几种method就可以
GET HEAD PUT DELETE OPTIONS TRACE |
根据rfc2616,其中GET和HEAD是安全(Safe)方法,无论重试多少次都不会对原站造成影响。而PUT,DELETE,OPTIONS,TRACE是等幂(Idempotent)方法,也就是说,执行1次和执行多次效果是一样的。
而其他方法,如POST就是非安全非等幂方法。如果客户端只发起了1次请求,而squid由于原站响应等问题重试了多次的话,会对执行结果产生影响。
Squid不允许非安全/非等幂方法复用长连接的原因是什么?我想了很久,最终在RFC2616第8.1.4节找到了一个说法:
这表明客户端,服务器与代理必须有能力从连接的异步终止事件中恢复。只要请求是等幂的(见9.1.2节),客户端软件应该能重新打开传输层连接并重试传输遗弃的请求序列而不需要用户进行去交互。对非等幂方法的请求就不能自动重试请求,尽管用户代理(user agent)可能提供一个人工操作去重试这些请求。用户代理(user-agent)对应用程序语义理解的确认应该替代用户的确认。如果再次重试请求序列失败,那么就不能再进行自动重试请求了。 |
非等幂方法必须由客户人工操作发起重试。但似乎不代表不能利用长连接。
对于这个问题,我个人的猜想是,既然协议这么规定,那么原站server的实现可能就不允许POST请求从一个旧连接上发送过来。如果squid允许POST利用旧连接,就可能造成POST请求被原站拒绝等问题。所以squid干脆每次POST都使用新连接了。
判断一个请求是否可以留下它的的长连接的流程如下
其实半连接这个概念,与长连接/短连接没有什么联系。而是指squid对于tcp半连接的支持。因为都带“连接”二字,容易被搞混,所以放在这里一起说。
半连接的配置是half_closed_clients,默认是on。
半连接的概念,就是指客户端调用过close并发来fin包之后,客户端的tcp由ESTABLISHED进入FIN_WAIT_1,而squid收到fin并发ack进入CLOSE_WAIT,客户端收到ack进入FIN_WAIT_2。
这时候如果half_closed_clients on的话,squid会继续将请求处理完,并将数据给客户发完,然后向客户端发fin,进入LAST_ACK,客户端收到fin,发ack并进入TIME_WAIT,继而CLOSED。最后squid收到最后一个ack进入CLOSED。
如果half_closed_clients off的话,squid将不再继续处理请求,直接向客户端发fin,大家一起关闭。
客户端发来fin包,只是代表客户端不想再给squid发数据了,并不代表不再从squid接收数据。客户端调用close之后,肯定不会再在这个socket上调用write了,但可能会继续调用read来读数据。一些客户端软件发完了请求之后就会调close的。
当然,使用半连接进行通信的客户端是少数。大多数客户端调用close之后,很可能不再接受数据了。这时候如果squid的half_closed_clients on,那么squid会在试图给客户端发数据时被reset掉,然后再关闭连接。
half_closed_clients的意义,首先是能够支持那种使用半连接进行通信的,即发完请求就close的客户端软件。其次,如果这个请求是miss的,还能趁着这次机会把object hit住,下次来同样的请求时可以直接从本地给出响应。但副作用也是有的,如果半连接上发来大量的hit请求,而且客户端也不再读取squid给出的响应,那么squid可能会有大量的cpu和磁盘资源损耗在处理这些请求上,对性能是一种损失。
我本人在平时使用squid中,都是关闭这个选项的。