接上篇写的【kafka与swoole多进程消费】。
在不断的测试与修正过程中,发现了一些比较难搞的问题:
1. 一个topic配置了30个分区,开起来跑30个子进程,这样大概半小时多一点各个子进程就会相继而亡,接着死而复生。
2.子进程死后重生,扰乱了kafka消费组当中的消费者,导致一些消费者莫名消失,甚至只剩下1个消费者,一个消费者监听多个分区的话,其中只会有一个分区在真正消费。
3.偶尔出现SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; 死锁问题。
接下来讲讲我的解决方法。
第一个问题:
消费进程只能坚持半小时左右,这个问题各种研究各种折腾想了好几天也没想明白究竟是为啥,查了一些日志也没发现端倪,最终觉得每个子进程的占用内存好像飙升得特别快,怀疑是不是因为内存过载。上网查了一些资料,发现一个命令:
dmesg -T | grep -E -i -B100 'killed process'
果然是这个原因!
那么为什么这玩意内存只增不减?又各种折腾,怀疑是不是变量没有释放,垃圾没有回收,于是各种unset(),各种gc_collect_cycles()。然而作用几乎没有。php这种脚本形的语言,不能直接操作内存,都是通过php解释器里的垃圾回收机制来处理。
然后又发现即使只消费kafka里的消息不走任何业务逻辑内存仍然会升,这样就有了个研究方向了。
又上网查了很久很久,换了多种姿势搜索,终于在git上查到了类似的问题:https://github.com/arnaud-lb/php-rdkafka/issues/14 。在这里得知了一个参数queued.max.messages.kbytes。
大概就是消费的时候每个分区都会默认缓存1G的消息。
然后仔细看了看 $conf->dump(); 里面的所有参数,我选了三个参数出来将它们都设置了一个较小的值:
$conf->set('queued.max.messages.kbytes', 1000);
//大概是每次最多缓存多少条消息
$conf->set('queue.buffering.max.messages', 100);
//大概是最大缓存多少消息
$conf->set('queue.buffering.max.kbytes', 102400);
把消费服务里的while(true)也改成了用
swoole_timer_tick(1000,function(){});
再跑起来之后,从原先的半小时可以撑到一个半小时了!相当大的进步哈。
从运维小哥那里得知这台机只有4g内存..emm.....好的 给我加到了20g。这样下来开两个主进程,50个子进程跑了一天一夜还没开始死。但是又遇到一个新的问题,越到后面业务逻辑执行的特别慢,内存涨得是慢了但是仍然只增不减。怀疑是太多临时数据存在内存里了,赞是还没找到解决方法。
第二个问题:
因为发现kafka每次新增消费者或者减少消费者的时候rebanlance的时间稍微有点久,所以就简单粗暴的让子进程死后等5分钟再重启,即使组内消费者可能当下数量还是不对,但是等多一会就会都出来了。这个虽然没有经过论证,但是有效果,那就这样吧。
第三个问题:
死锁这个问题偶尔出现,频率不算高。
网上的说法有说是因为innodb的行锁不是只锁自己那一行,会锁一块小范围。
也有说innodb的行锁和解锁都是针对主键索引的,如果不是根据主键而去更新锁表的值,那么等待的解锁查询的进程就会报死锁。
解决这个问题的方法是,catch到死锁错误时,当前等待几秒钟,然后再执行一遍所需执行的操作就可以了。