1. request per second
a jerky server model
这次做的是一个http log服务器,需要在有限的资源下支持尽可能高的PV。
提高PV,无非是改进两个指标:1. Connect Time;2. Process Time。
其实这两个问题都是一个思路,后面我们可以看到。
对于传统的长连接TCP服务,鉴于Erlang的低成本线程调度,一般采取的方案是每个TCP连接在gen_tcp:accept时spawn一个独立的线程处理后续的C/S交互。
但是对于眼下的http log服务,首先并不是一个keep-alive的连接:我们的设计目标是尽可能快的返回http响应并关闭TCP Socket,传统模型不是非常适用。
于是便有了一种callback模型:用一个daemon线程专司接收消息并创建监听线程,由监听线程阻塞在gen_tcp:accept上,例子如下:
start_server() ->
DT = spawn(test, daemon, [Listen_Sock]),
DT ! {new}.
daemon(Listen_Sock) ->
receive
{new} ->
spawn(test, proc, [Listen_Sock, self()])
end.
proc(Listen_Sock, DT) ->
Client = gen_tcp:accept(Listen_Sock),
DT ! {new},
gen_tcp:send(Client, "HTTP 200\n\r"),
% do further process
这种方案的好处在于,把spawn的开销从http处理中剥离了开来:
所有的处理线程都是预生成后阻塞在哪里,gen_tcp:accept后无需spawn直接执行后续的代码。
再配合+K true参数使用kqueue模型,可以预先生成多个proc的线程阻塞在accept上,可极大提高性能。
至于Process Time,同样是依赖预处理的思路:
因为http log服务涉及到Set-Cookie等操作,取localtime并格式化的过程中涉及到大量系统调用和字符串操作,在Erlang中开销不小。
解决办法是在daemon中预先生成好不同情况下需要返回的各色字符串,作为参数传给spawn出来的proc线程,剩下的操作无非是 条件判断->发送。
还有非常重要的一点是根据县城内处理数据的规模,使用spawn_opt设置合适的process heap大小
因为避免后期的heap扩充是开销非常大的一种操作,在轻量级线程内尤其要避免。
2. normal optimization[待续]
为了保证daemon进程的栈空间不增长,必须把函数写成tail-recursive。
这个积累还是很惊人的,而且属于不可回收空间;
如果你的服务运行那个多日因为内存问题挂掉,可以检查下所有的守护线程。
3. memory (crash dump)[待续]
4. 垃圾回收
期间遇到了比较到的严重的内存问题,造成服务器几个小时候因为alloc()失败而崩溃;
查看erlang:memory()的信息,发现processes占用绝大部分内存,开始误认为是产生了大量无用线程;
最后确诊还是因为deque大量申请释放,造成heap的回收效率问题以至于内存不够用。
使用spawn_opt设置fullsweep_after为0没有用,非常奇怪;必须显式调用erlang:garbage_collect()。
经过这几天压力测试,Erlang的garbage collection机制还是相当强健的:
在每秒近万次的不规则数据申请、释放的压力下,十几个小时后运行依然稳健,整体回收效率很高并且没有拖累到业务逻辑的性能。
可怜的Java…………
5. profiling[待续]
cprof
先列个题头,回头再写。。。
迅猛把活干完,然后去旅行。
What a wonderful world.
Ver. 3.14
周末肉过来了,去八达岭过了儿童节,很累。
所以洗洗睡了先,改日再写。
Ver. 3.141
在家了,一个劲的下雨。我华丽的假期

