Squid是目前最流行的开源CACHE引擎,本文对Squid和Netshine
ICS的实现机制进行分析,据此得出Netshine ICS性能为什么比Squid高的深层次的技术原因。
一、Squid存在的问题
在Internet上的应用几乎都是事件驱动式的(event
driven)。在Unix上,有两个基本的函数用来检测来自于网络的事件,一个是select(),另外一个是poll()。无一例外,Squid使用poll()函数作为它的事件检测和分派的核心。下面是poll()函数的接口原型:
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
|
其中,fds是需要检测的文件描述符集合,nfds是这个集合的大小。
当nfds比较小时,poll()工作得非常好。但是在实际中我们发现,当nfds比较大时,poll()本身的操作就占用了CPU的绝大部分时间,换句话说,就是poll()的伸缩性(scalablity)比较弱。那么,问题出在哪儿呢?
问题就出在poll()接口的定义和实现上。对于每次poll()调用,poll()函数都要求将整个文件描述符列表传递给它,由于poll()是一个系统调用,因此,系统对于每次poll()调用,都必须穿透user/kernel边界拷贝数据,同时在kernel内部还有一次malloc()调用。当nfds比较小时,这种开销不会导致多大的性能问题,但是如果nfs大到成千上万时,这种开销就非常了得!实际上,在一个中等规模的环境中,比如高校,系统同时保持1万多条连接是比较常见的事情(如拥有5000台以上计算机的高效,最高峰达到了20000条以上的连接,平均可以达到13000条左右的连接)。另外,从应用程序的角度看,每次当poll()调用返回后,都必须遍历整个文件描述符列表,这是一个O(n)复杂度的操作,因此伸缩性很差。同样的,这种遍历操作在kernel内部也必须做一遍。
总而言之,poll()低效的主要原因是,poll()做了太多的无效操作,因此浪费了大量的CPU时间:
(1)无效的user->kernel方向的数据拷贝。在一个实际的应用中,很多描述符都会存在于一段时间,因此每次对拷贝所要检测的所有文件描述符列表是一个无效的操作,因为两次连续的poll()调用中,必定有很多文件描述符是相同的;
(2)无效的kernel->user方向的数据拷贝。在实际中,尽管系统保持了很多文件描述符(套接字),但是在每个时刻,真正可readable或writable的文件描述符并不多,因此,将所有的文件描述符都拷贝给user是无效的,在一个保持了10000条连接的系统中,可能只有1000条连接上可readable或writable,因此有90%的数据拷贝是无效的。
(3)无效的应用层遍历。根据(2),既然10000条连接中只有1000条连接是有事件的(readable或writable),那么90%的遍历就是无效的。
影响cache系统性能的最主要两个因素是network I/O和disk
I/O。由于cache存在大量的数据读写操作,因此disk I/O的性能高低会极大地影响cache的整体性能。Squid为了解决这一点,在操作系统提供的文件系统的基础上实现了一个所谓的"cache文件系统"。但是无论如何,由于这个"cache文件系统"是建立在通用文件系统的基础之上,所以它最多只能利用内存来减少disk
I/O次数以提高系统性能。由于通用文件系统并没有利用多个磁盘I/O的独立性,因此当系统挂接了多个磁盘时,这种方式并不能有效利用多个磁盘同时I/O的特点,所以它并不能有效提高disk
I/O。
以上两点,决定了Squid具有很差的伸缩性,也就是说,即使你硬件配置再高,它也不能有效的提升性能!
二、Netshine ICS是如何解决问题的
Netshine ICS通过以下两个措施来解决上述问题:
(1)抛弃poll。由于poll()极差的伸缩性,Netshine ICS抛弃了poll(),而使用FreeBSD的kevent机制。关于kevent机制的详细介绍请参考相关的文档,这里就不多述了。
(2)建立专用的文件存储系统和内存缓存系统。为了提高disk
i/o,Netshine ICS在内存中建立高效的cache文件系统的同时,还对操作系统内核进行改造,实现软件级别的RAID和直接磁盘I/O以充分利用多个磁盘并发I/O的能力,并且,Netshine
ICS在磁盘裸分区上建立了一个专用的文件存储系统。在实践中,这种方式证明是非常高效的,也非常稳定的!
三、Netshine ICS和Squid性能对比
(1)吞吐量和内存使用有效性比较
(2)每秒请求数比较
|
|