ITKeyword,专注技术干货聚合推荐

注册 | 登录

100万并发连接服务器笔记之Erlang完成1M并发连接目标

shallowgrave 分享于 2014-02-26

推荐:100万并发连接服务器笔记之1M并发连接目标达成

第四个遇到的问题:tcp_mem 在服务端,连接达到一定数量,诸如50W时,有些隐藏很深的问题,就不断的抛出来。 通过查看dmesg命令查看,发现大量TCP: too many of

2020腾讯云共同战“疫”,助力复工(优惠前所未有!4核8G,5M带宽 1684元/3年),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1054

2020阿里云最低价产品入口,含代金券(新老用户有优惠),
地址https://www.aliyun.com/minisite/goods

前言

使用Erlang语言也写一个测试和前面大同小异的测试,在100万个并发连接用户情况下,就是想观察一下极显情况下的表现。
这个测试使用了优秀的Erlang界的明星框架cowboy,加单易用的接口,避免了我们对HTTP栈再次进行闭门造车。

测试Erlang服务器

运行在VMWare Workstation 9中,64位Centos 6.4系统,分配14.9G内存左右,双核4个线程,服务器安装Erlang/OTP R16B,最新版本支持异步代码热加载,很赞。

下载安装

本系统已经提前安装JDK,只需要安装Erlang好了。

安装依赖



 
  yum install build-essential m4 

yum install openssl 

yum install openssl-devel 

yum install unixODBC 

yum install unixODBC-devel 

yum -y install openssl make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel

yum install xsltproc fop
 

源代码安装

#wget https://elearning.erlang-solutions.com/binaries/sources/otp_src_R16B.tar.gz
#tar xvf otp_src_R16B.tar.gz
cd otp_src_R16B
./configure --prefix=/usr/local/erlang --enable-hipe --enable-threads --enable-smp-support --enable-kernel-poll
make
make install

添加到环境变量中(/etc/profile)

export ERL_HOME=/usr/local/erlang export PATH=$ERL_HOME/bin:$PATH

保存生效

source /etc/profile

测试进程创建

这里拷贝《Erlang程序设计》一书提供的processes.erl源码,稍作修改。

1234567891011121314151617181920212223
         
          -
          module
          (
          processes
          ).
         
         
          -
          export
          ([
          max
          /
          1
          ]).
         
         
           
         
         
          max
          (
          N
          ) 
          ->
         
         	
          Max 
          = 
          erlang
          :
          system_info
          (
          process_limit
          ),
         
         	
          io
          :
          format
          (
          "Maxmium allowed process is 
          ~p
           
          ~n
          "
          , 
          [
          Max
          ]),
         
         	
          statistics
          (
          runtime
          ),
         
         	
          statistics
          (
          wall_clock
          ),
         
         	
          L 
          = 
          for
          (
          1
          , 
          N
          , 
          fun
          () 
          -> 
          spawn
          (
          fun
          () 
          -> 
          wait
          () 
          end
          ) 
          end
          ),
         
         	
          {_, 
          Time1
          } 
          = 
          statistics
          (
          runtime
          ),
         
         	
          {_, 
          Time2
          } 
          = 
          statistics
          (
          wall_clock
          ),
         
         	
          lists
          :
          foreach
          (
          fun
          (
          Pid
          ) 
          -> 
          Pid 
          ! 
          die 
          end
          , 
          L
          ),
         
         	
          U1 
          = 
          Time1 
          * 
          1000 
          / 
          N
          ,
         
         	
          U2 
          = 
          Time2 
          * 
          1000 
          /
          N
          ,
         
         	
          io
          :
          format
          (
          "Process spawn time=
          ~p
           (
          ~p
          ) microseconds 
          ~n
          "
          , 
          [
          U1
          , 
          U2
          ]).
         
         
           
         
         
          wait
          () 
          ->
         
         	
          receive
         
         		
          die 
          -> 
          void
         
         	
          end
          .
         
         
           
         
         
          for
          (
          N
          , 
          N
          , 
          F
          ) 
          -> 
          [
          F
          ()];
         
         
          for
          (
          I
          , 
          N
          , 
          F
          ) 
          -> 
          [
          F
          ()|
          for
          (
          I
          +
          1
          , 
          N
          , 
          F
          )].
         
view raw processes.erl hosted with ❤ by  GitHub

创建一百万个进程,看看大概花费多少时间。

