用Mochiweb打造百万级Comet应用(二)

原文:A Million-user Comet Application with Mochiweb, Part 2

参考资料:Comet--基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”

MochiWeb--建立轻量级HTTP服务器的Erlang库

第一部分 , 我们构建了一个每10秒向客户端发送一条消息的mochiweb comet应用(没什么用处)。我们微调了一下linux内核,做了一个能够建立大量网络连接以测试应用性能和所耗内存的工具 。我们发现每个连接花费大约45K内存。

本系列的第二部分讲的主要是把应用变得更加有用,更加节省内存:

用一个login/logout/send API实现一个消息路由器更新mochiweb应用使之能够从路由器接收消息建立一个分布式erlang系统,这样我们可以在不同的节点和主机上运行路由器写一个能给路由器发送大量无用信息的工具超过24小时的内存用量图,优化mochiweb应用以节约内存

这就意味着我们需要把消息发送逻辑从mochiweb应用中剥离出来。利用第一部分的压力测试工具,我们可以建立一个更接近产品级别的基准测试。

实现消息路由器

路由器的API只有3个函数:

login(Id, Pid) 为Id注册一个接收消息的进程(Pid )logout(Pid) 停止接受消息send(Id, Msg)向已登录客户端(Id)发送消息(Msg)

注意,从设计上来说,多个不同的用户登陆到同一个进程是有可能的。

