前几天写了篇《gen_server tasting 之超简单名称服务(续) 》东西,亲身体验了 erlang otp 的强悍威力。这周正好有时间对 vsns/erlang 做个性能测试,验证传说中的 erlang 强大威力,其中包括了容错测试用例,关注在大并发压力下(13000tps)时,服务 oops 后通过 supervisor restart 时对整体性能的影响。在最后还包括了一些对服务容错设计上的一些思考。
测试用例设计
设计两个场景,都配置 200 并发压力:
- Complex: 通过 socket 混合调用 vsns 服务的 load_all、remove_all、remove、save、load 请求。无 Think time 迭代执行5分钟。用来取得服务满负载情况下吞吐量、响应时间及资源利用率性能指标。
- Crash: 在执行 Complex 场景过程中通过 kernel_oops 调用来 crash 名称服务。启用“continue on error” LoadRunner run-time 选项。用来取得服务满负载情况下,执行容错对吞吐量、响应时间及资源利用率性能指标的影响。
环境配置
环境是很简单的,精力有限,呵呵。需要说明的是,由于手上只有 Global 100 的 LoadRunner License,所以计划的 200 并发平均分配到两个测试机上,而且是启的独立 Controller,因此下边的吞吐量和响应时间结果也只能分别给出了。
“服务器”主机配置
说是服务器,实际是我的办公机器,HP 商用机。手头机器有限,呵呵。倒不是没有其他资源,只是考虑本次测试很原始,纯属个人娱乐,而且自己的机器调调配配、修修改改不麻烦。
测试机这里就不例了,两台也是 HP 商用机,不过配置很好 Intel Core 2 Duo 2.8GHz,2G 内存,呵呵够用。
测试执行
执行的力工细节就不说了,总之是负载很大,服务器饱和,有图为证。
测试结果
1. Complex 场景
测试机 A 的吞吐量和响应时间曲线。
测试机 B 的吞吐量和响应时间曲线。
注:其中的 “Wait%” 指标是无效的,nmon (nmon_x86_debian31(11f) 版本)采集的结果都是 0。我认为这是个 Bug,再不就是 debian 31 和 ubuntu 8.10 差异造成的。已经将该问题提交到 IBM developerWorks 的 AIX and UNIX Performance Tools Forum,不过没人回答 :(。难不成 je 的哪位大虾能给个意见?
2. Crash 场景
测试机 A 的吞吐量和响应时间曲线。
Pass Transcation.
Fail Transcation.
测试机 B 的吞吐量和响应时间曲线。
Pass Transcation.
Fail Transcation.
注:其中的 “Error: reset_conn once.” 是测试脚本中通过 lr_error_message 函数人为写入的,调试用。
结果分析
在 Complex 测试场景中,可以看到 vsns 服务在满负载情况下吞吐量能够持续、稳定的达到 13000tps(两台测试机总合),响应时间也稳定保持在 0.015 秒上下。在服务器资源方面,很明显 CPU 和内存已经饱和,也是因为我这个临时的“服务器”总共才 512M 物理内存,还没测试机一半强(2G),不过这也恰恰说明 erlang 面对艰巨条件时还是很坚挺的,呵呵。等回头和同事商量给我这台充当服务器的办公机器加条内存,估计那样一定会提高不少。当然这和测试中对 vsns 提交存储的 key/value 大小有直接关系。
可能对于 Crash 测试场景更有意义,现在就来一起看看。CPU 和内存也与上面一样,大负载下全都饱和。吞吐量和响应时间也和 Complex 测试一致。在整个 3 分钟的测试场景中,Crash 的 kernel_oops 方法是在 1 分 30 秒时提交的,通过响应时间可以明显看到其中的变化,响应“加快”的原因在于 Socket 连接被服务器断开造成的(此时调用都很快失败)。saleyn_tcp_serverk 中的 client 通信进程由于 name_server 进程 crash 而调用失败后纷纷退出,造成绑定在该进程上的 socket 全部失效而强制关闭。在这样的大的吞吐量下,共造成了 99 个事务报 Connection Abort(10053)异常。随后 erlang 监控进程将 name_server 进程启动,服务恢复正常。但从测试结果来看,两台测试机都有 10 秒左右的受影响时期,通过观察发现其间不是全部 vsns 服务调用都失败,而是部分正常部分失败。通过失败调用的吞吐量曲线可以看到,在 13000tps 压力下 erlang 进程 crash 后,在容错重新启动过程中,对于 client 来讲每秒会有50个左右的调用请求失败。同时在给出的可用物理内存曲线中可以看到,进程 crash 后有明显的 5M 内存释放发生,当然这包括 name_server 保存的进程字典数据。个人认为上面这些可以说明,erlang 进程的监控树结构是可以有效达到容错目的,但在大负载情况下,服务的 crash 对吞吐量影响还是不小的,起码性能下降不会很快缓解,当然这会考虑负载轻重。总体还讲还可以,尤其还是在这么烂的台式机上,呵呵。
对服务容错设计的思考
在进行上面所说的这个 Crash 场景测试时,在考虑是否应该将服务异常或不可用的信息暴露给客户端应用(服务消费者)呢?尤其是服务间存在互相委托关系时,比如开发 Service Hub 服务聚合或是 Service 扩展服务时。我想这可以有 3 种设计:
- 一旦服务失败立即返回调用者,并给出异常描述(原因)。这种设计会将服务错误完全暴露,将重试(容错)的机制依赖到客户端。
- 服务失败后一直等待服务就绪,什么时候正常了,什么时候重试。虽然容错对客户端透明了,但会引入延迟,对于实时的要求不好满足。
- 对上边第2点的补充,即服务重试次数和等待时间进行限定/可配置。就像erlang otp的supervisor设计相似。
很明显,第 3 种方式应该最有优势,兼顾了服务性能和容错处理,当然实现起来可以也最复杂了。
用到的几个优化方法
1. 启用 linux kernel epoll
configure --enable-kernel-poll
erl +K true parameter
2. 扩大 linux nproc 和 nofile limits
* soft nproc 2407
* hard nproc 16384
* soft nofile 1024
* hard nofile 65536
3. 扩大 linux 和 erlang 端口
echo 1024 65535 > ip_local_port_range
set ERL_MAX_PORTS = 102400
4. 启用 erlang SMP
erl -smp enable +S 2
5. 扩大进程数量
erl +P 102400
6. 扩大 linux tcp 协议栈中读写缓冲区大小,将影响 tcp window 大小
echo "640000" > /proc/sys/net/core/rmem_default
echo "640000" > /proc/sys/net/core/rmem_max
echo "640000" > /proc/sys/net/core/wmem_default
echo "640000" > /proc/sys/net/core/wmem_max
本文涉及的内容只涉及基础性的验证、测试,未涉及具体的 erlang 软件,且待我深入研究。Erlang rising~
附件 vsns_perf.zip 为备份目的所添加,请勿下载使用。
// 2009.02.16 16:29 添加 ////
关于 nmon 未能在 ubuntu 8.10 上获得 cpu wio% 指标的问题,想到有可能是未以 root 权限启动 nmon 进程的原因,不过能过刚才验证,结果还不是一样,不理想。尽管 vmstat 取得/确认了系统已经出现 wait io,但 nmon 的 wio% 采集结果还始终是 0。nmon_x86_debian31(11f) 版本对 ubuntu 8.10 的支持很失望。
sudo -i nmon -c310 -s1 -r -f
// 2009.02.17 17:04 添加 ////
呵呵,说是迟那是快。上面说的 nmon (nmon_x86_debian31(11f) 版本)采集的“Wait%” 结果都是 0 的问题,nagger 已经确认 ,并已提供新的 nmon_x86_12a 版本下载 ,包括了最新的 Ubuntu 8.10 系统的 nmon 映像。真的很强悍。
1 楼 supercode 2009-07-14 20:51