http://blog.chinaunix.net/uid-8474831-id-3503830.html
http://bbs.chinaunix.net/thread-3761308-1-1.html
Squid自带限速功能delay_pool,但delay_pool的代码太复杂了。这次给大家介绍一种几十行代码的简单实现。
1. Squid下载速度的控制
要了解squid下载速度是怎样控制的,就必须先了解squid的数据下载流程。
下图是squid数据发送的大概流程,略去了一些细节和请求读取/处理。
可以看到,发送数据的流程中可能造成延迟的只有2个环节,comm_write等待客户端收数据,storeClientCopy等待原站或磁盘的数据。其他环节都是无阻塞的,不会造成延迟。
2. Squid下载限速的思路
不难理解,如果要限制一个链接的速度,就必须在下载过程的某个环节加入一些延时。这就面临3个问题
1) 在哪里加
2) 加多少
3) 怎么加
只要这3个问题解决了,那么限速的问题就迎刃而解了。
首先,在哪加?
一般来说,clientWriteComplete进入storeClientCopy之前比较合适。因为这个时候改free掉的内存buf已经在进入clientWriteComplete之前free掉了。如果在comm_write之前加延迟的话,刚刚从磁盘或原站取来的数据要在内存buf里多呆一段时间,会造成squid进程内存占用高。
第二,加多少?
不难计算,假如你限制每秒发送20k数据的话,你就计算一下,这一秒是否已经发够了20k数据?如果已经发够了20k,那么就等到下一秒的开始就可以了。例如现在是1秒200毫秒,就再等待800毫秒即可。
第三,怎么加?
当然不能用sleep加。这会使整个进程卡住的!
正确的加法是用squid的事件机制,eventAdd来加。
3. 实现
1. structs.h,clientHttpRequest结构里面加成员,用于记录上一次发送数据的时间,以及这一秒已经发送了多少数据。
点击(此处)折叠或打开
- dlink_node active;
- squid_off_t maxBodySize;
- STHCB *header_callback; /* Temporarily here for storeClientCopyHeaders */
- StoreEntry *header_entry; /* Temporarily here for storeClientCopyHeaders */
- int is_modified;
- //add by xiaosi start
- struct timeval last_sent_time_tv;
- size_t last_sent_bytes;
- int limit_speed_started;
- //add by xiaosi end
2. client_side.c 里面, clientWriteComplete 函数的前面,加上我们自己实现的方法。
其中SPEED是限制到多少byte/s。limit_speed_event_handler是加了延迟之后的事件回调。不难看出其核心是重新调用storeClientCopy
点击(此处)折叠或打开
- //add by xiaosi start
- #define SPEED 20000
-
- typedef struct
- {
- clientHttpRequest *http;
- squid_off_t seen_offset;
- squid_off_t copy_offset;
- size_t sz;
- } limit_speed_event_arg_t;
-
- CBDATA_TYPE(limit_speed_event_arg_t);
-
- static void limit_speed_event_handler(void *data)
- {
- limit_speed_event_arg_t *ea = data;
- if(!cbdataValid(ea->http) || !cbdataValid(ea->http->sc))
- {
- cbdataUnlock(ea->http);
- cbdataUnlock(ea->http->sc);
- cbdataFree(ea);
- return;
- }
-
- storeClientCopy(ea->http->sc,
- ea->http->entry,
- ea->seen_offset,
- ea->copy_offset,
- ea->sz,
- memAllocate (MEM_STORE_CLIENT_BUF),
- clientSendMoreData,
- ea->http);
- cbdataUnlock(ea->http);
- cbdataUnlock(ea->http->sc);
- cbdataFree(ea);
- }
- //add by xiaosi end
3. 核心操作在这里。在clientWriteComplete里,阻断storeClientCopy的调用。就加在最后一个else里面即可。
其中最主要的操作就是,符合一定条件时,不调用storeClientCopy,而是eventAdd,过一会再继续。
点击(此处)折叠或打开
- } else {
- /* More data will be coming from primary server; register with
- * storage manager. */
- //add by xiaosi start
-
- if(!http->limit_speed_started)
- {
- http->last_sent_time_tv = current_time;
- http->last_sent_bytes = size;
- http->limit_speed_started = 1;
- }
-
- int skip = 0;
- if(http->last_sent_time_tv.tv_sec == current_time.tv_sec)
- {
- if(http->last_sent_bytes + size > SPEED)
- {
- int usec = 1000000 - current_time.tv_usec;
- CBDATA_INIT_TYPE(limit_speed_event_arg_t);
- limit_speed_event_arg_t *ea = cbdataAlloc(limit_speed_event_arg_t);
- ea->http = http;
- ea->seen_offset = http->out.offset;
- ea->copy_offset = http->out.offset;
- ea->sz = STORE_CLIENT_BUF_SZ - http->last_sent_bytes - size + SPEED;
- cbdataLock(ea->http);
- cbdataLock(ea->http->sc);
- eventAdd("limit_speed_copyer", limit_speed_event_handler, ea, ((double)usec) / 1000000, 1);
- http->last_sent_time_tv = current_time;
- http->last_sent_bytes += size;
- skip = 1;
- }
- else
- {
- http->last_sent_bytes += size;
- http->last_sent_time_tv = current_time;
- skip = 0;
- }
- }
- else
- {
- //in the diff second
- http->last_sent_bytes = 0;
- http->last_sent_time_tv = current_time;
-
- skip = 0;
- }
-
- if(!skip)
- //add by xiaosi end
- storeClientCopy(http->sc, entry,
- http->out.offset,
- http->out.offset,
- STORE_CLIENT_BUF_SZ, memAllocate(MEM_STORE_CLIENT_BUF),
- clientSendMoreData,
- http);
- }
4. 还有一点小细节,在 clientHttpRequest 创建出来的时候,初始化一下 limit_speed_started 为 0 。涉及到的函数有 parseHttpRequestAbort 和 parseHttpRequest 。只要加在 cbdataAlloc(clientHttpRequest) 的后面即可
点击(此处)折叠或打开
- //xiaosi add start
- http->limit_speed_started = 0;
- //xiaosi add end
好了。编译并安装,是不是下载成功限制到20k了呢?
另外,回源的速度是由客户端的速度决定的。客户端限定到20k之后,看一看回源速度,是不是也变成20k了呢?
这个例子是比较简单的,限制的速度是写死在代码里的,另外,也不支持只限速符合某些acl的请求。
速度的配置,acl的配置,这些实现相对简单,有squid开发经验的读者可以自行搞定。
有问题,想法或对我的算法的改进,欢迎留言哦!