转载自:http://blog.linezing.com/2012/02/twitter-storm%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E6%8A%A5%E5%91%8A
Twitter storm性能测试报告
摘要: twitter storm是一个流处理系统,本文中描述了它的基本测试性能(包括吞吐量和处理延迟)以及测试结果的简要分析
测试目的
测试twitter storm的运行性能以及数据处理的延迟。
系统配置
测试方法
Storm 是一个流处理系统,它以tuple为基本单位,每个tuple可以包含多个字段(field)。我们给tuple定义两个字段:
- Data: 存放原始的数据,这里是1000字节的数据,此测试中我们仅仅是直接的转发数据,所以唯一的处理开销就是1000字节的内存拷贝
- ltsInfo: 时间戳信息,每经过一个处理模块,我们就在此字段中追加上当时的时间戳,最后统计模块就可以根据这些时间信息计算出总延迟等。由于不同的机器时间戳并不同步,这给计算延迟带来了固有误差,解决的办法就是把数据发送模块和最后的统计模块放到一台物理机上。
关于在分布式集群上测试storm的一个说明:在storm上,我们很难给某个模块(component)指定其运行的物理机,storm总是自动的把任务平均分配给集群中的各个机器,因此在测试中我们将使用storm的工作方式来扩展,而非设计非典型的情景(给某个component指定特定的机器来运行,从而打破这种平均分配原则)。
各测试用例以及测试结果
a) 在单个主机上,采用如下的方式构造拓扑
数据处理延迟越来越大的原因是,后端数据处理模块处理的速度较慢,所以数据发送端发送的数据产生了累积,并且累积量越来愈大,所以延迟的值就越来越大。较精确的测试处理延迟方案见(测试e)
b) 由上面的测试可知,processer模块的处理速度跟不上sender的发送速度,导致数据累积在发送端,本测试用例并行扩展processer,查看并行扩展以后是否可以提高处理的速度从而使得sender模块没有数据累积。
- 拓扑图如下:
- 系统资源的使用情况如下:
- 内存利用率
- 系统的吞吐量如下图所示,平均值为2180921890条/秒
在本测试用例中sender每秒钟发送大约22000个tuple。为什么并行以后系统的吞吐量还下降了呢?这是由于系统中增加了处理负载,sender的每秒钟发送的数据减少了,由下图的“处理延迟”可见sender端基本上不累积数据了。
绝大部分的处理延迟就在毫秒级,偶尔有一些处理延迟较大,分析认为是由于调度器对多个任务的调度、以及sender线程偶尔获得较大的资源增大了发送能力导致的。数据处理延迟没有线性增长,说明sender发送的数据都可以被实时处理掉(或有较小延时)。
c) 在集群上的扩展测试,
sender与processer不在同一台机器,并与以上测试结果对比。
由于不同主机上时间戳不同步,为了消除由此带来的误差,我们必须将数据产生模块sender和最后的计算模块stats放到同一台计算机上,将数据处理模块放到另一台计算机上,如下图所示
由于sender线程发送数据较快,数据会不断累积,所以数据延迟会不断增大,这说明后端的处理能力不足,即数据发送端产生数据过快(较精确的测试处理延时方案见测试(e))
d) 横向扩展,增加处理模块,消除数据产生段的累积
数据处理的延迟大部分在毫秒级,也有少数的延迟较大(几百毫秒),分析认为是由于任务调度等引起的sender线程在某一时刻拥有了较多的资源,从而发送了较多的数据;另一个原因就是java gc对系统性能的影响(详见后面的测试)。但是后端的处理能力较强可以随时将这些数据处理完毕,从而sender模块不会累积太多的数据,即sender发送的数据可以全部被处理。
e) 在数据高可靠情况下测试性能
本测试在单节点上运行,拓扑原理与a)相同,不过每个tuple都需要被ack,本测试中还限制了sender中未被ack的数据不能多于50条。由于系统中增加了ack tuple,数据量将至少增加一倍,所以有效数据处理性能将下降。
资源使用情况:
结果分析,由于我们限制sender发送的速度(未被ack的tuple不能超过50个),所以它发送出来的tuple都能被及时的处理,由上图可见,所有tuple的处理延迟都在毫秒级,这个可以认为是storm系统本身的处理延迟。
f) Java GC对tuple处理延迟的影响
java在回收垃圾内存的时候会停止当前正在运行的程序,本测试用例测试java gc对系统性能(尤其是处理延迟)的影响。为了突出gc对系统的影响,我们将两个处理单元(datasender 与 processer1)放到一个worker(即jvm)中,从而使得内存回收的问题更加突出和易于观测。
(注: 在以上的其它测试中,每个处理单元各占一个worker(jvm),所以未出现这里测试的结果)
-
测试原理图同测试用例 b)
-
CPU 利用率
-
内存的使用情况
-
系统吞吐量
-
各tupletTupleTuplewewe处理延迟
-
Java虚拟机内存使用情况统计图
-
Java GC垃圾回收模块运行时间
- 为了便于分析,我们将cpu利用率、tuple处理延迟、系统吞吐量和gc合并到用一张图中
对上图前半段的分析:java gc对tupe处理延迟的影响不是特别明显,一般在几百毫秒左右 (延迟图中较小的波动)。当每个worker (jvm)中只包含一个处理单元时(内存使用较小且可被及时回收),测试结果基本与此相同(小波动,没有gc密集回收垃圾的过程)。
对上图“tuple处理延迟中”较大脉冲的分析: 由于两个耗费内存的处理模块放到了同一个jvm中,所以此进程的内存使用量很大,当GC不能回收足够的内存时(发送端有累积的tuple不能被及时处理,它们占用内存),大部分的时间都会耗费在稍后的内存回收,由上图可见,CPU的利用率骤降,原因是gc运行时间增加,jvm中被停止的进程长时间不能得以运行,进而导致tuple的处理延迟增加。这个结果不是每次测试都能出现,只有当某些原因导致发送端出现tuple累积时才会出现。即使未出现此现象也说明系统有潜在的较大延迟风险,除非设计中已经保证数据处理单元有足够的能力来处理发送端发送来的数据。
综上所述,java gc对系统的影响可以分两种情况阐述: 1)内存足够(内存可被及时回收)的情况下,gc对象影响不大 2)jvm中内存使用接近上限且暂时不可回收时,gc对系统影响极大。所以,storm在设计、开发时需要仔细考量各处理单元内存的使用以及系统中worker的数量。
g) Storm使用外部处理程序时的性能
本测试用例主要测试使用外部处理程序的情况下,系统的整体性能。使用外部处理程序的时候,storm将外部处理程序作为子程序来运行,并使用Json格式来交换数据。本测试中我们使用python脚本作为外部处理程序。
- 测试原理图如下:
测试中,sender,processer等都是单节点的,所以本测试结果为单条处理线的处理能力。测试结果如下: - CPU利用率如下
- 各进程CPU利用情况
- 内存使用情况
- 吞吐量(tuples/s)
- Tuple处理延迟
测试结果分析:由上面的测试可见,使用外部处理程序时,系统的处理能力只有1000 tuple/s,性能下降明显。 分析认为性能陡降的原因有二: 1) 所有的tuple都经过Json格式与外部程序交互,格式转换的过程耗费了CPU circle; 2) storm把外部处理程序当做子进程,使用linux管道来通信,由于linux 管道( pipe)使用的是4K大小的页面做中转,所以在数据量较大的时候会有性能的损耗,测试中每个消息至少为1K bytes,所以很快就会将pipe使用的内存用完儿产生等待。增加外部程序个数(即processer处理单元的并行度, 不超过系统中CPU的个数),性能基本上有线性的提升。
由处理延迟的测试结果可见,使用外部处理程序的时候,tuple处理延迟比使用storm内建的处理机制要大十倍左右。
测试结论
经过上面的测试我们可以得出以下的结论:
- storm单条流水线的处理能力大约为20000 tupe/s, (每个tuple大小为1000字节)
- storm系统本省的处理延迟为毫秒级
- 在集群中横向扩展可以增加系统的处理能力,实测结果为1.6倍
- Storm中大量的使用了线程,即使单条处理流水线的系统,也有十几个线程在同时运行,所以几乎所有的16个CPU都在运行状态,load average 约为 3.5
- Jvm GC一般情况下对系统性能影响有限,但是内存紧张时,GC会成为系统性能的瓶颈
- 使用外部处理程序性能下降明显,所以在高性能要求下,尽量使用storm内建的处理模式