这里相对openswan的性能做个简单的说明。为什么要介绍这个话题呢?
其实最主要的原因还是想openswan的性能到底如何、极限是多少隧道、会有哪些瓶颈等等? 比如某个项目,客户需要设备支持1000条隧道,那么首先要考虑自己的产品能否支持到这么多?也就是说需要知道自己的实力。如果不考虑这些实际的问题,只一味的接项目,那么最可能的结果就是白白投入这么多的人力物力时间,最终对于公司来说收效甚微。
另一个原因:openswan的官方也没有给出明确的性能参数,比如说最大能支持多少条隧道? 所以根据现有的硬件资源来测试其性能是必不可少的。具体问题具体分析嘛。
pluto能支持多少条隧道
这个问题,我是众里寻他千百度呀,可惜的是蓦然回首,产品经理他不走。后来我在openswan的源码中找到有这么一条邮件信息,原文如下:
上面的信息是2000年的一个邮件,回复的是freeswan的问题,但是这是我目前找到的最接近的回答,即使它也没有给出明确的答案。
我私下测试pluto性能,隧道最多的时候建了4800条左右(峰值),此时左右两端隧道的状态已经严重不一致了(left已经建立成功,但right却没有),这个原因我还没有细究,不过可以肯定的是:和pluto的低性能有关。如果不考虑因移植引入的瓶颈问题,支持5000条应该不是问题。但此时的pluto的效率已经很低很低。下面我把自己遇到的问题做一个简单的记录、分析。
pluto的瓶颈在哪里
既然说pluto的性能比较低,那么它的瓶颈在哪里呢?
这个原因会有很多,因此pluto的可用配置参数很多,不同的配置时,瓶颈略有不同,但是有几个函数,无论策略如何配置,它的CPU占有率都是稳坐前几把交椅的存在。(在很多隧道时,特明显,比如多于1000条)。下面我来一一说明:
openswan_log()
openswan_log()va_start(),va_end()openswan_log()
- 添加一条whack命令
这是因为whack命令已经实现了进程间的通讯(whack—pluto),完全可以满足要求。此种做法处理效率快,也更加正规。
- 访问文件系统中的一个文件
openswan_log()
此方法的优点是实现很简单,实时性很好;但问题是每次需要访问文件系统,效率比直接访问全局变量来的慢。
find_phase1_state()
在隧道协商过程开始之前,需要确定是否之前是否已经协商过且存在状态,如果存在,可能第一阶段已经协商成功,直接进行第二阶段协商即可。这个过程是必须的,通过它可以确定发起第一阶段协商还是第二阶段协商。那么这个函数都做了哪些操作呢? 从当前的状态哈希表(statetable), 找到最适合当前连接的状态。也就是说需要将整个哈希表遍历完才能找到“最合适”的。
除此之外,什么是"合适呢"?
same_peer_ids
find_phase1_state
目前对于这个的优化还没有特别好的想法。一个初步的想法是“以空间换时间”:即同时维持两个哈希表,除了上述的哈希表外,再维持一个基于连接的哈希表,两个哈希表共享所有的节点,只需增加一个指针域,将其链起来,构成一个网状的结构。使用时根据不同的需求访问不同的哈希表,速率上应该能得到提升。(增删节点时得同时操作两个哈希表)
event_schedule()
pluto中对于定时事件的处理是通过排序链表(LFU)实现的,使用排序链表有一个缺点:当定时事件很多时,添加新定时器的效率会很低。在插入操作时,我们需要将当前的时间在链表中进行排序,虽然时间复杂度是O(n),但当定时事件很多时,却不得不考虑效率低下的问题。这在pluto中添加成百上千条隧道时,体现的尤其明显。那么有没有办法进行优化呢? 答案肯定是有的。
常见的定时器有三类(《Linux高性能服务器编程》):排序链表,时间轮,事件堆。
既然排序链表低,很多人可能想到哈希表:根据定时的时间长度来取哈希,然后在进行排序岂不是可以提高效率。不错,的确这样,这就是时间轮的基本思想。如果需要优化的话pluto中的升序链表可以改为时间轮。那么改为时间轮效率能提高吗?由于pluto中的定时器都是以秒为单位,这个时间跨度感觉有点太短,导致多个定时器堆积的问题,但是比升序链表效率高是确定的,因为在添加定时器时的效率提高了很多。
con_by_name()
这个函数是在根据连接的名字查询连接时使用的,pluto是使用链表来维护的。如果数量很大时,遍历链表的时间复杂度O(n)。当然这个可以进行优化,同样采用哈希表,尽可能的将连接均匀的分布在不同的表中,这样便可以提升查询时的效率。
generate_msgid()
msgid是第二阶段时的一个重要参数,它用来唯一标识一个IPSEC SA。
在生成msgid时,要确保msgid的唯一性,因此需要遍历当前ISAKMP中所有的msgid。这个效率低下,跟我配置隧道参数时相关:我配置的隧道的端点IP相同,也就是说所有的隧道共用一个ISAKMP SA, 因此第二阶段的隧道生成msgID时,需要同所有的msgid进行匹配,如果重复需要重新生成。
pluto中核心架构分析
call_server()
IO密集型和计算密集型
首先得考虑业务的类型,属于IO密集型还是计算密集型。
- IO密集型
I磁盘的读取数据和输出数据非常大的时候就是属于IO密集型。由于IO操作的运行时间远远大于cpu、内存运行时间,所以任务的大部分时间都是在等待IO操作完成。
特点:cpu消耗小。(因为CPU在休息等待)
IO密集型,由于CPU很多时间处于休眠等待期,因此通过多线程(多进程)来同时执行多个IO操作,只要其中一个没有阻塞等待,那么效率就可以得到相应的改善(不考虑资源切换和非常多的线进程情况下)。
- 计算密集型 计算密集型就是计算、逻辑判断量非常大而且集中的类型,因为主要占用cpu资源所以又叫cpu密集型,而且当计算任务数等于cpu核心数的时候,是cpu运行效率最高的时候。
特点:消耗cpu。
很明显,虽然IPsec也有比较多的IO操作,典型的就是使用数字证书进行协商时,但它绝对称得上是计算密集型(加解密就是啪啪啪一顿计算,CPU直接满负荷工作)。这时即使才用了多进程多线程技术,效率上也有可以没有办法得到提升。此时最好的办法就是提升硬件的性能呢。
pluto适合多线程多进程嘛?
pluto虽然属于计算密集型,吃CPU的大户,但是如果将上述的三个事件(IO事件,信号,定时器)分开处理,自测情况下情况有所改观,协商的隧道数量提升了几百个左右。但是如果想把IO事件(IPSEC协商流程)进行多线程多进程处理,效率上应该得不到明显的提升,毕竟CPU已经满负荷的在计算,又让他去计算另一条隧道,理论上改善的可能性有限。
解决办法就是增加相应的硬件辅助资源:如增加CPU核心数、增加硬件加密卡等。
后来我的观点是:pluto中不能使用多线程/进程, 因为存在很多全局变量,对于共享资源加锁并不是一个很好的选择。