Skywalking中RabbitMQ消费链路被隔断

官方提供的rabbitMQ探针链路的断开原因分析:

1.原生Consumer分析

下面是原生 apm-rabbitmq-5.x-plugin 中plugin定义拦截点代码可以看出加强 com.rabbitmq.client.Consumer类 handleDelivery方法。

public class RabbitMQConsumerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
    public static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.rabbitmq.RabbitMQConsumerInterceptor";
    public static final String ENHANCE_CLASS_PRODUCER = "com.rabbitmq.client.Consumer";
    public static final String ENHANCE_METHOD_DISPATCH = "handleDelivery";
    public static final String INTERCEPTOR_CONSTRUCTOR = "org.apache.skywalking.apm.plugin.rabbitmq.RabbitMQProducerAndConsumerConstructorInterceptor";

    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return new ConstructorInterceptPoint[] {
            new ConstructorInterceptPoint() {
                @Override
                public ElementMatcher<MethodDescription> getConstructorMatcher() {
                    return takesArgumentWithType(0, "com.rabbitmq.client.impl.AMQConnection");
                }

                @Override
                public String getConstructorInterceptor() {
                    return INTERCEPTOR_CONSTRUCTOR;
                }
            }
        };
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
            new DeclaredInstanceMethodsInterceptPoint() {
                @Override
                public ElementMatcher<MethodDescription> getMethodsMatcher() {
                    return named(ENHANCE_METHOD_DISPATCH).and(takesArgumentWithType(2, "com.rabbitmq.client.AMQP$BasicProperties"));
                }

                @Override
                public String getMethodsInterceptor() {
                    return INTERCEPTOR_CLASS;
                }

                @Override
                public boolean isOverrideArgs() {
                    return false;
                }
            }
        };
    }

    @Override
    protected ClassMatch enhanceClass() {
        return HierarchyMatch.byHierarchyMatch(new String[] {ENHANCE_CLASS_PRODUCER});
    }
}

对这个方法做了如下增强,主要是通过BasicProperties中获取链路相关信息放入CarrierItem中。

public class RabbitMQConsumerInterceptor implements InstanceMethodsAroundInterceptor {
    public static final String OPERATE_NAME_PREFIX = "RabbitMQ/";
    public static final String CONSUMER_OPERATE_NAME_SUFFIX = "/Consumer";

    @Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        MethodInterceptResult result) throws Throwable {
        ContextCarrier contextCarrier = new ContextCarrier();
        String url = (String) objInst.getSkyWalkingDynamicField();
        Envelope envelope = (Envelope) allArguments[1];
        AMQP.BasicProperties properties = (AMQP.BasicProperties) allArguments[2];
        AbstractSpan activeSpan = ContextManager.createEntrySpan(OPERATE_NAME_PREFIX + "Topic/" + envelope.getExchange() + "Queue/" + envelope
            .getRoutingKey() + CONSUMER_OPERATE_NAME_SUFFIX, null).start(System.currentTimeMillis());
        Tags.MQ_BROKER.set(activeSpan, url);
        Tags.MQ_TOPIC.set(activeSpan, envelope.getExchange());
        Tags.MQ_QUEUE.set(activeSpan, envelope.getRoutingKey());
        activeSpan.setComponent(ComponentsDefine.RABBITMQ_CONSUMER);
        SpanLayer.asMQ(activeSpan);
        CarrierItem next = contextCarrier.items();
        while (next.hasNext()) {
            next = next.next();
            if (properties.getHeaders() != null && properties.getHeaders().get(next.getHeadKey()) != null) {
                next.setHeadValue(properties.getHeaders().get(next.getHeadKey()).toString());
            }
        }
        ContextManager.extract(contextCarrier);

    }

    @Override
    public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Object ret) throws Throwable {
        ContextManager.stopSpan();
        return ret;

    }

    @Override
    public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
        Class<?>[] argumentsTypes, Throwable t) {
        ContextManager.activeSpan().log(t);
    }
}

接着找到 com.rabbitmq.client.Consumer类 handleDelivery方法 发现是一个接口

package com.rabbitmq.client;
import com.rabbitmq.client.AMQP.BasicProperties;
import java.io.IOException;

public interface Consumer {
    void handleConsumeOk(String var1);

