producer的复用
一开始研究ActiveMQ的资料时,官网只说了connection和session最好复用,在我认识里,producer和consumer是由session创建出来的,session复用没问题,producer应该资源占用没那么大吧?
所以在一开始对项目组暴露的封装好的api接口中,client每发送一条消息,都会自动创建一个producer,随着发送消息的越来越多,在mq控制台上看到的生产者数量就越来越多,mq内存占用越来越大,从而导致mq的宕机。
最后的解决方法是在程序里复用producer,由于client需要持续往三个队列发送消息,因此使用了hashmap来存放建好的producer,map里键是目的地,值则是MessageProducer对象。
每次发送消息时,需要检查是否producer可用,主要是检查会话是否正常。
failover机制的问题
在项目上线后,需求方要求Agent可以在故障时自动切换到另一个MQ集群以实现容错,且在原集群正常后可以自动切换回来。
这个需求直觉上就是ActiveMQ的failover机制,只需要在建立连接时把randomize设置为false,然后加个priorityBackup=true,failover中配置上两个MQ集群的F5就OK,实际上我们也是这么做的。
上线后才发现了一个问题,只要有一点网络抖动,或者因为inactivityMonitor导致的连接断开,那么Agent就会自动连接到另一个集群,而后再尝试连接回来。也就是说,不会尝试多次第一个地址,而是直接尝试第二个可用的地址,就算将配置写成
ip1,ip1,ip2
的形式也不行。因为源码中在识别uri添加uri到重连列表时有一个去重的机制,在FailoverTransport中:
public void add(boolean rebalance, URI u[]) {
boolean newURI = false;
for (URI uri : u) {
if (!contains(uri)) {
uris.add(uri);
newURI = true;
}
}
if (newURI) {
reconnect(rebalance);
}
}
由于我们使用的是MQ上自带的系统消息来判断Agent是否在线,这样就会出现两个集群的ActiveMQ都会上送一条agent连接到该集群的提示,如果离线消息处理不及时,上层就会出现一个agent连接两个mq的情况。
实际解决方法还没有想到,目前想到的就是先把InactivityMonitor给关了,再看看效果吧
Channel was inactive for too long
这个问题出现在使用了上述的failover机制后,由于InactivityMonitor机制,一般情况下如果没有数据包,那么MQ和Agent会互相发送keepAlive Info数据包,是不会出现问题的。但是在MQ负载较高的情况下,会出现无法发送KeepAlive Info的问题(推测),因此InactivityMonitor就会断开连接,而后自动重连到另一个集群再连接回来。
在这种情况下,考虑两种方案
1.彻底关闭该功能
2.延长检测时间,比如10分钟内没数据交互才关闭。
目前暂定方案2具体测试情况待上线后检验
无限重连导致连接暴增
在测试环境上发现一个场景,agent不停发起对mq的重连,但是连接未被关闭,一段时间后,火墙就被这些连接占满了。
现在初步判断原因是mq被连接撑爆,导致mq不停关闭连接,agent又不停尝试重连,具体需要再进行测试。
这个问题的解决方法有两种
1.增加mq集群数量,限制每台mq的连接数
2.放弃failover机制,而是改用自己写的逻辑来进行重连。
具体解决方法待确定,后续更新
KahaDB无法及时清理db-*.log
问题原因已经找到,是ActiveMQ的一个bug:
ActiveMQ使用KahaDB进行消息的持久化存储,在KahaDB的db-.log文件中,存放的是消息和消息被消费者接收的ack数据(messages and acknowledgement),只有一个db-.log文件中所有的消息都被消费(都存在ack)后,这个db-*.log才会被清理。
那么现在存在一种情况,如果MQ上堆积了一条消息,许久没被消费。那么这条消息所在的db-1.log就不会被清理,也就是说db-1.log中所有其他已经被消费的消息也都不会被清理。如果db-2.log,db-3.log中存在db-1.log中的已经被消费的消息的ack数据,这个ack数据也是不能被清理的。原因是如果清理了ack数据,那么在MQ宕机重启后,db-1.log中的被消费的消息又会被置为未消费。因此就会产生一个连锁反应,只要有一条消息卡在MQ上,就可能后续的数据文件均无法被删除。当然连锁反应也有可能在某个环节被打破,比如某个文件刚好包含了自己所有消息的ack和上一个文件的消息的ack数据,但是如果一直连锁下去,文件系统的确是会被撑爆。
在测试环境已重现这个问题
这个bug在ActiveMQ-5.14.0版本中被解决,解决方法是把第一个数据文件中消息的ack给迁移到某个数据文件中。我也在测试环境搭了5.14.1版本的ActiveMQ进行了测试。
可惜我们用的版本是ActiveMQ-5.11.1……………………
解决方法是:
1、尽量减少MQ上的消息堆积的情况,比如设置消息的超时时间,定时清理等。如果堆积了要及时处理,不能让MQ上长期堆积相同的消息。
2、减小db-*.log的文件大小,理论上可以使脏数据的影响范围降低
3、设置KahaDB多实例,让队列之间的数据文件互不干扰。
设置方法
亲测,在分别对两个队列设置KahaDB多实例后,生产者生产持久化消息的TPS提升了
4、升级MQ
MQ服务器上出现大量CLOSE_WAIT连接
CLOSE_WAIT表示一个连接被被动关闭了,服务器没有检测到,因此程序忽略了继续进行连接的关闭。最麻烦的是CLOSE_WAIT的连接会占用amq的总连接数,因此如果要清理,只能重启mq来解决。
有一篇文章对TIME_WAIT和CLOSE_WAIT解释得很详细。
http://www.cnblogs.com/sunxucool/p/3449068.html
出现这个问题后,可能直接导致activemq报出timer already cancelled,连接超限,无法建立线程OOM等报错。(推测)
该问题在JIRA上已经被关闭,见AMQ-5543和AMQ-5251。
经过一段时间的研究,发现是我的代码触发了MQ 的一个bug代码中建立了过多的JMX连接,导致服务器无法建立新的线程,从而导致连接无法被正常处理,而后连接被客户端关闭,就出现了close_wait。
这个bug连接的是InactivityMonitor中建立ReadChecker和WriteCheck的bug,出现问题时会导致MQ上报出大量的Timer Already Cancelled报错,因为无法建立新的checker线程了,但是代码中仍然进行了checker的Timer类的配置,所以抛出了timer already cancelled的异常。在AMQ 5.13及以后的版本已修复该问题。