月度归档:2014年05月

利用Remote Shell进行RabbitMQ运行时分析

Erlang原生支持分布式,虚拟机帮应用搞定底层通信的细节,程序员只要用好spawn/2!receive...end这三个并发原语,很容易就写出分布式和位置透明的应用程序。

Remote Shell是得益于Erlang分布式特性而实现的问题跟踪和排错神器。从工作原理上看,Remote Shell其实就是本地开一个Erlang节点,通过消息传递机制与本地(或远程)Erlang节点进行交互:

Erlang Remote Shell

通过Remote Shell,我们甚至可以直接实时诊断生产环境下的RabbiMQ运行情况,步骤如下:

1. 打开Erlang Shell。如果是分析远程RabbitMQ节点,需要通过-setcookie参数设置cookie,这也是Erlang节点之间互联唯一的认证方式。另外使用-name或者-sname参数指定节点名称,-name和-sname的区别见官方说明。一般来说,RabbitMQ使用的是短节点名称(-sname)的方式。

$ erl -sname debug
Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10]

Eshell V5.10.4  (abort with ^G)
(debug@localhost)1>

2. 按Ctrl+g进入命令模式。h命令查看帮助,r命令打开远程节点的Remote Shell,c命令进入Remote Shell。

Eshell V5.10.4  (abort with ^G)
(debug@localhost)1>
User switch command
 --> h
  c [nn]            - connect to job
  i [nn]            - interrupt job
  k [nn]            - kill job
  j                 - list all jobs
  s [shell]         - start local shell
  r [node [shell]]  - start remote shell
  q                 - quit erlang
  ? | h             - this message
 --> r 'rabbit@localhost'
 --> j
   1  {shell,start,[init]}
   2* {'rabbit@localhost',shell,start,[]}
 --> c 2
Eshell V5.10.4  (abort with ^G)
(rabbit@localhost)1>

3. 如果出现了类似“(rabbit@localhost)1>”的提示,表示已经进入RabbitMQ运行时的Remote Shell,此时可以执行任意Erlang命令来分析和跟踪,只需谨记:每个操作都是作用于RabbitMQ运行时进程,敲回车之前要评估风险。比如一不小心敲了q().的话,会关闭RabbitMQ进程。

3.1 查看当前节点的Erlang进程限制和数量。

(rabbit@localhost)1> erlang:system_info(process_limit).
1048576
(rabbit@localhost)2> erlang:system_info(process_count).
227

3.2 查看内存使用情况。

(rabbit@localhost)3> erlang:memory().
[{total,2579219576},
 {processes,86890768},
 {processes_used,86890768},
 {system,2492328808},
 {atom,1238225},
 {atom_used,1225142},
 {binary,11733520},
 {code,20376011},
 {ets,2452957768}]

3.3 使用etop查看内存占用量最大的进程。

(rabbit@localhost)4> spawn(fun() -> etop:start([{output, text}, {interval, 5}, {lines, 5}, {sort, memory}]) end).

上述参数表示,每隔5秒显示当前系统中内存使用量前5的进程。另外,sort参数的可选值为: runtime(默认),reductions,memory,msg_q。停止etop可用etop:stop().命令。etop输出如下:

====================================================================================
 'rabbit@localhost'                                                        12:14:17
 Load:  cpu         0               Memory:  total     2521522    binary      11459
        procs     231                        processes   87562    code        19898
        runq        0                        atom         1209    ets       2395508

Pid            Name or Initial Func    Time    Reds  Memory    MsgQ Current Functio
------------------------------------------------------------------------------------
<6016.555.0>   gen:init_it/6            '-'       071303712       0 gen_server2:proc
<6016.198.0>   msg_store_transient      '-'       0  372488       0 erlang:hibernate
<6016.279.0>   gen:init_it/6            '-'       0  264224       0 gen_server2:proc
<6016.29765.2> etop_txt:init/1          '-'    7788  263968       0 etop:update/1
<6016.226.0>   rabbit_mgmt_external     '-'       0  196984       0 gen_server:loop/
====================================================================================

3.4 查看某个进程详情。