[yongboy@base erlang]$ erl +P 10240000
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V5.10.1 (abort with ^G)
1> processes:max(200000).
Maxmium allowed process is 16777216 Process spawn time=1.1 (2.68) microseconds ok
2> processes:max(200000).
Maxmium allowed process is 16777216 Process spawn time=1.7 (2.33) microseconds ok
3> processes:max(200000).
Maxmium allowed process is 16777216 Process spawn time=1.55 (2.12) microseconds ok
4> processes:max(1000000).
Maxmium allowed process is 16777216 Process spawn time=2.97 (3.967) microseconds ok
5> processes:max(1000000).
Maxmium allowed process is 16777216 Process spawn time=2.4 (2.729) microseconds ok
6> processes:max(1000000).
Maxmium allowed process is 16777216 Process spawn time=2.19 (2.735) microseconds ok
7> processes:max(10000000).
Maxmium allowed process is 16777216 Process spawn time=3.328 (4.2777) microseconds ok
8> processes:max(10000000).
Maxmium allowed process is 16777216 Process spawn time=3.144 (3.1361) microseconds ok
9> processes:max(10000000).
Maxmium allowed process is 16777216 Process spawn time=3.394 (3.2051) microseconds ok

恩,创建1000万个进程,每一个进程花费3.4微秒(μs)的CPU时间,相当于4.3微秒(μs)的消耗时间(亦即消耗的真实时间),在一定量的区间内,其值变化,可以看做是一个常量。

初始化小问题

Cowboy初始化需要事项

我们做的简单程序,使用了非常受欢迎的cowboy框架,其启动函数:

cowboy:start_http(my_http_listener, 100,
[{port, 8000}],
[{env, [{dispatch, Dispatch}]}]
),

Cowboy默认支持1024个连接,服务器端输出:

online user 1122 online user 1123 

停滞于此,后续的连接只能排队等候了。

这里设置支持无限个连接好了。

{max_connections, infinity}

话说,Cowboy为Erlang世界的明星产品,绝对值得一试!

Erlang默认创建进程限制

Erlang在我的机器上默认允许创建的线程也是有限的:

erlang:system_info(process_limit).
262144

才26万个,不够用。在启动脚本(start.sh)处添加允许创建的最大线程支持:

