调试笔记 — Redis 消息队列发布信息被消费者重复订阅多次牵扯到的 Tomcat 配置问题 [#00001]

最近在项目中发现了一个奇葩的 BUG ,当用户调用后台时,后台向消息队列中发布一条消息,这条消息会被监听器(消费者)监听到,有趣的事情就在这里,此时由于只发送了一条消息,照理说监听器应该只会触发一次,但是却总是订阅2次(有的客户服务器启动甚至会初始化好几次,不知具体原因),然后就不会再订阅了,当时向消息队列发布信息我是使用的 RedisTemplate 里面的 convertAndSend(channel,message) 方法(看了一遍发现并没有看出什么代码上的问题),于是我去看了一下源码并走了一下断点,发现程序在发布消息的时候确确实实只发布了一次,但是监听器却订阅了2次。

当时为了测试,特意写了一个跟下面所有代码片段差不多的内容

消息发布者

// Controller 的某个方法内容 用于向 "channel" 发布信息
// 此处使用的 RedisTemplate 对象
// 消息发布者
@RequestMapping("publishMsg")
public void publishMsg(){
    // 向 "channel" 发布一个信息 "这是我发送的一个消息"
    redisTemplate.convertAndSend("channel","这是我发送的一个消息");
}

消息订阅者 ( xxxConsumer )

// xxxListener 类,主要用于订阅向 "channel" 发布的信息
// 消息订阅者
public void handleMessage(Message message,byte[] pattern){
    // 输出 channel
    System.out.print("channel : " + stringRedisSerializer.deserialize(message.getChannel()));
    // 输出发布的信息
    System.out.print("message : " + stringRedisSerializer.deserialize(message.getBody()));
}

spring.xml Redis 的队列大概配置


<bean id="stringRedisSerializer" 
      class="org.springframework.data.redis.serializer.StringRedisSerializer">bean>

<bean id="xxxConsumer" class="com.xxx.xxx.xxxConsumer">bean>
<redis:listener-container>
    <redis:listener ref="xxxConsumer" serializer="stringRedisSerializer" 
                    method="handleMessage" topic="channel" />
redis:listener-container>

此时我联想到以下几点可能会引起的问题。
1、代码逻辑或者配置文件内容写的还是有问题,需要再 review 一遍 ?
2、spring-data-redis 版本问题导致的重复订阅多次 ?
3、redis 版本过于古老 ?
4、程序从前几个月的某次更新之后就出现了扫描了两次项目的问题,会不会跟这个有关系 ?

经过排查与尝试之后,最终问题定位在了第 4 点上,随后我便想到每次初始化资源的时候队列都会起一个线程(xxxConsumer)来监听(订阅) 某个 channel 发布的信息,如果程序连续初始化了两次项目,会不会有两个消费者线程,同时运作并监听一个 channel 发布的信息呢 ?
于是我尝试了一下,在程序第一次初始化成功之后立马在我的 redis 客户端中手动向 “channel” 发布了一个信息,这时候发现该信息的订阅者数量是 1,然后我定位到了问题所在,随后我突然想到了很久之前为了只用 ip 地址加端口访问到项目,特意在 server.xml 中的 Host 元素里多加了一个 Context 标签,用于忽略项目名访问项目,之后我在为了能去掉项目名的同时,也不被实例化两次的想法下,进行了一些修改,下面放出代码
修改前

<Host appBase="wtpwebapps" autoDeploy="true" name="localhost" unpackWARs="true">
    <Context debug="0" docBase="" path="ProjectName" reloadable="false">Context>
    <Context debug="0" docBase="ProjectName" path="/ProjectName" reloadable="false" source="..." >Context>
    
Host>

修改后
注意:如果是在 IDE 中启动 tomcat 可能路径会有变化,甚至还会造成项目启动失败的问题,报错内容主要就是路径对应的项目找不到,改成正确的路径就好了,server.xml docBase 路径配置示例 : ../wtpwebapps/ProjectName

<Host appBase="" autoDeploy="true" name="localhost" unpackWARs="true">
    <Context debug="0" docBase="webapps/ProjectName" path="/" reloadable="false" source="..." >Context>
    
Host>

经过这次事件之后也算是有了一点小收获(调的时候真的是一头雾水……)
了解了一点 Tomcat 加载的机制,也巩固了一遍 redis 队列配置方法,而且不知道算不算是找到了一个比较靠谱一点的去掉项目名的配置方案 ╰(°▽°)╯
[手动骄傲]

PS:这是我第一次写调试笔记,主要是为了记录工作中遇到的一些尴尬事儿(居然花了我几个小时的时间,才调完),防止下次重蹈覆辙 XD,可能比较啰嗦QAQ,还望谅解

你可能感兴趣的:(2017,年调试笔记,Redis-MQ)