解决ActiveMQ的“Invalid broker URI”异常的历程

简介:

000

最近碰到一个问题,把解决的过程记录下来。

故障原因

同事的应用上线,Tomcat无法正常启动。抛出这样的异常:

org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:|PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'brokerURL' threw exception; nested exception is java.lang.IllegalArgumentException: Invalid broker URI: nio://10.0.0.0:91616 
        at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:121)
        at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:75)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1504)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1216)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)

据同事说,在线下环境,可以正常运行,在线上环境就出错了,非常的诡异。

程序有一个env.properties的配置文件:

broker.producer  = failover://(nio://10.0.0.3:91615?connectionTimeout=3000,nio://10.0.0.4:91615?connectionTimeout=3000)

线上和线下加载的是不同的env.properties。

在Spring xml配置文件里有这样的配置:

	<!-- ActiveMQ 连接工厂 -->
	<amq:connectionFactory id="jmsConnectionFactory" brokerURL="${broker.producer}" />

Spring在运行时,会自动替换${}表达式里的值。

首先检查网络的连通性:

在线上机器上,执行

telnet 10.0.0.3 91616

可以正常连接,再到ActiveMQ的Web Console上查看Connection,发现的确没有这个IP的连接。

检查配置是否正常:

线上的配置的brokerURI是这个:

failover://(nio://10.0.0.3:91616?connectionTimeout=3000,nio://10.0.0.4:91616?connectionTimeout=3000)

直接先改为最简单的,在vim下yy复制了一行,删除多余的,剩下:

nio://10.0.0.3:91616

发现还是报异常。

利用ssh做端口转发,本地测试

在本地跑了个简单程序,用XShell的端口转发,把本地请求转发到线上机器上,发现可以正常发送消息。于是让同事回去检查代码里的其它问题了。

把测试程序放到线上机器运行

但是同事没有找到错误,于是把刚才的简单的程序打成一个fat jar包,放到线上机器上去跑,发现可以正常发送ActiveMQ消息。

程序打包用的是maven的one jar 插件,参考:

http://www.mkyong.com/maven/maven-create-a-fat-jar-file-one-jar-example/

把测试程序集成到同事的代码里,运行

把测试程序放到同事的War包的代码里,放到线上机器,发现可以正常发送消息。

但是同事配置的ActiveMQ还是不能发送消息,还是报“Invalid broker URI”异常。

增加变量,反复测试,对比配置

没办法了,把同事的环境变量${broker.producer},设置到测试代码里,发现测试代码抛异常了。

于是确认是${broker.producer} 这个变量有问题。

但是env.properties文件里的配置看起来是对的。于是怀疑是配置文件格式有问题。

备份旧文件,建了个新配置文件,配置上

broker.producer =nio://10.0.0.3:91615

发现,居然正常了。

于是对比两个配置文件,发现旧的配置文件上,最后多了一个空格。。就是91615后面多了一个空格。

蛋疼无比,ActiveMQ居然不能识别处理配置值后面多出来的一个空格。而且Spring抛出来的异常里也没有这个信息。

搜索关键字"Invalid broker URI",查看ActiveMQ代码,找到原始异常

开始调试时,找不到原始的异常信息在哪里,Spring的函数调用层次太多了。于是采用代码搜索。

在 https://searchcode.com 搜索"Invalid broker URI",终于找到原始的异常信息是下面的代码抛出来的:

URI org.apache.activemq.ActiveMQConnectionFactory.createURI(String brokerURL)
    private static URI createURI(String brokerURL) {
        try {
            return new URI(brokerURL);
        } catch (URISyntaxException e) {
            throw (IllegalArgumentException)new IllegalArgumentException("Invalid broker URI: " + brokerURL).initCause(e);
        }
    }
很奇怪的是,IllegalArgumentException异常里是正确地把URISyntaxException设置到cause里了,后面的Spring却没有把这个信息给打印出来。。

Spring打印异常的工作原理

为什么Spring能把前面的异常信息都打印出来,而原始的异常信息却不能打印出来?比如某个Spring异常信息是这样的:

Caused by: org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:

Srping会用Caused by,nested exception is,这样的字符把所有的异常都串起来。到底这里面是怎么工作的?

再次搜索"nested exception is"

查找到Spring相关的代码。

原来所有的Spring异常类都继承自NestedRuntimeException,而NestedRuntimeException重写了getMessage()函数,在getMessage()函数里,会把异常的信息全都串起来。

而IllegalArgumentException继承自Throwable类,Throwable类的getMessage()函数只是简单打印了message,并没有把cause也输出。

org.springframework.core.NestedRuntimeException

	/**
	 * Return the detail message, including the message from the nested exception
	 * if there is one.
	 */
	@Override
	public String getMessage() {
		return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
	}


