阿里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扩展点实现日志串联
- 实现扩展点接口:
// 生产消息
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 {
}
}
- 配置扩展点:完成接口实现后,我们需要在
classpath:META-INF/services
目录下创建名为com.aliyun.openservices.ons.api.spi.ProducerInterceptor
的文件,并在其中添加实现类的全限定名:
com.xuance.rocketmq.TraceIdProducerInterceptor
com.xuance.rocketmq.TraceIdConsumerInterceptor
- 测试与验证:最后,我们编写测试用例或在实际业务场景中验证扩展点的功能是否正常。通过查看输出的日志信息,我们可以确认是否成功通过traceId将相关日志串联起来。
通过以上步骤,我们可以利用RocketMQ
的扩展点功能实现日志串联的需求,提高系统的可追踪性和可维护性。同时,RocketMQ
的插件化架构也为我们提供了更多的定制化可能,可以根据实际需求灵活调整消息处理的各个环节。
以上内容在如下版本中验证通过:com.aliyun.openservices:ons-client1.8.8.Final
如有问题可与我联系!