(rabbit@localhost)5> erlang:process_info(pid(0,555,0)).
[{current_function,{gen_server2,process_next_msg,1}},
 {initial_call,{proc_lib,init_p,5}},
 {status,waiting},
 {message_queue_len,0},
 {messages,[]},
 {links,[#Port<0.16962>,<0.207.0>]},
 {dictionary,[{fhc_age_tree,{1,
                             {{1434,967179,600682},#Ref<0.0.0.7288>,nil,nil}}},
              {{"/Applications/rabbitmq_server-3.1.3/sbin/../var/lib/rabbitmq/mnesia/rabbit@localhost/queues/850UOO636QPB9FAFP10MQ2OM/journal.jif",
                fhc_file},
               {file,1,true}},
              {{xtype_to_module,direct},rabbit_exchange_type_direct},
              {credit_blocked,[]},
              {{#Ref<0.0.0.7288>,fhc_handle},
               {handle,{file_descriptor,prim_file,{#Port<0.16962>,34}},
                       0,false,0,infinity,[],true,
                       "/Applications/rabbitmq_server-3.1.3/sbin/../var/lib/rabbitmq/mnesia/rabbit@localhost/queues/850UOO636QPB9FAFP10MQ2OM/journal.jif",

                       [write,binary,raw|...],
                       [{write_buffer,...}],
                       true,true,...}},
              {{credit_from,<0.198.0>},1913},
              {'$ancestors',[rabbit_amqqueue_sup,rabbit_sup,<0.141.0>]},
              {guid,{{2697021060,4147890605,2048749941,133927191},0}},
              {'$initial_call',{gen,init_it,6}},
              {{credit_to,<0.6135.0>},50}]},
 {trap_exit,true},
 {error_handler,error_handler},
 {priority,normal},
 {group_leader,<0.140.0>},
 {total_heap_size,8912793},
 {heap_size,8912793},
 {stack_size,7},
 {reductions,5913527923},
 {garbage_collection,[{min_bin_vheap_size,46422},
                      {min_heap_size,233},
                      {fullsweep_after,65535},
                      {minor_gcs,0}]},
 {suspending,[]}]

4. 退出Remote Shell:Ctrl + c, a

(rabbit@localhost)6>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution

可见,善用Remote Shell可以得到实时和详尽的RabbitMQ运行时信息。理论上,Remote Shell适用于任一Erlang节点,因此只要用Erlang实现的服务器端程序,都可以通过这种方式进行问题定位和故障诊断,比如性能优化,内存泄露检查,CPU繁忙检测等等,非常方便。

--EOF--

erlang bit syntax

已知erlang bit syntax语法:Bit Syntax

1. 编写一个函数来反转某个二进制型的字节顺序。

1
2
reverse(Bin) ->
  iolist_to_binary(lists:reverse(binary_to_list(Bin))).

2. 编写一个term_to_packet(Term) -> Packet函数,通过调用term_to_binary(Term)来生成并返回一个二进制型,它内含长度为4个字节的包头N,后跟N个字节的数据。

1
2
3
4
term_to_packet(Term) ->
  Bin = term_to_binary(Term),
  BinLen = byte_size(Bin),
  <<BinLen:32, Bin:BinLen/binary>>.

3. 编写一个反转函数packet_to_term(Packet) -> Term, 使它成为前一个函数的逆向函数。

1
2
3
packet_to_term(Packet) ->
  <<_:32, Bin/binary>> = Packet,
  binary_to_term(Bin).

4. 编写一个函数来反转某个二进制型所包含的位。

1
2
3
4
5
6
7
bit_reverse(Bin) ->
  bit_reverse(Bin, <<>>).
bit_reverse(<<>>, Dbin) ->
  Dbin;
bit_reverse(Sbin, Dbin) ->
  <<H:1, T/bits>> = Sbin,
  bit_reverse(T, <<H:1, Dbin/bits>>).

在对位串进行模式匹配时,要注意最后一个片段(segment)的匹配格式,需要要加上binary(bytes)/bitstring(bits):

1
2
3
4
5
6
7
To match out the rest of a binary, specify a binary field without size:
foo(<<A:8,Rest/binary>>) ->
The size of the tail must be evenly divisible by 8.
 
To match out the rest of a bitstring, specify a field without size:
foo(<<A:8,Rest/bitstring>>) ->
There is no restriction on the number of bits in the tail.

--EOF--

Valve对异步Servlet的支持

今天发现的一个问题:上线已久的Tomcat7 + Servlet3.0实现的long polling功能异常。相同的代码本地运行正常,远程debug线上代码时发现程序在执行到
AsyncContext asyncContext = request.startAsync(request, response)时抛IllegalStateException异常:

1
2
3
java.lang.IllegalStateException: Not supported.
at org.apache.catalina.connector.Request.startAsync(Request.java)
......

比对了一下代码仓库中异步Servlet的基本配置,没有可疑的地方,于是基本可以确定是Servlet容器的配置问题。

找来线上Tomcat的server.xml文件,与本地的比对了一下,发现问题所在,线上Tomcat配置中的Context节点多了一条Valve配置:

1
<Valve className="org.jboss.web.rewrite.RewriteValve" />

删掉它或者在Valve节点中加一个asyncSupported="true"的属性:

1
<Valve className="org.jboss.web.rewrite.RewriteValve" asyncSupported="true" />

Tomcat所实现的Servlet3.0规定,请求在到达异步Servlet所在的调用链中,所有valve和filter都要显式声明支持asyncSupported="true",无论通过注解还是XML配置的方式。

其实还有一种比较猥琐的方式,在HttpServletRequest对象中加参数,从而绕过上述的对valve和filter的asyncSupported属性的依赖:

1
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

它的副作用就是造成了容器依赖,这段代码只对Tomcat容器有效。

--EOF--