女主宣言

服务的核心需求是长连接的需求,最近由于业务线的需求,突然增加对发消息的接口调用,由于之前对接口的压力测试还是处于在物理机(24核心,64G内存),目前部署在线上的数据是(16核心,24G内存)的线上环境上,物理机器的测试数据是2W+的测试结果,就按照当时3千一台的评估,线上在真实提供服务的时候,却和预期有很大出入,接着详细介绍一下这次的性能瓶颈,以及如何解决,解决的方法本身不麻烦,主要还是分享一下整个的排查过程,也希望能够学习一些线上的问题排查的思路,欢迎大家积极留言讨论。

PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!

问题描述

业务方的请求增多,流量的整体峰值QPS在5000,服务部署在九台虚拟机器上(分属于三个机房,三个不同的集群),由于流量不够均匀,流量开始主要集中到了其中一个机房的三台机器上,基本每台机器上的QPS在1350左右,而次数服务的CPU使用率已经达到了90%以上,触发了相应的报警,而次数的内存使用不大,基本维持在10%左右。

CPU使用率排查?

第一感觉CPU消耗比较多,但是服务本身并不是一个计算密集型的服务,CPU保持这么高的使用率肯定是在执行指令,服务本身没有出现无响应,或其他的异常表现,可以排除程序中存在死循环等验证的程序BUG导致的问题。那么,接下来就是先确定CPU到底在做什么,只有找到了消耗在哪里,才能针对性进行调整。备注:笔者使用的语言是小众Erlang语言,基于EVM。

准备一些需要检查的场景?

检查CPU的使用情况

recon:scheduler_usage(1000).

检查以下内存使用最多的前20进程

recon:proc_count(memory, 20).

获取节点上reduction使用最多的前20进程

recon:proc_count(reductions, 20).

获取一段时间reductions使用最多的20个进程

%% {<0.4490.1084>,983981,
    %%  [{current_function,{gen_server,loop,7}},
    %%   {initial_call,{proc_lib,init_p,5}}]}
    recon:proc_window(reductions, 5, 1000).
    [rp({recon:info(P), Num}) || {P, Num, _} <- recon:proc_window(reductions, 20, 5000)].

查询当前node的统计数据的变化

%% node_stats(N, Interval, fun(X,_) -> io:format("~p~n",[X]) end, nostate).
    %% 每个500ms打印一个当前的统计信息,总共打印5次
    recon:node_stats_print(5, 500).

线上环境的排查?

线上由于解决了负载均衡的问题之后,每台机器的负载峰值QPS也不到500,平时基本在200QPS左右,只能通过线上当前的CPU使用较大的系统,根据线上的情况评估出CPU消耗最多的位置(其中接口调用依然占用较大的使用比例,否则其他的信息会出现干扰)

