背景介绍
\qquad rocketmq是目前非常主流的一款用于发送消息通讯,实现异步解耦的中间,但是我们使用quarkus来构建我们的服务应用的时候会碰到一个很蛋疼的事,就是rocketmq目前为止还没有整合quarkus中,更加没有整合vertx来实现api的响应式调用。
简单的介绍一下我们一般编程模式下碰到的问题
\qquad
我们知道当用户从浏览器对我们的后端服务发起请求时,我们后端应用会跟N个服务或者中间件进行打交道,这个时候就会产生网络IO请求,如下图:
一个简单的已购订单查询服务,当中牵涉到了三个服务,分别是前置服务负责组装从订单服务和产品服务获取到的数据,这个服务从用户请求到最终的返回如果我们使用arthas工具来打印出代码中各个方法的耗时,我们可以发现整个请求链路的时间都被请求订单服务和商品服务占据了,剩下的那点微乎其微的时间只是用在数据的组装逻辑上;从这可以看出我们线程的主要时间都是用于等待网络IO了,在这期间是什么也做不了的,这样子的话如果要提升服务链路
通过整合vertx实现rocketmq客户端响应式调用
引入依赖,构建quarkus应用
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
<quarkus.platform.version>2.9.2.Final</quarkus.platform.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.1</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
创建端封装rocketmq客户端调用的类
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.vertx.AsyncResultUni;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.impl.VertxInternal;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
public class ReactiveRocketmqClient {
VertxInternal vertx;
DefaultMQProducer producer;
public ReactiveRocketmqClient(VertxInternal vertx, DefaultMQProducer producer) {
this.vertx = vertx;
this.producer = producer;
}
public Uni<SendResult> send(Message message) {
Future<SendResult> resultFuture = vertx.executeBlocking(blockPromise -> {
try {
SendResult send = producer.send(message);
blockPromise.complete(send);
} catch (Exception e) {
blockPromise.fail(e);
}
});
return AsyncResultUni.toUni(handler -> resultFuture
.onComplete(ar -> emit(handler, ar)));
}
private <T> void emit(Handler<AsyncResult<T>> handler, AsyncResult<T> ar) {
if (ar.succeeded()) {
handler.handle(Future.succeededFuture(ar.result()));
} else {
handler.handle(Future.failedFuture(ar.cause()));
}
}
}
创建监听类
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import javax.enterprise.context.ApplicationScoped;
import java.util.List;
@ApplicationScoped
public class MessageDemoListener implements MessageListenerConcurrently {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.println("接收到消息,内容是:" + new String(msgs.get(0).getBody()));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
Bean生产类
import io.vertx.core.Vertx;
import io.vertx.core.impl.VertxInternal;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
@ApplicationScoped
public class BeanProduce {
@Produces
ReactiveRocketmqClient reactiveRocketmqClient(Vertx vertx, MessageDemoListener listener) throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer("g-producer-test");
producer.setNamesrvAddr("192.168.19.132:9876");
producer.start();
startConsumer(listener);
return new ReactiveRocketmqClient((VertxInternal) vertx, producer);
}
DefaultMQPushConsumer startConsumer(MessageDemoListener listener) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("g-consumer-test");
consumer.setNamesrvAddr("192.168.19.132:9876");
consumer.subscribe("reactive-mq", "*");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.registerMessageListener(listener);
consumer.start();
System.out.println("消费者已启动");
return consumer;
}
}
创建Resource类
import io.smallrye.mutiny.Uni;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.io.UnsupportedEncodingException;
@Path("/mq")
public class MqResource {
@Inject
ReactiveRocketmqClient reactiveRocketmqClient;
@Path("/send")
@GET
public Uni<SendResult> send(@QueryParam("content") String content) throws UnsupportedEncodingException {
Message message = new Message("reactive-mq", content.getBytes(RemotingHelper.DEFAULT_CHARSET));
return reactiveRocketmqClient.send(message);
}
}
写在最后
从上述的实现我们可以看到,我们只是把rocketmq的调用放在了vertx的一个可以执行阻塞式代理的线程池中调用而已,治标不治本,而且监听类也没有办法做到响应式编码,基于这样的简单封装,勉强可以使用,但是本质上rocketmq的调用还是阻塞式的,要想彻底改变这种情况大家可以异步到github上有一个对rocketmq客户端的封装,地址:https://github.com/hjh7516/quarkus-rocketmq-client