zoukankan      html  css  js  c++  java
  • rocketmq 主机负载异常飙高问题的解决

    最近在部署rocketmq到物理机时, 发现并解决了一个主机Load异常飙高的问题, 觉得有必要记录一下。
    我们采用了rocketmq(https://github.com/alibaba/RocketMQ)的3.4.6版本,并自己实现了一套dashboard(包括topic管理,producer管理 ,consumer管理,broker管理,消息跟踪等) 。
     
    部署架构如下:
    机器 角色 备注  
    ip1 nameserver + broker master 落盘方式:ASYNC_FLUSH  
    ip2 nameserver + broker slave 同步方式:异步复制  
    ip3 nameserver + benchmark app +tomcat    
     
     
    三台机器都是物理机 ,机器配置如下:
    操作系统 centos 6.5
    linux 版本 2.6.32-431.el6.x86_64
    cpu 40 核Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz
    内存 64g
    磁盘 SCSI 3.2T
    文件系统 ext4
    io调度算法 cfq(默认的完全公平算法, 并未采用官方文档提到的deadline) .
    jdk 1.7.0_71
     
    启动rocketmq以后,我们创建了一个测试topic-a, 设置了5个队列(都具有读写权限), 然后启动了5个Producer和5个consumer进行疲劳测试(投递采用的是队列轮询投递, 消费采用的是集群消费方式),启动压力测试,在第二天的早上发现dashboard里面已经查看不到master的机器了 ,这时候试图通过openssh 远程登录也登录不了, 但是22端口仍然可以telnet ,也可以ping通 ,找到运维同事,重新启动机器。
    启动后,查看rocketmq本身的gc日志 ,也只看到一些新生代的垃圾回收信息:
    2016-08-17T16:02:10.349+0800: 13552.986: [GC2016-08-17T16:02:10.349+0800: 13552.987: [ParNew: 3404557K->47713K(3774912K), 0.2118670 secs] 5311800K->1954963K(7969216K), 0.2121470 secs] [Times: user=2.78 sys=0.00, real=0.21 secs]
    2016-08-17T16:02:20.945+0800: 13563.583: [GC2016-08-17T16:02:20.945+0800: 13563.583: [ParNew: 3403233K->49831K(3774912K), 0.2107390 secs] 5310483K->1957085K(7969216K), 0.2110130 secs] [Times: user=2.74 sys=0.00, real=0.21 secs]
    2016-08-17T16:02:31.497+0800: 13574.135: [GC2016-08-17T16:02:31.497+0800: 13574.135: [ParNew: 3405351K->48942K(3774912K), 0.2087310 secs] 5312605K->1956200K(7969216K), 0.2090060 secs] [Times: user=2.71 sys=0.00, real=0.21 secs]
     
    这时候查看/var/log/messages, 看linux 系统日志 ,抓到了如下日志:
    Aug 15 20:10:11**** kernel: INFO: task java:97019 blocked for more than 120 seconds.
    Aug 15 20:10:11**** kernel: Tainted: G --------------- H 2.6.32-431.el6.x86_64 #1
    Aug 15 20:10:11**** kernel: "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
    Aug 15 20:10:11**** kernel: java D 0000000000000000 0 97019 96892 0x00000080
    Aug 15 20:10:11**** kernel: ffff880eb8677cf0 0000000000000082 ffffffff810afa0a ffff880f199bf538
    Aug 15 20:10:11**** kernel: 00000000000014bc 00000000199bf500 0000000000000000 ffffffffb8677ca8
    Aug 15 20:10:11**** kernel: ffff880f198785f8 ffff880eb8677fd8 000000000000fbc8 ffff880f198785f8
    Aug 15 20:10:11**** kernel: Call Trace:
    Aug 15 20:10:11**** kernel: [<ffffffff810afa0a>] ? futex_wait+0x21a/0x380
    Aug 15 20:10:11**** kernel: [<ffffffff81529f85>] rwsem_down_failed_common+0x95/0x1d0
    Aug 15 20:10:11**** kernel: [<ffffffff8152a116>] rwsem_down_read_failed+0x26/0x30
    Aug 15 20:10:11**** kernel: [<ffffffff8128e854>] call_rwsem_down_read_failed+0x14/0x30
    Aug 15 20:10:11**** kernel: [<ffffffff81529614>] ? down_read+0x24/0x30
    Aug 15 20:10:11**** kernel: [<ffffffff8104a92e>] __do_page_fault+0x18e/0x480
    Aug 15 20:10:11**** kernel: [<ffffffff8152d45e>] do_page_fault+0x3e/0xa0
    Aug 15 20:10:11**** kernel: [<ffffffff8152a815>] page_fault+0x25/0x30
    Aug 15 20:10:11**** kernel: INFO: task java:97020 blocked for more than 120 seconds.
    Aug 15 19:04:56 **** sssd: Killing service [hx], not responding to pings!
     
     
    发现在晚上的八点多, 有java进程被阻塞了120秒 ,后面紧接着的红色信息大概意思是在开始杀服务,系统对ping操作都已经不响应了 ,询问运维,在这个时间段,也没有对问题主机做任何操作;
    没有头绪的情况下,暂时只好重新再做一次benchmark了,发现问题依旧!!rocketmq master又登录不上,直观的感觉就是down掉了,但是端口却能telnet 通,只好再次让运维同事重启机器,登录到master, 通过sar命令,得到了如下信息:
     
    cpu负载(sar -q 得到)
     
    02:17:42 AM   runq-sz  plist-sz   ldavg-1   ldavg-5  ldavg-15
    03:15:22 AM         5      1120     14.43     17.30     14.87
     
    cpu利用率:(sar -p 得到)
    02:17:42 AM     CPU     %user     %nice   %system   %iowait    %steal     %idle
    03:15:16 AM     all      0.25      0.00     23.40      0.04      0.00     76.30
    03:15:22 AM     all      0.02      0.00     13.83      0.04      0.00     86.11
     
     
     
     
    内存:(sar -r 得到)
     
    02:17:42 AM kbmemfree kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit
    03:15:16 AM    593108  65262556     99.10    197600  56394056  20486868     27.59
    03:15:22 AM    592224  65263440     99.10    197648  56394296  20485052     27.59
     
     
     
    磁盘:(sar -d得到)
    02:17:42 AM       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
    03:15:16 AM    dev8-0      0.37      0.45      4.77     14.24      0.00      1.06      0.71      0.03
    03:15:16 AM  dev253-0      0.47      0.42      3.65      8.60      0.00      1.16      0.46      0.02
    03:15:16 AM  dev253-1      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
    03:15:16 AM  dev253-2      0.14      0.03      1.11      8.00      0.00      0.42      0.31      0.00
    03:15:22 AM    dev8-0     11.95     71.38    207.41     23.32      0.03      2.86      1.48      1.77
    03:15:22 AM  dev253-0     29.29     71.38    207.41      9.52      0.04      1.37      0.60      1.77
    03:15:22 AM  dev253-1      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
    03:15:22 AM  dev253-2      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
    04:01:19 AM    dev8-0      0.59      3.01      5.61     14.49      0.00      0.85      0.70      0.04
    04:01:19 AM  dev253-0      0.31      2.98      2.05     16.06      0.00      1.77      0.98      0.03
    04:01:19 AM  dev253-1      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
     
    发现cpu的负载非常高,而且20%多的cpu都是在系统态,内存利用率已经到了99%以上, 磁盘几乎没有读写。
    是什么引起了cpu load这么高,并且花在了系统态上呢,直观感觉就是内存已经很吃紧了.
     
    结合rocketmq的commitlog ,consumequeue ,Index file 都采用了mmap做内存映射来操作文件,吃内存的地方就会是这三个地方,但是索引文件大概500M能索引2000万的消息, 而consumequeue的 一个item是20个字节(8字节commitlog offset+4字节 size+8字节message tag hashcode), 也不会吃太大内存,吃内存的地方就会在commitlog 了。
    那疑问在于, 为什么吃内存会把系统的负载搞到这么高呢?我们做了一个mmap的测试程序:
     
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.ByteBuffer;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    import java.util.concurrent.atomic.AtomicInteger;
     
    /**
    * * * Created by lk on 2016/8/16.
    * * */
    public class MapfileTest {
     
    private static final AtomicInteger wrotePostion = new AtomicInteger(0);
     
    public static void main(String []args) {
    File file = new File(args[0]);
    int filesize = Integer.MAX_VALUE;
    FileChannel fileChannel = null;
    MappedByteBuffer mappedByteBuffer = null;
    try {
    fileChannel = new RandomAccessFile(file,"rw").getChannel();
    mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, filesize);
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024 * 2);
    for(int i = 0; i < 1000; ++i){
    appendMessage(byteBuffer.array(),filesize,mappedByteBuffer);
    try {
    Thread.currentThread().sleep(50);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
     
    System.in.read();
     
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }finally{
    if(fileChannel != null){
    try {
    fileChannel.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
     
     
    }
     
    }
     
    private static boolean appendMessage(final byte[] data,long fileSize,MappedByteBuffer mappedByteBuffer) {
    int currentPos = wrotePostion.get();
     
    if ((currentPos + data.length) <= fileSize) {
    ByteBuffer byteBuffer = mappedByteBuffer.slice();
    byteBuffer.position(currentPos);
    byteBuffer.put(data);
     
    wrotePostion.addAndGet(data.length);
    return true;
    }
     
    return false;
     
    }
     
     
    }
     
    上面这个程序是一次创建一个大小为2g的内存映射文件,我们在一个只有5g free内存的虚拟机上 ,逐个启动多个进程:
    /opt/jdk/jdk1.7.0_71/bin/java MapfileTest ./testfile1
    /opt/jdk/jdk1.7.0_71/bin/java MapfileTest ./testfile2
    /opt/jdk/jdk1.7.0_71/bin/java MapfileTest ./testfile3
    /opt/jdk/jdk1.7.0_71/bin/java MapfileTest ./testfile4
     
    再通过sudo cat /proc/pid/status 查看进程状态中的VmRSS 指标(实际占用内存) , 得到一个结论,java内存映射文件只要内存够用,就尽量多消耗, 内存不够用的情况下,后起的进程会从先启的进程中抢占内存; 从这里看到,Java内存映射机制本身是没有什么问题的。
     
    再结合前面的问题分析猜想, 虽然Java内存映射没问题 ,但是不是rocketmq的内存吃的太多 ,导致操作系统本身的内存不够用,而直接造成os crash呢?
     
    我们试着调整内核参数:
    #允许超量使用内存
    echo 'vm.overcommit_memory=1' >> /etc/sysctl.conf
    #给系统预留的内存,Kb, 保留16G
    echo 'vm.min_free_kbytes=16777216' >> /etc/sysctl.conf
    #释放pagecache;
    echo 'vm.drop_caches=1' >> /etc/sysctl.conf
    #节点内存不够,从其他节点获取内存
    echo 'vm.zone_reclaim_mode=0' >> /etc/sysctl.conf
    #限制进程的虚拟内存区域的数量
    echo 'vm.max_map_count=655360' >> /etc/sysctl.conf
    #最大限度使用内存,然后才是swap分区。
    echo 'vm.swappiness=0' >> /etc/sysctl.conf
     
     
    调整好内核参数以后,我们让内核参数实时生效
    sudo sysctl -p
     
    然后重启rocketmq ,继续进行benchmark,通过sar -r 3 命令,每3秒钟查看一次内存利用率,发现,
    Linux 2.6.32-431.el6.x86_64 (s10-2-26-7.hx) 08/18/2016 _x86_64_ (40 CPU)
    04:00:01 AM 23522004 42333660 64.28 151040 32570228 20337848 27.39
    04:10:01 AM 22760760 43094904 65.44 153568 33319244 20337664 27.39
    04:20:01 AM 22044920 43810744 66.53 154260 34011192 20338040 27.39
    04:30:01 AM 21252440 44603224 67.73 154660 34765872 20337548 27.39
    04:40:01 AM 24537392 41318272 62.74 153252 31503576 20338100 27.39
     
    ....
    07:30:01 AM 21264948 44590716 67.71 156280 34532204 20337676 27.39
    07:40:01 AM 24547740 41307924 62.72 156920 31266096 20337720 27.39
    07:50:01 AM 23789356 42066308 63.88 157592 32016024 20337628 27.39
    08:00:01 AM 23872952 41982712 63.75 156716 31929608 20338072 27.39
    08:10:01 AM 23000656 42855008 65.07 157412 32785184 20338604 27.39
     
    内存利用率每次达到接近68%的时候, 会立即下降到 62% ,
    结合自己在前面设置的参数:
    echo 'vm.min_free_kbytes=16777216' >> /etc/sysctl.conf
    来分析,物理机器的内存是64g, 保留16g 给系统,
    1-16/64= 75%
    即从理论上看,内存利用率到75%的时候,就会禁止应用继续申请内存,而实际是到68%左右 ,会启动一次内存回收的动作。
     
    调整完内核参数以后,继续压测一天, rocketmq运行平稳,没有出现之前的os crash的问题。
     
     
    基本可以确认是之前rocketmq在做commitlog文件的内存映射操作时 ,因为持续的压测,导致commitlog的内存映射操作把内存吃满了,导致系统内存不足而造成。
     
  • 相关阅读:
    BaseDao
    url中文参数解决方案
    Ajax实现步骤和原理
    在服务器端使用文件时的路径解决方案
    用户验证登录拦截器
    jenkins环境搭建
    gitlab环境搭建
    nexus3.X环境搭建
    base64文件大小计算
    JVM远程调试功能
  • 原文地址:https://www.cnblogs.com/dongqingswt/p/5782884.html
Copyright © 2011-2022 走看看