消费端实现了MessageListenerConcurrently监听接口,然后实现了consumeMessage这个方法。 此方法中,我开了线程池去执行消费消息的逻辑,但是走到一行打印日志的代码时候,突然不执行了。
然后就没了,也没有报任何异常,下面的其他逻辑也没有执行。我怀疑是线程挂了。
首先我排查下面的逻辑是否有问题, 发现没问题后,多打印了几个我觉得一定会打印的日志。结果发现,还是没有打印我觉得一定会打的日志。
其次,我开始追踪这个线程。
通过jps
快速找到pid,jstack -l >temp.txt
命令快速将堆栈信息导出来。
观察这个mq-incr-pool-4
线程在干嘛,是否存在等。
结果发现并没有这个mq-incr-pool-4
线程,说明这个线程挂了。
那为啥会挂呢?还没有任何报错日志。
我尝试换成了其他打印的日志。再次观察。发现可以打出来,就我那条打不出来。
继续查看堆栈,线程仍然存活,因为个数没有超过核心数,会阻塞等待队列中的任务。
那么打印的对象是我通过@autowired注解进来的一个变量,然后是注入进来的时候没注入成功? 按理说spring启动容器的时候如果依赖有问题,应用会直接起不来。
于是我尝试性的,将@autowire注入改为了 构造注入。重新启动任务,发现ok了!~ 能打印出来这个注入的变量了! 这我就开始猜测,是否之前这个变量有问题,或许报了null指针,但是没有报异常。于是,我又手工构造了其他异常,放在这个方法里
Object Id = 10034432; long a = (long) Id; System.out.println(a);
这个正常是会报cast exception的
但是,如果所料,在这个方法里面,并没有打印任何异常。然后查看堆栈,发现线程也会像之前一样消失。
那就说明了, 这个方法里面的所以异常,如果你不自己try catch的话,那么就不会报,也不会打印。看源码便知道,
consumeMessage 方法中所有的异常,都会被catch住,日志会打到mq中间件里面,所以我这里并没有。
正确的操作应该是业务自行catch,类似下面这样
@Slf4j @Component public class Consumer { /** * 消费者实体对象 */ private DefaultMQPushConsumer consumer; /** * 消费者组 */ public static final String CONSUMER_GROUP = "test_consumer"; /** * 通过构造函数 实例化对象 */ public Consumer() throws MQClientException { consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); consumer.setNamesrvAddr("47.99.203.55:9876;47.99.203.55:9877"); //订阅topic和 tags( * 代表所有标签)下信息 consumer.subscribe("topic_family", "*"); //注册消费的监听 并在此监听中消费信息,并返回消费的状态信息 consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { //1、获取消息 Message msg = msgs.get(0); try { //2、消费者获取消息 String body = new String(msg.getBody(), "utf-8"); //3、获取重试次数 int count = ((MessageExt) msg).getReconsumeTimes(); log.info("当前消费重试次数为 = {}", count); //4、这里设置重试大于3次 那么通过保存数据库 人工来兜底 if (count >= 2) { log.info("该消息已经重试3次,保存数据库。topic={},keys={},msg={}", msg.getTopic(), msg.getKeys(), body); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } //直接抛出异常 throw new Exception("=======这里出错了============"); //return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } catch (Exception e) { e.printStackTrace(); return ConsumeConcurrentlyStatus.RECONSUME_LATER; } }); //启动监听 consumer.start(); } }
但是,为什么之前注入的有问题呢?改成构造注入就可以了呢?感兴趣的可以点我看下。
而我依赖注入的实例中,在它的构造器里面有一个稍微耗时的逻辑。
public Client() { init(); }
因为Field 注入允许构建对象实例的时候依赖的示例对象为空,这就导致了空指针异常无法尽早的暴露出来。而构造器是强依赖注入,就解决了这个问题。
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句