阿里RocketMQ之扩展点实践

阿里RocketMQ之扩展点实践

在业务处理过程中,我们通常需要输出日志以记录关键信息。为了更便捷地追踪和排查问题,我们期望能够通过一个唯一的traceId将相关的日志串联起来,从而轻松查看整个处理过程。为此,我们有两种实现方式:一是利用spring aop进行拦截处理,二是借助RocketMQ预留的扩展点进行定制化操作。

关于spring aop的实现方式,由于其相对简单且网上有大量成熟的实现案例,这里不再赘述。接下来,我们将重点探讨RocketMQ扩展点的实践应用。

RocketMQ扩展点实践:

一、源码解析:RocketMQ如何支持扩展点

RocketMQ作为一款高性能、高可靠性的消息中间件,其内部设计充分考虑了扩展性和灵活性。通过预留扩展点,RocketMQ允许开发者在不修改核心代码的情况下,对消息的生产、消费、过滤等关键环节进行定制化操作。这主要得益于RocketMQ采用的插件化架构和SPI(Service Provider Interface)机制。
当需要生产消息时,会调用Producer类的send()方法,调用后实际由ProducerImpl类处理逻辑:

@Override
public SendResult send(Message message) {
    this.checkONSProducerServiceState(this.defaultMQProducer.getDefaultMQProducerImpl());
    DefaultInvocationContext invocationContext = new DefaultInvocationContext();
    invocationContext.setMessages(Collections.singletonList(message));
    final List<Runnable> postHandleStack = new ArrayList<Runnable>();
    // 扩展点运行
    boolean proceed = preHandle(serviceLoader, invocationContext, postHandleStack);
    try {
        if (proceed) {
            com.aliyun.openservices.shade.com.alibaba.rocketmq.common.message.Message msgRMQ = ONSUtil.msgConvert(message);
            try {
                com.aliyun.openservices.shade.com.alibaba.rocketmq.client.producer.SendResult sendResultRMQ = this.defaultMQProducer.send(msgRMQ);
                message.setMsgID(sendResultRMQ.getMsgId());
                SendResult sendResult = new SendResult();
                sendResult.setTopic(sendResultRMQ.getMessageQueue().getTopic());
                sendResult.setMessageId(sendResultRMQ.getMsgId());
                invocationContext.setSendResult(sendResult);
                return sendResult;
            } catch (Exception e) {
                LOGGER.error(String.format("Send message Exception, %s", message), e);
                ONSClientException onsClientException = checkProducerException(message.getTopic(), message.getMsgID(), e);
                invocationContext.setException(onsClientException);
                throw onsClientException;
            }
        }
    } finally {
        executePostHandle(postHandleStack);
    }
    throw new ONSClientException("ProducerInterceptor aborts sending");
}
/**
 * 运行扩展点
 */
protected boolean preHandle(ServiceLoader serviceLoader, final InvocationContext invocationContext,
        final List<Runnable> runnables) {
    if (null != serviceLoader) {
        for (Object item : serviceLoader) {
            if (item instanceof Interceptor) {
                final Interceptor interceptor = (Interceptor) item;
                // Build call-stack to unwind 
                // 后置处理 消息生产完后调用
                runnables.add(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            interceptor.postHandle(invocationContext, ONSClientAbstract.this);
                        } catch (Exception e) {
                            LOGGER.error("Exception raised while executing postHandle of custom provided " +
                                "interceptors", e);
                        }
                    }
                });

                try {
                    // 前置处理,一般会在这个节点处理一些自定义代码
                    if (!interceptor.preHandle(invocationContext, this)) {
                        return false;
                    }
                } catch (Exception e) {
                    LOGGER.error("Exception raised while executing preHandle of custom provided interceptors", e);
                }
            }
        }
    }
    return true;
}
// ServiceLoader初始化
private ServiceLoader<ProducerInterceptor> serviceLoader;
public ProducerImpl(final Properties properties) {
    ...
    ...
    serviceLoader = ServiceLoader.load(ProducerInterceptor.class);
}

