使用阿里云RocketMq openservice ons的一次异常排查
日常开发中常常会运用到消息中间件对服务进行解耦或者消息的削峰,前段时间公司使用阿里云的openservice ons包进行消息队列的消费出现了一次异常问题。
先描述一下问题,消费者服务是一个Java进程,有一次服务升级逻辑代码线程数不够导致大量的消息阻塞,此时阿里云平台发出了消息消费报警的短消息,这时我开始看问题,并迅速定位修复bug,再次升级的过程中问题出现了,docker镜像在k8s中无法启动,容器报健康检查失败,查看代码发现程序却没有抛出异常,由于k8s集群网关的策略,35秒钟之内服务不能启动就会杀掉这个进程,导致现在服务频繁重启。
在查询是什么原因导致服务启动耗时这么久的过程中发现了一段代码
@PostConstruct
public void getPropertiesFactoryBean(){
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
Properties properties = new Properties();
// 您在MQ控制台创建的Producer ID
properties.put(PropertyKeyConst.ProducerId, aliyunProperties.getSuperctlPId());
// ConsumerId
properties.put(PropertyKeyConst.ConsumerId, aliyunProperties.getSuperctlCID());
// 鉴权用AccessKey,在阿里云服务器管理控制台创建
properties.put(PropertyKeyConst.AccessKey,aliyunProperties.getAccessKey());
// 鉴权用SecretKey,在阿里云服务器管理控制台创建
properties.put(PropertyKeyConst.SecretKey, aliyunProperties.getAccessSecret());
// 设置 TCP 接入域名(此处以公共云的公网接入为例)
properties.put(PropertyKeyConst.ONSAddr, aliyunProperties.getMqAddr());
// 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可
propertiesFactoryBean.setProperties(properties);
// 消费者消费启动
consumerBean.start();
}
而这个start()方法中的实现是这样子的
if (null == this.properties) {
throw new ONSClientException("properties not set");
} else if (null == this.subscriptionTable) {
throw new ONSClientException("subscriptionTable not set");
} else {
this.consumer = ONSFactory.createConsumer(this.properties);
Iterator it = this.subscriptionTable.entrySet().iterator();
while(true) {
//消息消费逻辑获取迭代器中的消息
}
}
}
这其中的Iterator it = this.subscriptionTable.entrySet().iterator();意味着,服务启动时需要接受所有的消息队列放入这个迭代器中,如果现在出现了消息堆积且堆积量比较大(服务中大约10万的堆积),那么spring容器启动到这里将会使得这里发生阻塞,服务会不断地去接收消息,这会导致服务启动时间变长,另一个是会导致内存泄漏
我们可以尝试这样去改造,让spring初始化完bean再去获取消息
public PropertiesFactoryBean getPropertiesFactoryBean(){
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
Properties properties = new Properties();
// 您在MQ控制台创建的Producer ID
properties.put(PropertyKeyConst.ProducerId, aliyunProperties.getPId());
// ConsumerId
properties.put(PropertyKeyConst.ConsumerId, aliyunProperties.getCID());
// 鉴权用AccessKey,在阿里云服务器管理控制台创建
properties.put(PropertyKeyConst.AccessKey,aliyunProperties.getAccessKey());
// 鉴权用SecretKey,在阿里云服务器管理控制台创建
properties.put(PropertyKeyConst.SecretKey, aliyunProperties.getAccessSecret());
// 设置 TCP 接入域名(此处以公共云的公网接入为例)
properties.put(PropertyKeyConst.ONSAddr, aliyunProperties.getMqAddr());
// 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可
propertiesFactoryBean.setProperties(properties);
return propertiesFactoryBean;
}
@Bean(initMethod = "start", destroyMethod = "shutdown")
public ConsumerBean mqStationConsumer(PropertiesFactoryBean propertiesFactoryBean) throws IOException {
ConsumerBean consumerBean = new ConsumerBean();
consumerBean.setProperties(propertiesFactoryBean.getObject());
Map<Subscription, MessageListener> subscriptionTable = new HashMap<>();
Subscription subscriptionMpStation = new Subscription();
subscriptionMpStation.setExpression("*");
subscriptionMpStation.setTopic(aliyunProperties.getTopic());
subscriptionTable.put(subscriptionMpStation, (final Message message, final ConsumeContext context) -> {
if (TAG_MP_DEVICESTATUS.equals(message.getTag())) {
return mpUpDownMsgListener.consume(message, context);
}
return mpStationMsgListener.consume(message, context);
});
consumerBean.setSubscriptionTable(subscriptionTable);
return consumerBean;
}
这样的话解决了这次服务无法重启的问题。