    void handleCancelOk(String var1);

    void handleCancel(String var1) throws IOException;

    void handleShutdownSignal(String var1, ShutdownSignalException var2);

    void handleRecoverOk(String var1);

    void handleDelivery(String var1, Envelope var2, BasicProperties var3, byte[] var4) throws IOException;
}

 寻找这个方法的调用链

 ChannelN.processAsync->ChannelN.processDelivery->ConsumerDispatcher.handleDelivery AbstractMessageListenerContainer类中executeListener ->doExecuteListener-> invokeListener ->ContainerDelegate.invokeListener (这个是函数接口)

this._channel0 = new AMQChannel(this, 0) {
    @Override public boolean processAsync(Command c) throws IOException {
        return getConnection().processControlCommand(c);
    }
};

public abstract boolean processAsync(Command command) throws IOException;

这个方法在 ConsumerDispatcher  被使用, 

package com.rabbitmq.client.impl;
final class ConsumerDispatcher {
public void handleDelivery(final Consumer delegate,
                           final String consumerTag,
                           final Envelope envelope,
                           final AMQP.BasicProperties properties,
                           final byte[] body) throws IOException {
    executeUnlessShuttingDown(
    new Runnable() {
        @Override
        public void run() {
            try {
                delegate.handleDelivery(consumerTag,
                        envelope,
                        properties,
                        body);
            } catch (Throwable ex) {
                connection.getExceptionHandler().handleConsumerException(
                        channel,
                        ex,
                        delegate,
                        consumerTag,
                        "handleDelivery");
            }
        }
    });
}
}

2.基础逻辑分析

       原生的rabbitmq 插件是对ConsumerDispatcher 类 handleDelivery方法增强只是将消息中带的链路信息传到 handleDelivery方法新建的线程中。如果使用spring整合的方式消费,具体实现类里面方法handleDelivery又会将消息放到一个blockqueue队列中,然后由另外线程去消费这个消息。这也就是链路信息断掉的原因。

spring提供的实现内容分为两步:

(下面解释 相同类里面方法调用用空格隔开调用不同类的方法会加上类名且加上箭头->)

分析代码源码版本为

        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-amqp</artifactId>
            <version>2.0.3.RELEASE</version>
            <scope>compile</scope>
        </dependency>

1.消息放入blockqueue流程
ConsumerDispatcher  handleDelivery 的run()delegate.handleDelivery  将消息放进 BlockingQueue<Delivery> 中 (Delivery是message的一个封装,delegate是一个BlockingQueueConsumer.ConsumerDecorator 也是一个装饰模式 装饰BlockingQueueConsumer对象,实际调用还是BlockingQueueConsumer的handleDelivery)
具体 调用链 ConsumerDecorator handleDelivery -> InternalConsumer handleDelivery (由 BlockingQueueConsumer.this.queue.put(new Delivery(consumerTag, envelope, properties, body)); 这行代码放入BlockingQueue 中)
2.消费消息,应用于@RabbitListener注解的方法
SimpleMessageListenerContainer  里面静态类 AsyncMessageProcessingConsumer(该类是个runnable) 异步线程 run() 循环调用  receiveAndExecute  doReceiveAndExecute   consumer.nextMessage获取队列中的message

doReceiveAndExecute  调用父抽象类 AbstractMessageListenerContainer  executeListener invokeListener  actualInvokeListener doInvokeListener ->MessagingMessageListenerAdapter  onMessage invokeHandler  最后调用自己定义带@RabbitListener的方法。

辅助驱动这两个步骤的线程池
1.ConsumerWorkService 内部类 WorkPoolRunnable  运行 ConsumerDispatcher run方法,内部有个线程池。
2.AbstractMessageListenerContainer  中属性 “private Executor taskExecutor = new SimpleAsyncTaskExecutor();” 自定义了一个Executor,大致实现了限制并发度的功能。
3.RabbitListenerEndpointRegistry start() SimpleMessageListenerContainer doStart方法 ->  用 taskExecutor 启动了 AsyncMessageProcessingConsumer(runnable)

3.实现

知道断开原因就开始实现连接链路插件改写

增强invokeListener方法

public class RabbitMQConsumerInvokeInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {

