项目场景:

某模块A需要从kafka读取某个实时高流量的数据流消息,并将其写入磁盘文件中。

问题描述:

监控系统持续向负责人手机和邮箱发送短信,提示该consumer group:topic的延迟lag过高!

通过监控系统web可以看到,kafka对应ConsumerGroup:topic的消息延迟Lag非常高,仅少部分分区的Lag处于正常水平,而大部分分区的Lag却不断堆积,甚至很多分区的Lag值达到了数十亿。

原因分析:

1、确定问题机器:

  1、根据kafka部分partition消息堆积问题解决记录01的经验,马上获取到当前正常运行的consumer线程,并与partition一一对应:

  当前topic的partition数为75,而consumer线程数部署了72个。但是大量partition的延迟仍在走高。

  根据一一对应情况可以根据partition延迟情况对应到问题机器。由于大部分partition延迟均较高,需要查看多台机器。

2、确认问题产生因素:

  需要查看多台服务器的:

  • consumer进程部署情况及consumer线程分布情况
  • consumer线程运行情况
  • 服务器资源利用情况
  1、consumer进程部署情况及consumer线程分布情况:

  与之前排查的问题模块类似,该模块consumer进程同样均分配了8个consumer线程。但是consumer进程的分布不太合理:

  比如06、07机器均部署了两个consumer进程,查看partition延迟情况可以发现,这共四个consumer负责的32个partition基本都是高延迟情况,只有零星几个partition还保持正常。

  而且每个consumer进程都部署8个线程也不太合理,并不是每个服务器的资源都可以承受这样的分配。而有的服务器资源又有富余。

  应该根据服务器资源合理进行consumer进程的部署以及每个进程的consumer线程的分配。

  2、consumer线程运行情况:

  通过jstack命令生成多个consumer进程的dump文件。

jstack -l [pid]  > [dump文件]  //生成dump文件

  查看dump情况可以发现,没有死锁问题产生;对比前后dump文件可以发现,大部分consumer处于waiting on condition状态,没有进行消费,一定时间内很少有能够进行消费的。

  根据watch命令重点持续监控几个consumer线程,发现同样如此,大部分consumer是持续阻塞,很少会恢复到Runnable状态。

watch -n 1 -d "jstack [pid] | grep -A [n] [consumer线程名] " 
//每隔1秒执行一次后面的命令,持续进行监控。
//grep参数 -A [n] 表示显示显示查找到的行及其后n行。这样可以同步看到consumer的调用栈,查看其具体阻塞在哪一步操作。

  其实根据第一步的部署情况就猜测到可能是服务器资源不足导致。现在根据这些consumer线程阻塞状态可以进一步确认,阻塞的consumer线程大多在等待服务器资源中。

  具体原因还需要进一步查看服务器资源情况。

  3、服务器资源利用情况:

  1、根据top命令整体查看服务器各资源利用情况

top

  2、根据iostat命令查看磁盘读写情况

iostat -dxk 1 10	//查看磁盘IO情况

  3、查看网络IO情况:通过以下命令对比查看总体网络IO情况:

watch -n 1 -d "ifconfig [网卡] | grep bytes" //(备注:网卡可以通过ifconfig命令直接查看)

  通过查看各个服务器的系统负载、cpu、磁盘IO、网络IO等情况,对比partition延迟情况,可以确定延迟问题是服务器资源利用不合理导致:

  • 部分consumer因竞争网络带宽而阻塞
  • 部分consumer因竞争CPU资源而阻塞
  • 部分consumer因磁盘IO达到瓶颈而阻塞

解决方案:

  1、调整consumer的部署,针对服务器资源情况,引入该项目集群其他服务器机器,对各个consumer进程分配的consumer线程数进行相应调整。

   2、同时需要注意不同consumer线程数分配情况下,JVM堆内存各年龄代的内存分配合理性。此处由于新生代单位时间产生量较大(代码方面的问题 , 对每一条消息都要创建新对象去处理),而且朝生夕逝,因此设置内存重心偏向新生代的Eden区。

   3、合理控制consumer总共线程数,尽量保证consumer消费分区数一致。否则不同消费分区数可能产生不同问题。此处控制消费线程数为38个。对应单个consumer消费两个partition。

后续:

   1、同样对该模块进行JVM简单调优。调优过程类似记录一次JVM简单调优01
   2、调整后延迟缓慢消费至正常水平。后续将数据量调整至正常水平后,发现单个consumer消费两个partition消费能力不够。再次调整,将consumer添加至75个,保证消费侧消费能力足够。

思考:

   1、consumer不是单纯的越多越好,相反如果不控制好服务器资源的供应,consumer越多反而可能导致资源竞争而阻塞。说到底,我们还是需要对服务器资源进行合理规划,充分利用。
   2、代码方面我们也要有资源把控的意识。比如该模块,在大数据量的情况,对每一条消息都新建处理对象去处理,显然会极大耗费内存资源,如果数据流过大,磁盘IO再出现瓶颈导致大量处理对象不能被GC回收,很可能会导致OOM。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