// TCP接收相关的进程
    {[{meta,[{registered_name,[]},
             {dictionary,[]},
             {group_leader,<0.1466.0>},
             {status,waiting}]},
      {signals,[{links,[<0.1900.0>]},
                {monitors,[]},
                {monitored_by,[#Port<0.21>]},
                {trap_exit,false}]},
      {location,[{initial_call,{ranch_acceptor,loop,3}},
                 {current_stacktrace,[{prim_inet,accept0,3,[]},
                                      {inet_tcp,accept,2,[{file,"inet_tcp.erl"},{line,175}]},
                                      {ranch_acceptor,loop,3,
                                                      [{file,"/home/sync360/iot_mqtt/_build/default/lib/ranch/src/ranch_acceptor.erl"},
                                                       {line,28}]}]}]},
      {memory_used,[{memory,5800},
                    {message_queue_len,0},
                    {heap_size,233},
                    {total_heap_size,609},
                    {garbage_collection,[{max_heap_size,#{error_logger => true,kill => true,size => 0}},
                                         {min_bin_vheap_size,46422},
                                         {min_heap_size,233},
                                         {fullsweep_after,1000},
                                         {minor_gcs,927}]}]},
      {work,[{reductions,51141345}]}],
     195520}
    // Kafka写入相关的
    {[{meta,[{registered_name,[]},
             {dictionary,[{'$initial_call',{brod_producer,init,1}},
                          {'$ancestors',[<0.2043.0>,<0.2041.0>,event_post,brod_sup,
                                         <0.1551.0>]}]},
             {group_leader,<0.1550.0>},
             {status,waiting}]},
      {signals,[{links,[<0.2043.0>]},
                {monitors,[{process,<0.3197.0>}]},
                {monitored_by,[]},
                {trap_exit,false}]},
      {location,[{initial_call,{proc_lib,init_p,5}},
                 {current_stacktrace,[{gen_server,loop,7,
                                                  [{file,"gen_server.erl"},{line,403}]},
                                      {proc_lib,init_p_do_apply,3,
                                                [{file,"proc_lib.erl"},{line,249}]}]}]},
      {memory_used,[{memory,21696},
                    {message_queue_len,0},
                    {heap_size,1598},
                    {total_heap_size,2585},
                    {garbage_collection,[{max_heap_size,#{error_logger => true,kill => true,size => 0}},
                                         {min_bin_vheap_size,46422},
                                         {min_heap_size,233},
                                         {fullsweep_after,1000},
                                         {minor_gcs,894}]}]},
      {work,[{reductions,25976991}]}],
     108405}

通过预估,准备压力测试环境,尽可能复现线上环境?

初步对比了线上的环境和之前的压测数据的差异这么大,对比了相应的变化发现,变化点主要有以下三个

  1. 机器配置的不同
  2. 线上环境多了Nginx的代理
  3. 增加了用来排查问题的日志(正常处理的日志)
  4. 增加了写入Kafka的插件(没有合适的kafka测试环境)

综合对比上面的情况,Kafka的最有可能是系统的瓶颈点

进行压力测试

stress设置cpu使用率 cpu使用率调整_stress设置cpu使用率

备注:以上数据仅仅进行了一组测试,没有进行多次,但是从大体的数据对比中已经可以说明问题,环境主要和线上在Kafka的性能上存在不一样,压测环境的使用的共有集群,线上环境使用的是私有集群。

数据分析

进行组1的测试数据可以看出性能指标确实不容乐观,通过对当时环境的CPU的使用情况进行抓包看出

(emqx@10.216.6.159)1> [rp({recon:info(P), Num}) || {P, Num, _} <- recon:proc_window(reductions, 20, 5000)].
    %%写日志的进程
    {[{meta,[{registered_name,logger_disk_log_h_file_for_info},
             {dictionary,[{'$ancestors',[logger_sup,kernel_sup,
                                         <0.1366.0>]},
                          {'$initial_call',{logger_h_common,init,1}}]},
             {group_leader,<0.1365.0>},
             {status,running}]},
      {signals,[{links,[<0.1399.0>,<0.1405.0>]},
                {monitors,[]},
                {monitored_by,[<0.13884.752>,<0.13086.752>,<0.12947.752>,
                               <0.12443.752>,<0.14194.752>,<0.6277.752>,<0.13735.752>,
                               <0.13916.752>,<0.13549.752>,<0.13686.752>,<0.14010.752>,
                               <0.5855.752>,<0.13455.752>,<0.12666.752>,<0.13945.752>,
                               <0.12490.752>,<0.32587.751>,<0.5895.752>,<0.13400.752>,
                               <0.13271.752>,<0.13976.752>,<0.1400.0>]},
                {trap_exit,true}]},
      {location,[{initial_call,{proc_lib,init_p,5}},
                 {current_stacktrace,[{gen_server,loop,7,
                                                  [{file,"gen_server.erl"},{line,403}]},
                                      {proc_lib,init_p_do_apply,3,
                                                [{file,"proc_lib.erl"},{line,249}]}]}]},
      {memory_used,[{memory,35784},
                    {message_queue_len,1},
                    {heap_size,2586},
                    {total_heap_size,4284},
                    {garbage_collection,[{max_heap_size,#{error_logger => true,kill => true,size => 0}},
                                         {min_bin_vheap_size,46422},
                                         {min_heap_size,233},
                                         {fullsweep_after,1000},
                                         {minor_gcs,199}]}]},
      {work,[{reductions,758926433}]}],
     845062}
    %% ranch接收HTTP相关的进程
    {[{meta,[{registered_name,[]},
             {dictionary,[]},
             {group_leader,<0.1457.0>},
             {status,running}]},
      {signals,[{links,[<0.1757.0>]},
                {monitors,[]},
                {monitored_by,[]},
                {trap_exit,false}]},
      {location,[{initial_call,{ranch_acceptor,loop,3}},
                 {current_stacktrace,[{ranch_conns_sup,start_protocol,2,
                                                       [{file,"/home/sync360/iot_mqtt/_build/default/lib/ranch/src/ranch_conns_sup.erl"},
                                                        {line,73}]},
                                      {ranch_acceptor,loop,3,
                                                      [{file,"/home/sync360/iot_mqtt/_build/default/lib/ranch/src/ranch_acceptor.erl"},
                                                       {line,34}]}]}]},
      {memory_used,[{memory,11772},
                    {message_queue_len,0},
                    {heap_size,987},
                    {total_heap_size,1363},
                    {garbage_collection,[{max_heap_size,#{error_logger => true,kill => true,size => 0}},
                                         {min_bin_vheap_size,46422},
                                         {min_heap_size,233},
                                         {fullsweep_after,1000},
                                         {minor_gcs,960}]}]},
      {work,[{reductions,2681352616}]}],
     624395}
    %% 写入kafka相关的进程
    {[{meta,[{registered_name,[]},
             {dictionary,[{'$ancestors',[<0.22379.258>,<0.22372.258>,
                                         event_post,brod_sup,<0.22452.258>]},
                          {'$initial_call',{brod_producer,init,1}}]},
             {group_leader,<0.22459.258>},
             {status,waiting}]},
      {signals,[{links,[<0.22379.258>]},
                {monitors,[{process,<0.15563.737>}]},
                {monitored_by,[<0.15356.752>,<0.8237.752>,<0.15477.752>,
                               <0.9175.752>,<0.14682.752>,<0.14831.752>,<0.15023.752>,
                               <0.2041.752>,<0.14115.752>,<0.14779.752>,<0.14640.752>,
                               <0.14804.752>]},
                {trap_exit,false}]},
      {location,[{initial_call,{proc_lib,init_p,5}},
                 {current_stacktrace,[{gen_server,loop,7,
                                                  [{file,"gen_server.erl"},{line,403}]},
                                      {proc_lib,init_p_do_apply,3,
                                                [{file,"proc_lib.erl"},{line,249}]}]}]},
      {memory_used,[{memory,68696},
                    {message_queue_len,0},
                    {heap_size,4185},
                    {total_heap_size,8370},
                    {garbage_collection,[{max_heap_size,#{error_logger => true,kill => true,size => 0}},
                                         {min_bin_vheap_size,46422},
                                         {min_heap_size,233},
                                         {fullsweep_after,1000},
                                         {minor_gcs,11}]}]},
      {work,[{reductions,61643916}]}],
     219381}

分析结论:

  1. 通过上述CPU的占用消耗,比线上环境多了LOG写入的开销,这个也和代码对比中的LOG增加相互对应。
  2. 对比1和9(线上环境,扩容一台机器进行测试,由于不能频繁写入数据,这里只对线上环境进行了一组 测试),发现由于不同的Kafka的影响,对整体性能的差异近3倍。具体的差异的原因不在本次的性能 调优里面。
  3. 对比1和2的数据,可以看出Kafka在没有的情况下,基本是有的2.4倍。
  4. 对比4和1的数据,Nginx没有的情况下是有的1.2倍。
  5. 对比7和1的数据,Log没有的情况下是有的2.3倍。

分析结论

通过上述数据分析,Kafka,Log是性能下降的主要原因,Nginx的代理也有一定的衰减。针对以上三点并且结合项目的具体需要,主要进行了以下三个方向的优化调整

        1.Log日志记录

         a.去掉非必须的排查日志,该日志早期主要为了帮助排查定位问题

  1. b.是否可以提高写日志的效率?
    比如减少日志的格式化操作,目前为了方便,程序会将日志格式化为JSON形式收集,一方面工具进行展示
    2.Nginx代理
    a.检查Nginx的参数设置是否合理,日志记录是否合理?

    具体的调研和测试还在进一步进行中,目前的方向主要是日志方面和是否可以采用长连接的形式

    3.Kafka写入

    目前采用的是同步写入的方式,考虑是否能够添加缓存以及批处理的形式,来进行CPU的削峰操作。

具体操作实施

去掉非核心日志之后的压力测试数据如下所示

stress设置cpu使用率 cpu使用率调整_sed_02

从数据的结果来看,性能提升了一倍多,已经可以满足目前的业务需求,以上的各点还在持续的优化跟进中。

总结

线上遇到瓶颈问题,首先以满足业务需求为根本出发点,第一时间解决业务线的需求。瓶颈问题主要是如何定位瓶颈在哪里,以及如何找到具体的瓶颈点,一旦找到了之后,就可以根据具体的情况处理相应的瓶颈点。