如何在RabbitMQ通信的微服务间共享单个Sentry事务?
如何在采用RabbitMQ通信的微服务间使用单个Sentry事务?
我尝试了以下代码,但消费者会创建新事务而非沿用原有事务,请问如何通过RabbitMQ在两个微服务间传递事务?
生产者中间件代码
public function handle(Envelope $envelope, StackInterface $stack): Envelope { $transaction = SentrySdk::getCurrentHub()->getTransaction(); if (!is_null($transaction)) { $spanContext = new SpanContext(); $spanContext->setOp('rabbitmq.send'); $spanContext->setDescription('description'); $span = $transaction->startChild($spanContext); $sentryTrace = $span->toTraceparent(); $envelope = $envelope->with(new SentryStamp($sentryTrace)); $envelope = $stack->next()->handle($envelope, $stack); $span->finish(); return $envelope; } return $stack->next()->handle($envelope, $stack); }
消费者中间件代码
public function handle(Envelope $envelope, StackInterface $stack): Envelope { /** @var $sentryStamp SentryStamp */ $sentryStamp = $envelope->last(SentryStamp::class); if (!is_null($sentryStamp)) { $sentryTrace = $sentryStamp->getSentryTrace(); $transactionContext = TransactionContext::fromSentryTrace($sentryTrace); $transactionContext->setOp('rabbitmq.consumer'); $transactionContext->setDescription('description'); $transaction = SentrySdk::getCurrentHub()->startTransaction($transactionContext); SentrySdk::getCurrentHub()->setSpan($transaction); $spanContext = new SpanContext(); $spanContext->setOp('processing'); $spanContext->setDescription("description"); $span = $transaction->startChild($spanContext); SentrySdk::getCurrentHub()->setSpan($span); $envelope = $stack->next()->handle($envelope, $stack); $span->finish(); $transaction->finish(); return $envelope; } return $stack->next()->handle($envelope, $stack); }
问题原因与解决方法
你当前的消费者代码会创建新事务,核心原因是TransactionContext::fromSentryTrace($sentryTrace)生成的上下文是用于启动独立子事务的,但你直接调用startTransaction会在Sentry中生成一个全新的事务节点,而非延续生产者的根事务链路。
正确的做法是:消费者端不要启动新事务,而是基于传递的trace信息启动子span,将消息消费过程作为生产者根事务的一部分进行追踪。
修改后的消费者中间件代码
public function handle(Envelope $envelope, StackInterface $stack): Envelope { /** @var $sentryStamp SentryStamp */ $sentryStamp = $envelope->last(SentryStamp::class); if (!is_null($sentryStamp)) { $sentryTrace = $sentryStamp->getSentryTrace(); // 基于传递的trace信息创建span上下文,而非事务上下文 $spanContext = SpanContext::fromSentryTrace($sentryTrace); $spanContext->setOp('rabbitmq.consumer'); $spanContext->setDescription('description'); // 启动子span,而非新事务 $consumerSpan = SentrySdk::getCurrentHub()->startSpan($spanContext); SentrySdk::getCurrentHub()->setSpan($consumerSpan); // 处理消息的子span,嵌套在消费span下 $processingSpan = $consumerSpan->startChild(new SpanContext([ 'op' => 'processing', 'description' => 'description' ])); SentrySdk::getCurrentHub()->setSpan($processingSpan); $envelope = $stack->next()->handle($envelope, $stack); // 按逆序结束span,保证链路层级正确 $processingSpan->finish(); $consumerSpan->finish(); return $envelope; } return $stack->next()->handle($envelope, $stack); }
关键要点说明
- 生产者端代码无需修改,它已经正确创建了
rabbitmq.send子span,并将trace信息通过SentryStamp嵌入到消息中。 - 消费者端必须用
SpanContext::fromSentryTrace()替代TransactionContext::fromSentryTrace(),确保基于原有事务的trace信息创建子span。 - 调用
startSpan()而非startTransaction(),让消费操作归属到生产者发起的根事务链路中。 - 严格遵循span嵌套顺序:先启动消费span,再启动处理子span,最后按逆序结束,保证链路层级清晰。
内容的提问来源于stack exchange,提问作者sveta600
相关产品推荐
相关产品推荐