org.springframework.core.NestedExceptionUtils
	/**
	 * Build a message for the given base message and root cause.
	 * @param message the base message
	 * @param cause the root cause
	 * @return the full exception message
	 */
	public static String buildMessage(String message, Throwable cause) {
		if (cause != null) {
			StringBuilder sb = new StringBuilder();
			if (message != null) {
				sb.append(message).append("; ");
			}
			sb.append("nested exception is ").append(cause);
			return sb.toString();
		}
		else {
			return message;
		}
	}

其它的一些东东:

NestedExceptionUtils这个类是abstract,这样可以防止使用者得到实例,这样使用者不能用错。这个也是一个常见的util类的技巧了。

public abstract class NestedExceptionUtils {

总结:

这个代码搜索网站比较好用:https://searchcode.com

很多流行网站的数据都有,比github上要全。


后来在网上搜索了下“Invalid broker URI”,有10万多条结果。。估计有不少就是因为一些空格而造成的。。

ActiveMQ的开发者只需要加上一点点的trim()的判断处理代码,就可以减少很多人的痛苦了。

所以防御性编程还是有必要的,有的时候并不真的是使用者不会用,而是错误来自想像不到的地方。

URI org.apache.activemq.ActiveMQConnectionFactory.createURI(String brokerURL)
    private static URI createURI(String brokerURL) {
        try {
            if(brokerURL != null)
                brokerURL = brokerURL.trim();
            return new URI(brokerURL);
        } catch (URISyntaxException e) {
            throw (IllegalArgumentException)new IllegalArgumentException("Invalid broker URI: " + brokerURL).initCause(e);
        }
    }



目录
相关文章
|
消息中间件 存储 监控
ActiveMQ系列: ActiveMQ 的死信队列与消费重试机制
maximumRedeliveryDelay:最大传送延迟,只在 useExponentialBackOff 为 true 时有效(V5.5),假设首次重连间隔为 10ms,倍数为 2,那么第二次重连时间间隔为 20ms,第三次重连时间间隔为 40ms,当重连时间间隔大的最大重连时间间隔时,以后每次重连时间间隔都为最大重连时间间隔。默认为 -1。
1732 0
ActiveMQ系列: ActiveMQ 的死信队列与消费重试机制
|
存储 Kubernetes 数据安全/隐私保护
k8s学习--Secret详细解释与应用
Secret 支持四种类型: - **Opaque Secrets**:存储任意类型机密数据,需自行加密。 - **Service Account Token Secrets**:自动管理 API 访问令牌。 - **Docker Registry Secrets**:存储 Docker 私有仓库认证信息。 - **TLS Secrets**:存储 TLS 证书和私钥,用于加密通信。
1343 0
|
移动开发 开发框架 JavaScript
Vue3 Vite electron 开发桌面程序
Vue3 Vite electron 开发桌面程序
888 0
|
开发者 图形学 C#
深度解密:Unity游戏开发中的动画艺术——Mecanim状态机如何让游戏角色栩栩如生:从基础设置到高级状态切换的全面指南,助你打造流畅自然的游戏动画体验
【8月更文挑战第31天】Unity动画系统是游戏开发的关键部分,尤其适用于复杂角色动画。本文通过具体案例讲解Mecanim动画状态机的使用方法及原理。我们创建一个游戏角色并设计行走、奔跑和攻击动画,详细介绍动画状态机设置及脚本控制。首先导入动画资源并添加Animator组件,然后创建Animator Controller并设置状态间的转换条件。通过编写C#脚本(如PlayerMovement)控制动画状态切换,实现基于玩家输入的动画过渡。此方法不仅适用于游戏角色,还可用于任何需动态动画响应的对象,增强游戏的真实感与互动性。
1111 0
|
数据采集 SQL 分布式计算
|
机器学习/深度学习 人工智能 自然语言处理
使用 Java 进行自然语言处理(NLP)
【4月更文挑战第19天】自然语言处理(NLP)让计算机理解人类语言,Java作为广泛应用的编程语言,在NLP中扮演重要角色。NLP应用于智能客服、机器翻译、情感分析和文本分类等领域。Java因丰富库支持、高性能和跨平台性成为NLP开发的理想选择。关键技術包括词法分析、句法分析、语义理解和文本向量化。Stanford NLP和OpenNLP是Java中的知名NLP库。开发流程涉及数据准备、选择库、特征工程、模型训练、评估和部署。随着NLP技术进步,Java在该领域将持续发挥影响力。
1650 2
|
JavaScript 定位技术 开发者
vue项目使用腾讯地图获取定位
vue项目使用腾讯地图获取定位
1300 0
|
NoSQL 安全 Redis
redis内存限制与淘汰策略
Redis内存管理包括限制和淘汰策略。`maxmemory`配置参数决定内存上限,无设置时64位系统默认不限制,可能导致系统资源耗尽,生产环境建议设定合理值。当内存满时,未设置淘汰策略会导致写入错误。Redis提供8种淘汰策略,如LRU(最近最少使用)和LFU(最不经常使用),以及随机或基于过期时间的删除。需根据数据重要性、访问频率和一致性选择合适策略。
1633 0