这个实例路由器模块用了2个ets表存储Pids和Ids的双向映射(pid2id和id2pid在下面的#state记录定义中)。

router.erl:

-module( router) .-behaviour( gen_server) . -export([start_link /0]) .-export([ init/1 , handle_call/3 , handle_cast/2 , handle_info/2 ,     terminate/2 , code_change/3]) . -export([ send/2 , login/2 , logout/1]) . -define(SERVER , global:whereis_name( ?MODULE)) . % will hold bidirectional mapping between id <–> pid-record( state, { pid2id, id2pid}) . start_link() ->    gen_server :start_link({ global, ?MODULE} , ?MODULE , [] , []) . % sends Msg to anyone logged in as Idsend(Id , Msg) ->    gen_server :call( ?SERVER , { send, Id , Msg}) . login(Id , Pid) when is_pid(Pid) ->    gen_server :call( ?SERVER , { login, Id , Pid}) . logout(Pid) when is_pid(Pid) ->    gen_server :call( ?SERVER , { logout, Pid}) . %% init([]) ->    % set this so we can catch death of logged in pids:    process_flag( trap_exit, true) ,    % use ets for routing tables    { ok, #state{                pid2id = ets:new( ?MODULE , [ bag]) ,                id2pid = ets:new( ?MODULE , [ bag])               }    } . handle_call({ login, Id , Pid} , _From , State) when is_pid(Pid) ->    ets :insert(State #state.pid2id, {Pid , Id}) ,    ets:insert(State #state.id2pid, {Id , Pid}) ,    link(Pid) , % tell us if they exit, so we can log them out    io:format("~w logged in as ~w\n " ,[Pid , Id]) ,    { reply, ok, State} ; handle_call({ logout, Pid} , _From , State) when is_pid(Pid) ->    unlink(Pid) ,    PidRows = ets:lookup(State #state.pid2id, Pid) ,    casePidRowsof        [] ->            ok ;        _ ->            IdRows = [{I ,P} || {P ,I} <- PidRows] , % invert tuples            % delete all pid->id entries            ets:delete(State #state.pid2id, Pid) ,            % and all id->pid            [ ets:delete_object(State #state.id2pid, Obj) || Obj <- IdRows]    end ,    io:format("pid ~w logged out\n " ,[Pid]) ,    { reply, ok, State} ; handle_call({ send, Id , Msg} , _From , State) ->    % get pids who are logged in as this Id    Pids = [P || { _Id , P} <- ets:lookup(State #state.id2pid, Id)] ,    % send Msg to them all    M = { router_msg, Msg} ,    [Pid ! M || Pid <- Pids] ,    { reply, ok, State} . % handle death and cleanup of logged in processeshandle_info(Info , State) ->    caseInfoof        {‘EXIT’ , Pid , _Why} ->            % force logout:            handle_call({ logout, Pid} , blah, State) ;         Wtf ->            io :format("Caught unhandled message: ~w\n " , [Wtf])    end ,    { noreply, State} . handle_cast( _Msg , State) ->    { noreply, State} .terminate( _Reason , _State) ->    ok .code_change( _OldVsn , State , _Extra) ->    { ok, State} .

更新mochiweb应用

让我们假设用户是由基于连入mochieweb的URl中的Id号所描述的,我们用那个id向消息路由器注册。 取代阻塞10秒后发送消息,mochiweb的loop循环将组塞在从路由器接收消息上,路由器给mochiweb进程发送消息后mochiweb进程就会向客户端发送Http数据块:

客户端从http://localhost:8000/test/123连接mochiwebMochiweb应用为id为123的用户的那个连接向消息路由器注册进程(pid)假如你用id为123向路由器发送一条消息,他将转发到正确的mochiweb进程,继而消息会出现在那个用户的浏览器上

这是mochiconntest_web.erl的更新版本:

-module( mochiconntest_web) . -export([ start/1 , stop/0 , loop/2]) . %% External API start(Options) ->    {DocRoot , Options1} = get_option( docroot, Options) ,    Loop = fun (Req) ->                   ?MODULE :loop(Req , DocRoot)           end ,    % we’ll set our maximum to 1 million connections. (default: 2048)    mochiweb_http:start([{ max, 1000000} , { name, ?MODULE} , { loop, Loop} | Options1]) . stop() ->    mochiweb_http :stop( ?MODULE) . loop(Req , DocRoot) ->    "/" ++ Path = Req :get( path) ,    caseReq :get( method)of        Method when Method =:= ‘GET’ ; Method =:= ‘HEAD’ ->            casePathof                "test/" ++ Id ->                    Response = Req :ok({"text/html; charset=utf-8" ,                                      [{"Server" ,"Mochiweb-Test"}] ,                                      chunked}) ,                    % login using an integer rather than a string                    {IdInt , _} = string:to_integer(Id) ,                    router:login(IdInt , self()) ,                    feed(Response , IdInt , 1) ;                _ ->                    Req :not_found()            end ;        ‘POST’ ->            casePathof                _ ->                    Req :not_found()            end ;        _ ->            Req :respond({501 , [] , []})    end . feed(Response , Id , N) ->    receive    { router_msg, Msg} ->        Html = io_lib:format("Recvd msg #~w: ‘~s’" , [N , Msg]) ,        Response :write_chunk(Html)    end ,    feed(Response , Id , N+1) . %% Internal API get_option(Option , Options) ->    { proplists:get_value(Option , Options) , proplists:delete(Option , Options)} .

动起来!

现在让我们让它活起来 - 我们用两个erlang shell, 一个用于mochiweb,一个用于路由器。 编辑start-dev.sh , 用于启动mochiweb, 下面的额外参数是用于erl的:

-sname n1 命名节点‘n1′+K true 使kernel-poll有效。 当处理当量的连接时看起来不是那么的不知所措+P 134217727 缺省的你能调度的最大进程数为 32768. 想像一下我们每个连接就用一个进程(我不知道不那样做的更好原因) 我建议设置这个参数为最大的可能值。 根据 “man erl”,134,217,727 是最大的

现在运行make && ./start-dev.sh 你将看到一个像([email protected])1>的提示符 -你的mochiweb应用已经那个运行起来了,erlang节点也有了名字。

现在运行另外的erlang shell,就像这样:
erl -sname n2
现在两个erlang实例彼此不知道对方,更正它:
([email protected])1> nodes().
[]
([email protected])2> net_adm:ping([email protected]).
pong
([email protected])3> nodes().
[[email protected]]

现在从这个shell上编译启动路由器:
([email protected])4> c(router).
{ok,router}
([email protected])5> router:start_link().
{ok,<0.38.0>}

现在更好玩点, 从浏览器中执行 http://localhost:8000/test/123 (或者从终端执行lynx --source "http://localhost:8000/test/123" )。 检查运行路由器的shell,你将看到已经有一个用户登陆了。

你现在可以向路由器发送消息并且在浏览器上看到她们。现在只是发送字符串,因为我们在feed函数中用~s 来格式化io_lib:fomart中的参数,原子将使其崩溃:

借用你运行路由器的shell:

([email protected])6> router:send(123, "Hello World").
([email protected])7> router:send(123, "Why not open another browser window too?").
([email protected])8> router:send(456, "This message will go into the void unless you are connected as /test/456 too").

检查你的浏览器,你已经得到了comet,呵呵

在分布式erlang系统中运行

感觉上路由器和mochiweb前端运行在不同的机器上。 假设你有一对备用机用来测试,你应该把erlangshell作为分布式节点启动,也就是说用 -name [email protected] 取代 -sname n1 (n2也一样)。确信他们可以看到彼此,就像上面似的用 net_adm:ping(...) 。

注意router.erl中的16行, 路由器进程的名字(’router’)被注册成全局的,因为我们在对gen_server的调用中用随后的宏去标志和定位路由器,它已经在分布式系统中很好的工作了:

-define(SERVER, global:whereis_name(?MODULE))。

在分布式系统中为进程注册全局名是elang为你做的很自然的事情之一。

生成大量信息

在实际环境中我们可能看到像用例模型似的长尾想象,有一些很活跃的用户和很多不活跃用户。但是在这个测试中我们将不分青红皂白的为随机用户生成无用的消息。

msggen.erl:

-module( msggen) .-export([ start/3]) . start(0 , _, _) -> ok ;start(Num , Interval , Max) ->    Id = random:uniform(Max) ,    router:send(Id , "Fake message Num = " ++ Num) ,    receiveafterInterval -> start(Num-1 , Interval , Max)end .

这将向id在1到max之间的随机用户发送Num个消息,每次发送等待Interval 毫秒。

你可以看到这些东西假如你运行路由器和mochiweb应用后用浏览器连接http://localhost:8000/test/3之 后执行

erl -sname test ([email protected])1> net_adm:ping([email protected]).pong ([email protected])2> c(msggen).{ok,msggen} ([email protected])3> msggen:start(20, 10, 5).ok

这将向随机id在1-5之间的用户发送20条消息,每条消息之间有10毫秒等待。 Id 3有机会收到一条或者四条消息。

我们可以均等的并行运行一些进程以模拟多个消息源。这里的例子是生成10个进程,每个进程发送20条消息到1-5号用户,每条消息之间间隔100毫秒:

[ spawn(fun() -> msggen:start(20, 100, 5), io:format("~w finished.\n", [self()]) end) || _ <- lists:seq(1,10) ].
[<0.97.0>,<0.98.0>,<0.99.0>,<0.100.0>,<0.101.0>,<0.102.0>,
<0.103.0>,<0.104.0>,<0.105.0>,<0.106.0>]
<0.101.0> finished.
<0.105.0> finished.
<0.106.0> finished.
<0.104.0> finished.
<0.102.0> finished.
<0.98.0> finished.
<0.99.0> finished.
<0.100.0> finished.
<0.103.0> finished.
<0.97.0> finished.

再次感受C10K测试

现在我们需要运行另外一个有更大伸缩行的测试;客户端链接到mochiweb应用,mochiweb把他们注册到路由器。我们能生成更多的虚假消息来考验路由器,路由器将把这些消息发送到任何注册的客户端。让我们再次运行在Part 1 使用的10,000个并发用户的测试,但是这次我们将在传输大量消息之前保持所有的客户连接一段时间。

假设你按照在 部分提到的操作调整了你的内核,增加了最大文件限制,这很简单。你已经运行了mochiweb应用和路由器,让我们查看下流量情况。

在没有任何客户连接的情况下, 每个pochiweb beam进程用了大约40MB内存(常驻):

$ ps -o rss= -p `pgrep -f ‘sname n1‘`
40156

带‘sname n1‘的greps命令是为了得到我们的mochiweb erlang进程ID的,然后用带有格式化选项的ps命令打印常驻内存大小 (KB)

我组合出了这讨厌的每60秒用一行打印时间戳, 当前内存用量 (常驻 KB), 当前建立的连接数的语句 - 在另外一个运行mochiweb的机子的终端中放任他运行:

$ MOCHIPID=`pgrep -f ‘name n1‘`; while [ 1 ] ; do NUMCON=`netstat -n | awk ‘/ESTABLISHED/ && $4==”127.0.0.1:8000″‘ | wc -l`; MEM=`ps -o rss= -p $MOCHIPID`; echo -e “`date`\t`date +%s`\t$MEM\t$NUMCON”; sleep 60; done | tee -a mochimem.log

假如有谁知道为一个进程长时间动态剥离出内存用量更好的方法,请留个言。

现在在一个新的erl shell中运行第一部分使用的floodtest工具:
erl> floodtest:start("/tmp/mochi-urls.txt", 10).

这将每秒建立100个新的连接,直到10,000个客户端都已经连接了。
你将看到非常快的达到10K个连接:
erl> floodtest:start("/tmp/mochi-urls.txt", 10).
Stats: {825,0,0}
Stats: {1629,0,0}
Stats: {2397,0,0}
Stats: {3218,0,0}
Stats: {4057,0,0}
Stats: {4837,0,0}
Stats: {5565,0,0}
Stats: {6295,0,0}
Stats: {7022,0,0}
Stats: {7727,0,0}
Stats: {8415,0,0}
Stats: {9116,0,0}
Stats: {9792,0,0}
Stats: {10000,0,0}
...

检查这讨厌的一行输出的内存使用情况
Mon Oct 20 16:57:24 BST 2008 1224518244 40388 1
Mon Oct 20 16:58:25 BST 2008 1224518305 41120 263
Mon Oct 20 16:59:27 BST 2008 1224518367 65252 5267
Mon Oct 20 17:00:32 BST 2008 1224518432 89008 9836
Mon Oct 20 17:01:37 BST 2008 1224518497 90748 10001
Mon Oct 20 17:02:41 BST 2008 1224518561 90964 10001
Mon Oct 20 17:03:46 BST 2008 1224518626 90964 10001
Mon Oct 20 17:04:51 BST 2008 1224518691 90964 10001

他已经达到了10K个并发连接(加上我用firefox打开的这个),mochiweb常驻内存的大小也在90MB左右(90964KB)。

现在来弄些消息吧

erl> [ spawn(fun() -> msggen:start(1000000, 100, 10000) end) || _ <- lists:seq(1,100) ].
[<0.65.0>,<0.66.0>,<0.67.0>,<0.68.0>,<0.69.0>,<0.70.0>,
<0.71.0>,<0.72.0>,<0.73.0>,<0.74.0>,<0.75.0>,<0.76.0>,
<0.77.0>,<0.78.0>,<0.79.0>,<0.80.0>,<0.81.0>,<0.82.0>,
<0.83.0>,<0.84.0>,<0.85.0>,<0.86.0>,<0.87.0>,<0.88.0>,
<0.89.0>,<0.90.0>,<0.91.0>,<0.92.0>,<0.93.0>|...]

也就是100个进程各自以每秒10条消息的速度向随机的id为1到10000的客户端发送1百万条消息。那就意味着路由器每秒钟看到1000条消息,平均我们10K个客户端每10秒得到一条信息。

检查floodtest shell的输出,你将看到客户端正在接收http数据块(记住,格式是{NumConnected, NumClosed, NumChunksRecvd}):
...
Stats: {10000,0,5912}
Stats: {10000,0,15496}
Stats: {10000,0,25145}
Stats: {10000,0,34755}
Stats: {10000,0,44342}
...

一百万条消息以每个进程每秒10条速度将用27小时完成。这仅仅是10分钟后的没存用量:
Mon Oct 20 16:57:24 BST 2008 1224518244 40388 1
Mon Oct 20 16:58:25 BST 2008 1224518305 41120 263
Mon Oct 20 16:59:27 BST 2008 1224518367 65252 5267
Mon Oct 20 17:00:32 BST 2008 1224518432 89008 9836
Mon Oct 20 17:01:37 BST 2008 1224518497 90748 10001
Mon Oct 20 17:02:41 BST 2008 1224518561 90964 10001
Mon Oct 20 17:03:46 BST 2008 1224518626 90964 10001
Mon Oct 20 17:04:51 BST 2008 1224518691 90964 10001
Mon Oct 20 17:05:55 BST 2008 1224518755 90980 10001
Mon Oct 20 17:07:00 BST 2008 1224518820 91120 10001
Mon Oct 20 17:08:05 BST 2008 1224518885 98664 10001
Mon Oct 20 17:09:10 BST 2008 1224518950 106752 10001
Mon Oct 20 17:10:15 BST 2008 1224519015 114044 10001
Mon Oct 20 17:11:20 BST 2008 1224519080 119468 10001
Mon Oct 20 17:12:25 BST 2008 1224519145 125360 10001

当10K个客户端已经连接后,你能看到内存已经由40MB涨到了90M,运行更长一段时间涨到了125M。

值得指出的是floodtest shell 与cpu相关的,消息产生shell 用了2%cpu,路由器和mochiweb应用则少于1%。(也就是只有仿真大量客户端用的cpu比较多 - 服务器本身则很少)。这有助于用多台机子或多核cpu进行测试。

运行24小时后的结果

我运行这个24小时, mochiweb进程的内存用量信息被写到mochimem.log文件中。这是10000个连接客户端,每秒1000条消息发送给随机的客户端。

下面的bash/awk语句是为了把mochimem.log信息转成图例:

(echo -e "set terminal png size 500,300\nset xlabel \"Minutes Elapsed\"\nset ylabel \"Mem (KB)\"\nset title \"Mem usage with 10k active connections, 1000 msg/sec\"\nplot \"-\" using 1:2 with lines notitle" ; awk ‘BEGIN{FS="\t";} NR%10==0 {if(!t){t=$2} mins=($2-t)/60; printf("%d %d\n",mins,$3)}‘ mochimem.log ; echo -e "end" ) | gnuplot > mochimem.png

内存用量,c10k连接, 1000条消息/秒,24小时

这个图展示内存用量 (10k 活跃连接, 1000条消息/秒) 24小时持续在250M。有两个大点的下掉, 一个在测试开始一个在结束, 这是当在mochiweb进程中处于好奇运行这个:

erl> [erlang:garbage_collect(P) || P <- erlang:processes()].

他迫使所有的进程进行垃圾回收,这收回大约100MB的内存-下面我们研究一些不用手动强迫进行垃圾回收的方法以节约内存。

在mochiweb减少内存的方法

看起来mochiweb应用只是发送消息然后立即跌掉她们,内存用量不应该随消息发送数的增长而增长。

对于Erlang内存管理我是个新手,但是我继续假设能够频繁的进行垃圾回收,这将允许我们剩下大量内存, 最终让我们能用比较少的系统内存服务更多用户。 我们可能利用更多点的cpu占用率, 但是是可以接收的。

深挖

有这么几个选项:

erlang:system_flag(fullsweep_after, Number)

Number是一个标志在没有全扫描的情况下多少次垃圾回收可以做的一个非负常数。这个值适用于新进程;已经运行的进程不受影响。
在低内存的系统中(特别没有虚拟内存),设置这个值为0可以帮助节约内存
另一个设置次值可选的方法是通过(操作系统)环境变量ERL_FULLSWEEP_AFTER。

听起来挺有意思,但是他仅仅适用于新进程而且将对虚拟机中的所有进程产生作用,只是除了我们的mochiweb进程,呵呵

接下来

erlang:system_flag(min_heap_size, MinHeapSize)

为进程设置缺省的堆大小。以字为单位。新的min_heap_size仅仅影响当min_heap_size改变后生成的进程。通过spawn_opt/N or process_flag/2 min_heap_size可以为单独进行设置

可能有用,但是我更愿意确保我们的mochiweb进程有一个比缺省值大点的内存堆。我更喜欢尽可能避免为了加spawn选项而对mochieweb源代码打补丁

下面的吸引了我的眼球

erlang:hibernate(Module, Function, Args)

把正在调用的进程处于等待状态,它的内存分配就会尽可能的少,假如进程不想在短时间内接收任何数据了那么这是非常有用的。

进程在有消息发送过来时被唤醒, 跟着调用栈被清空,带有由Args给定参数的Module:Function将得到控制权, 意味着这个进程当函数返回时将被终止。 这样erlang:hibernate/3将永远也返回不到调用他的地方

假如进程在消息队列里有任何消息,进程也会以上面的方式被立即唤醒。

用更专业的术语来说,erlang:hibernate/3 做了下面几点。他丢弃进程的调用栈之后进行垃圾回收。在回收后,所有活跃数据都在一个连续的堆中。这个堆然后把空间压缩到刚好能容纳的了活跃数据的尺寸 (即使这个尺寸比进程的最小堆的还小).

假如进程活跃数据的大小小于最小堆尺寸,第一次垃圾回收也会在进程被唤醒后发生,这样确保堆尺寸会变到不小于最小堆尺寸。

注意,清空调用栈意味着任何异常处理都被移除并在休眠后重新插入。一个影响是进程要用proc_lib启动(间接的, gen_server也可以), 用proc_lib:hibernate/3代替主要是确保异常处理在进程被唤醒后能够继续工作。

听起来很合理 - 让我们发送完每个消息后试着休眠,看到底发生了什么

编辑mochiconntest_web.erl ,改变如下:

让 feed(Response, Id, N)函数的 最后一行调用hibernate,而不是调用他自己登进路由器后立马调用hibernate,而不是调用feed并阻塞在receive上记住导出feed/3 ,这样hibernate在唤醒时可以回调回这个函数

用hibernation更新后的mochiconntest_web.erl :

-module( mochiconntest_web) . -export([ start/1 , stop/0 , loop/2 , feed/3]) . %% External API start(Options) ->    {DocRoot , Options1} = get_option( docroot, Options) ,    Loop = fun (Req) ->                   ?MODULE :loop(Req , DocRoot)           end ,    % we’ll set our maximum to 1 million connections. (default: 2048)    mochiweb_http:start([{ max, 1000000} , { name, ?MODULE} , { loop, Loop} | Options1]) . stop() ->    mochiweb_http :stop( ?MODULE) . loop(Req , DocRoot) ->    "/" ++ Path = Req :get( path) ,    caseReq :get( method)of        Method when Method =:= ‘GET’ ; Method =:= ‘HEAD’ ->            casePathof                "test/" ++ IdStr ->                    Response = Req :ok({"text/html; charset=utf-8" ,                                      [{"Server" ,"Mochiweb-Test"}] ,                                      chunked}) ,                    {Id , _} = string:to_integer(IdStr) ,                    router:login(Id , self()) ,                    % Hibernate this process until it receives a message:                    proc_lib:hibernate( ?MODULE , feed, [Response , Id , 1]) ;                _ ->                      Req :not_found()            end ;        ‘POST’ ->            casePathof                _ ->                    Req :not_found()            end ;        _ ->            Req :respond({501 , [] , []})    end . feed(Response , Id , N) ->    receive    { router_msg, Msg} ->        Html = io_lib:format("Recvd msg #~w: ‘~w’<br/>" , [N , Msg]) ,        Response :write_chunk(Html)    end ,    % Hibernate this process until it receives a message:    proc_lib:hibernate( ?MODULE , feed, [Response , Id , N+1]) .  %% Internal API get_option(Option , Options) ->    { proplists:get_value(Option , Options) , proplists:delete(Option , Options)} .

我做了这些改变,运行make重新构建mochiweb,然后重做同样的c10k测试 (1000条消息/秒 24小时).

运行24小时后的结果 w/ proc_lib:hibernate()

内存用量 c10k, 1000条消息/秒, 24小时, 用hibernate()

恰如其分的,用了hibernate,10K个连接的mochiweb应用的内存用量维持在78MB这个水平, 要比我们在

看到的450MB好的多。CPU占用率也没明显增加。

总结

我们基于mochiweb做了个comet应用,他让我们推送任意消息给由ID标志的客户端。在以每秒1000条消息的速度推送24小时后, 10,000个连接用户,我们发现它用了80MB内存,或者说每个用户8KB 。 我们同样也做了很漂亮的图。

相对于在

我们所看到的每个用户45KB,这是一个很大的改进。这些优化节省是归因于让我们的应用表现的更加贴近实际, 为mochiweb进程在每条消息之间用 hibernate。

下一步

后续, 我将调整它到一百万个连接客户端。我将部署这个测试应用到拥有充沛内存的多核64位服务器上 。这将展示有什么不同,如果有的话也可以运行在64位虚拟机上。为了模拟一百万客户连接我将详细介绍一些额外的技巧和调整 。

这个应用将发展成一系列公共子系统,在那订阅被关联于用户ID 并被存储于这个应用, 而不是当用户连接时由他们提供。我们将调入一个典型的社会网络数据集: friends。这将允许一个用户用一个用户ID登陆并且自动接收任何有他朋友生成的消息。

时间: 2024-10-12 08:53:18

用Mochiweb打造百万级Comet应用(二)的相关文章

用Mochiweb打造百万级Comet应用(一)

原文:A Million-user Comet Application with Mochiweb, Part 1 参考资料:Comet--基于 HTTP 长连接.无须在浏览器端安装插件的“服务器推”技术为“Comet” MochiWeb--建立轻量级HTTP服务器的Erlang库 在这个系列中,我将详述我所发现的mochiweb是怎样支持那么巨大的网络连接的,为大家展示怎样用mochiweb构建一个comet应用,这个应用中每个mochiweb连接都被注册到负责为不同用户派送消息的路由器上.最

HERE 使用 AWS EF 和 JFrog Artifactory 打造百万级工件 CI&amp;CD

本篇文章是根据 AWS 发布在 Youtube 上的视频资料翻译并整理而来,介绍的是 AWS re:Invent 2017大会上分享的 HERE Technology 使用 AWS EF 和 JFrog Artifactory 打造百万级工件 CI&CD 系统,支持数千活跃开发者的案例. 本次主讲人有三位,分别是 Suresh Prem(HERE principal system engineer),Yoav Landman(JFrog CTO.Co-Founder)和 Yong Kim(AWS

百万级运维经验二:Redis和Memcached的选择

看到很多人推荐使用Redis代替Memcached,我觉得这两个是不一样的东西,它们的关系应该是共存而不是替代. Memcached是个纯内存型的缓存系统,支持数据类型单一,单个缓存数据有限制,支持分布式,我觉得这是个很理想的缓存系统. Redis是个简单的NOSQL数据库,支持几种简单的数据类型,支持主从复制,支持持久化,可以看作是个内存型数据库. 由此可见,Memcached是正宗的缓存系统,Redis是个可以做缓存系统的内存型数据库. 由于Redis的数据可以设置过期时间,支持多种数据类型

迅雷链技术沙龙第一站:百万级TPS是怎样炼成的

9月15日下午,由迅雷集团主办的链创未来?迅雷链技术沙龙在北京举行,作为此系列技术沙龙的首期活动,本期邀请了来自迅雷链开放平台产品负责人.研发负责人.研发工程师.HGBC等企业的技术大咖,为区块链爱好者和开发者分享智能合约开发与DAPP实践经验等干货满满的区块链技术知识,上百名开发者大牛在现场自由交流,就区块链技术进行了深入探讨. 张慧勇:迅雷链同构多链框架解析,揭开迅雷链神秘面纱 图:迅雷链开放平台研发负责人 张慧勇 迅雷链开放平台研发负责人张慧勇率先揭开迅雷链的神秘面纱,为现场的开发者解析迅

单台服务器并发百万级配置(转)

单台服务器并发百万级配置(转) 目的:让服务器支持大量并发访问. 注:以下内容 ASP/ASP.NET IIS 用不成.另外要精通Linux,TCP/IP. 摘要:本文主要介绍利用单台PC服务器来实现可支持百万级用户并发访问的WEB服务器的实践工作.意在提出一些手段来发掘设备的潜力,充分利用设备资源,以求达到降低硬件投入成本和维护成本的目的. 随着硬件技术的飞速发展,当前单台PC 服务器的性能得到了显著提升,反之,硬件成本却在快速下降.另一方面,多数门户网站.大型社区在建设WEB服务.邮件服务等

“匿名聊聊”作者谈如何打造现象级爆款小程序

前段时间小程序“匿名聊聊”刷爆了朋友圈,可惜后面被屏蔽了.作为第一款现象级呈现爆炸级传播的小程序它是如何做到的呢?我们就跟随“匿名聊聊”作者来聊聊如何打造现象级爆款小程序. 作为第一款现象级呈现爆炸级传播的小程序,“匿名聊聊”背后的公司:朋友印象是一家深耕社交产品的公司,创始人栗浩洋和魏志成两人都公开表示,这次的“匿名聊聊”是扔出的一个问路的石子,真正的生化武器和核弹还在后面. 被寄予厚望的小程序自今年1月上线以来,一直表现的冷冷清清.流量入口没有优先级的倾斜,用户获取提供的也是模糊搜索,即便微

可编程数据平面将OpenFlow扩展至电信级应用(二)

可编程数据平面将OpenFlow扩展至电信级应用(二) 案例:基于WinPath网络处理器的电信极OpenFlow (CG-OF)客户端实现 作者:Liviu Pinchas, Tao Lang - PMC-Sierra Eddie Millsopp, Dermot Flanagan - Asidua 4. 软件考量 4.1 数据通道 为达到理想目标,可编程数据通道设备还需要配备一套丰富的数据通道软件协议. 开发该软件的目的在于满足电信级以太网协议严格的实时性要求,并提供所需的灵活度与便利性,从

【转】处理百万级以上的数据提高查询速度的方法

处理百万级以上的数据提高查询速度的方法: 1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:     select id from t where num is null     可以在num上设置默认值0,确保表中num列没有

处理百万级以上的数据提高查询速度的方法(收藏)

处理百万级以上的数据提高查询速度的方法: 1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:     select id from t where num is null     可以在num上设置默认值0,确保表中num列没有