    public static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.rabbitmq.RabbitMQConsumerInvokeInterceptor";
    public static final String ENHANCE_CLASS_PRODUCER = "org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer";
    public static final String ENHANCE_METHOD_DISPATCH = "invokeListener";
    public static final String INTERCEPTOR_CONSTRUCTOR = "org.apache.skywalking.apm.plugin.rabbitmq.RabbitMQProducerAndConsumerConstructorInterceptor";
    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return new ConstructorInterceptPoint[] {
            new ConstructorInterceptPoint() {
                    @Override public ElementMatcher<MethodDescription> getConstructorMatcher() {
                        return takesArgumentWithType(0,"com.rabbitmq.client.impl.AMQConnection");
                    }

                    @Override public String getConstructorInterceptor() {
                        return INTERCEPTOR_CONSTRUCTOR;
                    }
                }
        };
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
            new InstanceMethodsInterceptPoint() {
                    @Override public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        return named(ENHANCE_METHOD_DISPATCH).and(takesArgumentWithType(1,"org.springframework.amqp.core.Message"));
                    }


                    @Override public String getMethodsInterceptor() {
                        return INTERCEPTOR_CLASS;
                    }

                    @Override public boolean isOverrideArgs() {
                        return true;
                    }
                }
        };
    }

    @Override
    protected ClassMatch enhanceClass() {
        return MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS_PRODUCER);
    }
}

切面实现增强实现

public class RabbitMQConsumerInvokeInterceptor implements InstanceMethodsAroundInterceptor {
    public static final String OPERATE_NAME_PREFIX = "RabbitMQ/";
    public static final String CONSUMER_OPERATE_NAME_SUFFIX = "/Consumer/invoke/";
 
    @Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
        ContextCarrier contextCarrier = new ContextCarrier();

        Connection connection = ((Channel)allArguments[0]).getConnection();
        String url =  connection.getAddress().toString().replace("/","") + ":" + connection.getPort();
        Message message = (Message) allArguments[1];
        MessageProperties msgProperties = message.getMessageProperties();
        String msgTopic = msgProperties.getConsumerQueue();
        AbstractSpan activeSpan = ContextManager.createEntrySpan(OPERATE_NAME_PREFIX + "Queue/" + msgTopic +
                CONSUMER_OPERATE_NAME_SUFFIX , null).start(System.currentTimeMillis());
        Tags.MQ_BROKER.set(activeSpan, url);
        Tags.MQ_TOPIC.set(activeSpan, msgProperties.getReceivedExchange());
        Tags.MQ_QUEUE.set(activeSpan, msgProperties.getConsumerQueue());
        activeSpan.setComponent(ComponentsDefine.RABBITMQ_CONSUMER);
        SpanLayer.asMQ(activeSpan);
        CarrierItem next = contextCarrier.items();
        while (next.hasNext()) {
            next = next.next();
            if (msgProperties.getHeaders() != null && msgProperties.getHeaders().get(next.getHeadKey()) != null) {
                next.setHeadValue(msgProperties.getHeaders().get(next.getHeadKey()).toString());
            }
        }
        ContextManager.extract(contextCarrier);
    }

    @Override
    public Object afterMethod(EnhancedInstance enhancedInstance, Method method, Object[] objects, Class<?>[] classes, Object o) throws Throwable {
        ContextManager.stopSpan();
        return o;
    }

    @Override
    public void handleMethodException(EnhancedInstance enhancedInstance, Method method, Object[] objects, Class<?>[] classes, Throwable throwable) {
        ContextManager.activeSpan().log(throwable);
    }
}

具体实现见 https://github.com/summer-slow-grace/rabbitmq-5.x-plugin

不同spring版本会有细小差异,如果应用不同版本spring,将插件增强方法更改对应到具体版本invokeListener位置即可。

 

参考文章中Skywalking中RabbitMQ消费链路被隔断_Antony868的博客-CSDN博客说到的

自认为我看到官方版本的是没有问题的,就是解决handleDelivery到run方法中的,真正引起链路断裂是spring中异步消费问题引起的,而不是这篇文章中 handleDelivery方法。

參考文章

Skywalking中RabbitMQ消费链路被隔断_Antony868的博客-CSDN博客

 @RabbitListener源码解析_u013905744的专栏-CSDN博客_@rabbitlistener

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值