看到这里基本明白RocketMQ是如何预留扩展点,并且执行扩展点的了,接下来动手写demo来验证是否生效。
好奇ServiceLoader是什么?请移步《深入了解Java的ServiceLoader类

二、实战Demo:利用RocketMQ扩展点实现日志串联

  1. 实现扩展点接口:
// 生产消息
package com.xuance.rocketmq;

import com.aliyun.openservices.ons.api.Admin;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.spi.InvocationContext;
import com.aliyun.openservices.ons.api.spi.ProducerInterceptor;
import com.byering.common.lang.util.CollectionUtils;
import com.byering.common.lang.util.TraceUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

import java.util.List;
import java.util.Optional;
import java.util.Properties;

@Slf4j
public class TraceIdProducerInterceptor implements ProducerInterceptor {
    @Override
    public boolean preHandle(InvocationContext invocationContext, Admin instance) throws Exception {
        List<Message> messages = invocationContext.getMessages().get();
        if (CollectionUtils.isEmpty(messages)) {
            return true;
        }
        try {
            Message message = messages.get(0);
            Properties userProperties = message.getUserProperties();
            if (userProperties == null) {
                userProperties = new Properties();
            }
            if (userProperties.get("traceId") != null) {
                return true;
            }
            String traceId = Optional.ofNullable(MDC.get("traceId")).orElse(TraceUtils.getTraceId());
            userProperties.put("traceId", traceId);
            message.setUserProperties(userProperties);
            log.info("traceId:{}", traceId);
        } catch (Exception e) {
            log.error("TraceIdProducerInterceptor preHandle exp", e);
        }
        return true;
    }

    @Override
    public void postHandle(InvocationContext invocationContext, Admin instance) throws Exception {

    }
}

// 消费消息
package com.xuance.rocketmq;

import com.aliyun.openservices.ons.api.Admin;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.spi.InvocationContext;
import com.aliyun.openservices.ons.api.spi.ProducerInterceptor;
import com.byering.common.lang.util.CollectionUtils;
import com.byering.common.lang.util.TraceUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

import java.util.List;
import java.util.Optional;

@Slf4j
public class TraceIdConsumerInterceptor implements ProducerInterceptor {
    @Override
    public boolean preHandle(InvocationContext invocationContext, Admin instance) throws Exception {
        List<Message> messages = invocationContext.getMessages().get();
        if (CollectionUtils.isEmpty(messages)) {
            return true;
        }
        try {
            Message message = messages.get(0);
            String traceId = Optional.ofNullable(message.getUserProperties())
                    .map(up -> up.getProperty("traceId"))
                    .orElse(TraceUtils.getTraceId());
            MDC.put("traceId", traceId);
            log.info("traceId:{}", traceId);
        } catch (Exception e) {
            log.error("TraceIdConsumerInterceptor preHandle exp", e);
        }
        return true;
    }

    @Override
    public void postHandle(InvocationContext invocationContext, Admin instance) throws Exception {

    }
}
  1. 配置扩展点:完成接口实现后,我们需要在classpath:META-INF/services目录下创建名为com.aliyun.openservices.ons.api.spi.ProducerInterceptor的文件,并在其中添加实现类的全限定名:
com.xuance.rocketmq.TraceIdProducerInterceptor
com.xuance.rocketmq.TraceIdConsumerInterceptor
  1. 测试与验证:最后,我们编写测试用例或在实际业务场景中验证扩展点的功能是否正常。通过查看输出的日志信息,我们可以确认是否成功通过traceId将相关日志串联起来。

通过以上步骤,我们可以利用RocketMQ的扩展点功能实现日志串联的需求,提高系统的可追踪性和可维护性。同时,RocketMQ的插件化架构也为我们提供了更多的定制化可能,可以根据实际需求灵活调整消息处理的各个环节。

以上内容在如下版本中验证通过:com.aliyun.openservices:ons-client1.8.8.Final
如有问题可与我联系!

  • 39
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值