#!/bin/sh
erl +K true +P 10240000 -sname testserver -pa ebin -pa deps/*/ebin -s htmlfilesimple\
-eval "io:format(\"Server start with port 8000 Success!~n\")."

脚本启动后现在在erl shell中测试一下:

erlang:system_info(process_limit).
16777216

数量完全够用了。

开启erlang的epoll属性

+K true | false 是否开启kernel poll,就是epoll;

不开启,测试过程中,在内存完好情况下,经常会有连接失败情况。

使用cowboy_req:compact降低内存占用

一旦你从request对象中获取到足够的信息,以后不再获取其附加属性时,调用compact/1函数可去除无用属性,起到节省内存作用。 
程序里面调用如下:

推荐:五 100万并发连接服务器笔记之Java Netty处理1M连接会怎么样

前言 每一种该语言在某些极限情况下的表现一般都不太一样,那么我常用的Java语言,在达到100万个并发连接情况下,会怎么样呢,有些好奇,更有些期盼。 这次使用

init(_Any, Req, State) -> NowCount = count_server:welcome(),
io:format("online user ~p :))~n", [NowCount]),
output_first(Req),
Req2 = cowboy_req:compact(Req),
{loop, Req2, State, hibernate}.

在本例中精测压缩内存效果不明显,因为 ,测试端输出的HTTP头部压根就没有几个。

Cowboy无法处理没有header的HTTP请求

这里需要牢记,也不能算是BUG,前面的client2.c源码,就未曾设置HTTP Header元数据,需要做些简单修改,修改之后的测试端程序文件名为client5.c, 可以到这里 下载client.c

Cowboy处理长连接

Cowboy很贴心的提供了cowboy_loop_handler behaviour。在init/3函数中,可以进入休眠状态,节省内存,消息到达时,被唤醒,值得一赞! 其定义如下:

123456789101112131415161718192021222324252627
         
          -
          module
          (
          cowboy_loop_handler
          ).
         
         
           
         
         
          -
          type 
          opts
          () 
          :: 
          any
          ().
         
         
          -
          type 
          state
          () 
          :: 
          any
          ().
         
         
          -
          type 
          terminate_reason
          () 
          :: 
          {
          normal
          , 
          shutdown
          }
         
         	
          | 
          {
          normal
          , 
          timeout
          }
         
         	
          | 
          {
          error
          , 
          atom
          ()}.
         
         
           
         
         
          %% 处理用户第一次请求,可以处理请求,为当前会话生成状态数据,进行等待或者休眠,或者关闭掉当前会话。
         
         
          -
          callback 
          init
          ({
          atom
          (), 
          http
          }, 
          Req
          , 
          opts
          ())
         
         	
          -> 
          {
          ok
          , 
          Req
          , 
          state
          ()}
         
         	
          | 
          {
          loop
          , 
          Req
          , 
          state
          ()}
         
         	
          | 
          {
          loop
          , 
          Req
          , 
          state
          (), 
          hibernate
          }
         
         	
          | 
          {
          loop
          , 
          Req
          , 
          state
          (), 
          timeout
          ()}
         
         	
          | 
          {
          loop
          , 
          Req
          , 
          state
          (), 
          timeout
          (), 
          hibernate
          }
         
         	
          | 
          {
          shutdown
          , 
          Req
          , 
          state
          ()}
         
         	
          | 
          {
          upgrade
          , 
          protocol
          , 
          module
          ()}
         
         	
          | 
          {
          upgrade
          , 
          protocol
          , 
          module
          (), 
          Req
          , 
          opts
          ()}
         
         	
          when 
          Req
          ::
          cowboy_req
          :
          req
          ().
         
         
          %% init若返回loop状态信息,等待接收消息,可以继续接收,或者继续休眠
         
         
          -
          callback 
          info
          (
          any
          (), 
          Req
          , 
          State
          )
         
         	
          -> 
          {
          ok
          , 
          Req
          , 
          State
          }
         
         	
          | 
          {
          loop
          , 
          Req
          , 
          State
          }
         
         	
          | 
          {
          loop
          , 
          Req
          , 
          State
          , 
          hibernate
          }
         
         	
          when 
          Req
          ::
          cowboy_req
          :
          req
          (), 
          State
          ::
          state
          ().
         
         
          %% 会话终端时被调用,可执行会话清理工作
         
         
          -
          callback 
          terminate
          (
          terminate_reason
          (), 
          cowboy_req
          :
          req
          (), 
          state
          ()) 
          -> 
          ok
          .
         
view raw cowboy_loop_handler.erl hosted with ❤ by  GitHub 注意 hibernate 和 timeout 参数,按照实际需求返回即可。

htmlfile_handler示范代码如下:

1234567891011121314151617181920212223242526272829303132
         
          -
          module
          (
          htmlfile_handler
          ).
         
         
          -
          behaviour
          (
          cowboy_loop_handler
          ).
         
         
          -
          export
          ([
          init
          /
          3
          , 
          info
          /
          3
          , 
          terminate
          /
          3
          ]).
         
         
          -
          define
          (
          HEARBEAT_TIMEOUT
          , 
          20
          *
          1000
          ).
         
         
          -
          record
          (
          status
          , 
          {
          count
          =
          0
          }).
         
         
           
         
         
          init
          (_
          Any
          , 
          Req
          , 
          State
          ) 
          ->
         
         	
          NowCount 
          = 
          count_server
          :
          welcome
          (),	
         
         	
          io
          :
          format
          (
          "online user 
          ~p~n
          "
          , 
          [
          NowCount
          ]),
         
         	
         
         	
          output_first
          (
          Req
          ),
         
         	
          %%Req2 = cowboy_req:compact(Req),
         
         	
          {
          loop
          , 
          Req
          , 
          State
          , 
          hibernate
          }.
         
         
           
         
         
          %% POST/Short Request
         
         
          info
          (_
          Any
          , 
          Req
          , 
          State
          ) 
          ->
         
         	
          {
          loop
          , 
          Req
          , 
          State
          , 
          hibernate
          }.
         
         
           
         
         
          output_first
          (
          Req
          ) 
          ->
         
         	
          {
          ok
          , 
          Reply
          } 
          = 
          cowboy_req
          :
          chunked_reply
          (
          200
          , 
          [{
          <<
          "Content-Type"
          >>
          , 
          <<
          "text/html; charset=utf-8"
          >>
          },
         
         								 
          {
          <<
          "Connection"
          >>
          , 
          <<
          "keep-alive"
          >>
          }], 
          Req
          ),
         
         	
          cowboy_req
          :
          chunk
          (
          <<
          "<html><body><script>var _ = function (msg) { parent.s._(msg, document); };</script> "
          >>
          , 
         
         								
          Reply
          ),
         
         	
          cowboy_req
          :
          chunk
          (
          gen_output
          (
          "1::"
          ), 
          Reply
          ).
         
         
           
         
         
          gen_output
          (
          String
          ) 
          ->
         
         	
          DescList 
          = 
          io_lib
          :
          format
          (
          "<script>_('
          ~s
          ');</script>"
          , 
          [
          String
          ]),
         
         	
          list_to_binary
          (
          DescList
          ).
         
         
           
         
         
          terminate
          (
          Reason
          , 
          _
          Req
          , 
          _
          State
          ) 
          ->
         
         	
          NowCount 
          = 
          count_server
          :
          bye
          (),	
         
         	
          io
          :
          format
          (
          "offline user 
          ~p
           :(( 
          ~n
          "
          , 
          [
          NowCount
          ]).
         
view raw htmlfile_handler.erl hosted with ❤ by  GitHub

100万并发连接达成

测试过程跌跌撞撞的,虽然这中间因为内存问题抛出若干的异常,但也达到100W连接的数量

online user 1022324 :))
online user 1022325 :))
online user 1022326 :))
online user 1022327 :))
online user 1022328 :))
online user 1022329 :))
online user 1022330 :))
online user 1022331 :))
online user 1022332 :))
online user 1022333 :))
online user 1022334 :))
online user 1022335 :))
online user 1022336 :))
online user 1022337 :))
online user 1022338 :))

可以看到状态信息 

算一下: 14987952K = 14636M 
14987952/1022338 = 14.7K/Connection

未启动时的内存情况:

 total used free shared buffers cached
Mem: 14806 245 14561 0 12 60
-/+ buffers/cache: 172 14634
Swap: 3999 0 3999

启动后的内存占用情况:

 total used free shared buffers cached
Mem: 14806 435 14370 0 12 60
-/+ buffers/cache: 363 14443
Swap: 3999 0 3999

用户量达到1022338数量后的内存一览:

 total used free shared buffers cached
Mem: 14806 14641 165 0 1 5
-/+ buffers/cache: 14634 172
Swap: 3999 1068 2931

可以看到,当前内存不够用了,需要虚拟内存配合了。

查看一下当前进程的内存占用

ps -o rss= -p `pgrep -f 'sname testserver'`
4869520

这样算起来,系统为每一个进程持有 4869520/1022338 = 4.8K 内存。 这个值只是计算物理内存,实际上连虚拟内存都占用了,估计在4.8K-6.8K之间吧。

和C语言相比,内存占用相当大,我虚拟机器分配的15G内存,也仅仅处理达到100万的连接,已经接近极限时,会发现陆陆续续的有连接失败。

不得不说的代码热加载

运行时系统的代码热加载功能,在这个实例中,通过vi修改了htmlfile_handler.erl文件,主要修改内容如下:

io:format("online user ~p :))~n", [NowCount]),
......
io:format("offline user ~p :(( ~n", [NowCount]).

执行make,编译

[root@base htmlfilesimple]# make
==> ranch (get-deps)
==> cowboy (get-deps)
==> htmlfilesimple (get-deps)
==> ranch (compile)
==> cowboy (compile)
==> htmlfilesimple (compile)
src/htmlfile_handler.erl:5: Warning: record status is unused
src/htmlfile_handler.erl:29: Warning: variable 'Reason' is unused
Compiled src/htmlfile_handler.erl

很好,非常智能的rebar,自动只编译了htmlfile_handler.erl一个文件,然后通知Erlang的运行环境进行代码热替换吧。

(testserver@base)4> code:load_file(htmlfile_handler). 

查看日志输出控制台,可以看到已经生效,同时也保存着到状态数据等。

非常利于运行时调试,即不伤害在线状态数据,又能即时修改,赞!但生产环境下,一般都是版本切换,OTP的版本切换,测试或马上修改bug时,着实有些复杂。

小结

和C相比,处理相同的事情(100万并发连接),及其简单,但Erlang会需要更多的内存,廉价的内存可以满足,只是我的搭建在Vmware中的虚拟机器已经达到了它所要求的极限。 
完整的源代码,可点击这里下载

原文链接:http://www.blogjava.net/yongboy/archive/2013/04/28/398558.html

推荐:100万并发连接服务器笔记之Java Netty处理1M连接会怎么样

前言 每一种该语言在某些极限情况下的表现一般都不太一样,那么我常用的Java语言,在达到100万个并发连接情况下,会怎么样呢,有些好奇,更有些期盼。 这次使用

前言 使用Erlang语言也写一个测试和前面大同小异的测试,在100万个并发连接用户情况下,就是想观察一下极显情况下的表现。 这个测试使用了优秀的Erlang界的明星框架cowboy,加单易用的接口,避

相关阅读排行


相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。