Chinaunix首页 | 论坛 | 博客
  • 博客访问: 606560
  • 博文数量: 356
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2287
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-08 17:08
文章分类

全部博文(356)

文章存档

2023年(3)

2022年(7)

2021年(33)

2020年(47)

2019年(36)

2018年(221)

2017年(1)

2015年(1)

2013年(7)

我的朋友

分类: LINUX

2018-11-29 14:28:56

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,那么就等到下一秒的开始就可以了。例如现在是1200毫秒,就再等待800毫秒即可。

第三,怎么加?

当然不能用sleep加。这会使整个进程卡住的!

正确的加法是用squid的事件机制,eventAdd来加。

3. 实现

1.      structs.hclientHttpRequest结构里面加成员,用于记录上一次发送数据的时间,以及这一秒已经发送了多少数据。


点击(此处)折叠或打开

  1.     dlink_node active;
  2.     squid_off_t maxBodySize;
  3.     STHCB *header_callback; /* Temporarily here for storeClientCopyHeaders */
  4.     StoreEntry *header_entry; /* Temporarily here for storeClientCopyHeaders */
  5.     int is_modified;
  6.     //add by xiaosi start
  7.     struct timeval last_sent_time_tv;
  8.     size_t last_sent_bytes;
  9.     int limit_speed_started;
  10.     //add by xiaosi end

2.      client_side.c里面,clientWriteComplete函数的前面,加上我们自己实现的方法。


其中SPEED是限制到多少byte/slimit_speed_event_handler是加了延迟之后的事件回调。不难看出其核心是重新调用storeClientCopy

 

点击(此处)折叠或打开

  1. //add by xiaosi start
  2. #define SPEED 20000
  3.  
  4. typedef struct
  5. {
  6.         clientHttpRequest *http;
  7.         squid_off_t seen_offset;
  8.         squid_off_t copy_offset;
  9.         size_t sz;
  10. } limit_speed_event_arg_t;
  11.  
  12. CBDATA_TYPE(limit_speed_event_arg_t);
  13.  
  14. static void limit_speed_event_handler(void *data)
  15. {
  16.         limit_speed_event_arg_t *ea = data;
  17.         if(!cbdataValid(ea->http) || !cbdataValid(ea->http->sc))
  18.         {
  19.                 cbdataUnlock(ea->http);
  20.                 cbdataUnlock(ea->http->sc);
  21.                 cbdataFree(ea);
  22.                 return;
  23.         }
  24.  
  25.         storeClientCopy(ea->http->sc,
  26.                         ea->http->entry,
  27.                         ea->seen_offset,
  28.                         ea->copy_offset,
  29.                         ea->sz,
  30.                         memAllocate (MEM_STORE_CLIENT_BUF),
  31.                         clientSendMoreData,
  32.                         ea->http);
  33.         cbdataUnlock(ea->http);
  34.         cbdataUnlock(ea->http->sc);
  35.         cbdataFree(ea);
  36. }
  37. //add by xiaosi end



3.      核心操作在这里。在clientWriteComplete里,阻断storeClientCopy的调用。就加在最后一个else里面即可。

        其中最主要的操作就是,符合一定条件时,不调用storeClientCopy,而是eventAdd,过一会再继续。


点击(此处)折叠或打开

  1. } else {
  2.          /* More data will be coming from primary server; register with
  3.          * storage manager. */
  4.         //add by xiaosi start
  5.  
  6.         if(!http->limit_speed_started)
  7.         {
  8.                 http->last_sent_time_tv = current_time;
  9.                 http->last_sent_bytes = size;
  10.                 http->limit_speed_started = 1;
  11.         }
  12.  
  13.         int skip = 0;
  14.         if(http->last_sent_time_tv.tv_sec == current_time.tv_sec)
  15.         {
  16.                 if(http->last_sent_bytes + size > SPEED)
  17.                 {
  18.                         int usec = 1000000 - current_time.tv_usec;
  19.                         CBDATA_INIT_TYPE(limit_speed_event_arg_t);
  20.                         limit_speed_event_arg_t *ea = cbdataAlloc(limit_speed_event_arg_t);
  21.                         ea->http = http;
  22.                         ea->seen_offset = http->out.offset;
  23.                         ea->copy_offset = http->out.offset;
  24.                         ea->sz = STORE_CLIENT_BUF_SZ - http->last_sent_bytes - size + SPEED;
  25.                         cbdataLock(ea->http);
  26.                         cbdataLock(ea->http->sc);
  27.                         eventAdd("limit_speed_copyer", limit_speed_event_handler, ea, ((double)usec) / 1000000, 1);
  28.                         http->last_sent_time_tv = current_time;
  29.                         http->last_sent_bytes += size;
  30.                         skip = 1;
  31.                 }
  32.                 else
  33.                 {
  34.                         http->last_sent_bytes += size;
  35.                         http->last_sent_time_tv = current_time;
  36.                         skip = 0;
  37.                 }
  38.         }
  39.         else
  40.         {
  41.                 //in the diff second
  42.                 http->last_sent_bytes = 0;
  43.                 http->last_sent_time_tv = current_time;
  44.  
  45.                 skip = 0;
  46.         }
  47.  
  48.         if(!skip)
  49.         //add by xiaosi end
  50.         storeClientCopy(http->sc, entry,
  51.             http->out.offset,
  52.             http->out.offset,
  53.             STORE_CLIENT_BUF_SZ, memAllocate(MEM_STORE_CLIENT_BUF),
  54.             clientSendMoreData,
  55.             http);
  56.     }


4.     
还有一点小细节,在clientHttpRequest创建出来的时候,初始化一下limit_speed_started0。涉及到的函数有parseHttpRequestAbortparseHttpRequest。只要加在cbdataAlloc(clientHttpRequest)的后面即可



点击(此处)折叠或打开

  1. //xiaosi add start
  2. http->limit_speed_started = 0;
  3. //xiaosi add end



好了。编译并安装,是不是下载成功限制到20k了呢?

另外,回源的速度是由客户端的速度决定的。客户端限定到20k之后,看一看回源速度,是不是也变成20k了呢?

 

这个例子是比较简单的,限制的速度是写死在代码里的,另外,也不支持只限速符合某些acl的请求。

速度的配置,acl的配置,这些实现相对简单,有squid开发经验的读者可以自行搞定。

有问题,想法或对我的算法的改进,欢迎留言哦!

阅读(1253) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~