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开发经验的读者可以自行搞定。
有问题,想法或对我的算法的改进,欢迎留言哦!
阅读(1398) | 评论(0) | 转发(0) |