Remoting RocketMQ在5.0后为了适应多个语言以及考虑到性能等问题,引入了GRPC,不过5.0中依然保留了对Remoting协议的支持,5.0的新组件Proxy中包含了支持Remoting和GRPC两个端口,使用Remoting作为通讯协议的话,可以使用已经支持Spring Boot的客户端版本
依赖 1 2 3 4 5 6 7 8 <dependency > <groupId > org.apache.rocketmq</groupId > <artifactId > rocketmq-spring-boot-starter</artifactId > <version > 2.3.0</version > </dependency >
生产者 配置 1 2 3 4 rocketmq : name-server : 127.0.0.1:9876 producer : group : test-group
上面是两个生产者必须的配置,缺一不可,这是因为RocketMQTemplate实例化必需要这两个参数,具体可以参考org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration
代码 1 2 3 4 5 6 7 8 9 10 11 public class MessageService { @Autowired private RocketMQTemplate rocketMQTemplate; public void sendMessage (String message) { Message<String> messaging = MessageBuilder.withPayload(message).build(); rocketMQTemplate.send(RocketConfig.TEST_TOPIC, messaging); } }
一般情况下,直接使用rocketMQTemplate发送消息即可,支持单方向发送,同步发送,异步发送 ,这里的Message是spring-messaging的内容,spring-rabbitmq和spring-kafka都使用到了这个类
消费者 配置 1 @RocketMQMessageListener(consumerGroup = "test-group", topic = RocketConfig.TEST_TOPIC)
@RocketMQMessageListener注解中可以配置单个监听器的部分配置
1 2 3 4 5 rocketmq: name-server: 127.0 .0 .1 :9876 consumer: pull-batch-size: 10 topic: test-group
yaml中配置消费者的全局配置,并且其中大部分配置只支持通过yaml的方式配置
代码 1 2 3 4 5 6 7 8 9 10 @Component @RocketMQMessageListener(consumerGroup = "test-group", topic = RocketConfig.TEST_TOPIC) public class MessageListener implements RocketMQListener <String> { @Override public void onMessage (String message) { System.out.println(message); } }
消费者直接实现RocketMQListener即可,底层具体逻辑是先将消息转为String,然后再通过org.springframework.messaging.converter.MessageConverter#fromMessage方法转为具体的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private Object doConvertMessage (MessageExt messageExt) { if (!Objects.equals(this .messageType, MessageExt.class) && !Objects.equals(this .messageType, org.apache.rocketmq.common.message.Message.class)) { String str = new String (messageExt.getBody(), Charset.forName(this .charset)); if (Objects.equals(this .messageType, String.class)) { return str; } else { try { return this .messageType instanceof Class ? this .getMessageConverter().fromMessage(MessageBuilder.withPayload(str).build(), (Class)this .messageType) : ((SmartMessageConverter)this .getMessageConverter()).fromMessage(MessageBuilder.withPayload(str).build(), (Class)((ParameterizedType)this .messageType).getRawType(), this .methodParameter); } catch (Exception var4) { log.info("convert failed. str:{}, msgType:{}" , str, this .messageType); throw new RuntimeException ("cannot convert message to " + this .messageType, var4); } } } else { return messageExt; } }
事务消息 介绍 官方文档:https://rocketmq.apache.org/zh/docs/featureBehavior/04transactionmessage
RocketMQ的事务消息可以理解为一种半消息,需要通过二次确认才能确定是否可以发送给消费者,分为以下几步
发送事务消息到RocketMQ
执行本地事务
根据本地事务执行情况,提交二次确认到RocketMQ,如果RocketMQ没有收到二次确认,它会主动请求生产者,检查确认,检查确认可以保证二次确认可以成功到达RocketMQ
RocketMQ事务消息保证了发送消息和本地事务执行两个动作状态的一致性,事务消息不支持异步发送
RocketMQ和RabbitMQ的事务不太一样,RocketMQ的事务是针对单条消息,而RabbitMQ和数据库事务一样,针对的是多个操作
代码 RocketMQ的事务消息,就是加上事务监听器,先发送消息,再通过事务监听器发送二次确认,并且事务监听器支持检查确认
1 2 3 4 5 6 7 8 9 10 11 12 @Service public class MessageService { @Autowired private RocketMQTemplate rocketMQTemplate; public void sendMessage (String message) { Message<String> messaging = MessageBuilder.withPayload(message).build(); rocketMQTemplate.sendMessageInTransaction(RocketConfig.TEST_TOPIC, messaging, null ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RocketMQTransactionListener public class TransactionListener implements RocketMQLocalTransactionListener { @Override public RocketMQLocalTransactionState executeLocalTransaction (org.springframework.messaging.Message message, Object o) { System.out.println("运行本地事务" ); return RocketMQLocalTransactionState.COMMIT; } @Override public RocketMQLocalTransactionState checkLocalTransaction (org.springframework.messaging.Message message) { return RocketMQLocalTransactionState.COMMIT; } }
加上@RocketMQTransactionListener注解,就是全局定义事务监听器 默认会给所有名称为“rocketMQTemplate”的RocketMQTemplate的bean实例设置这个事务监听器 并且同一个RocketMQTemplate实例不能设置两个RocketMQLocalTransactionListener,不然会报错
由于这里RocketMQTemplate和DefaultMQProducer都交给Spring管理了,设置多个事务监听器不太方便 如果需要为不同的业务设置不同的RocketMQ事务监听器,个人认为定义一个事务监听器,然后在这个事务监听器中对不同的业务进行不同的处理 或者通过rocketMQTemplate.getProducer().getDefaultMQProducerImpl().sendMessageInTransaction这种方式发送消息并指定事务监听器,其中的getDefaultMQProducerImpl已经被标注为已弃用了,不过还可以将rocketMQTemplate.getProducer()作为参数,调用new DefaultMQProducerImpl(producer)的构造方法构造一个新的DefaultMQProducerImpl实例
延申 为了保证一致性,还有一个思路,不使用事务消息,不过将发送消息这一步放到业务的最后一步,发送成功即提交本地事务,发送失败即回滚本地事务,但是有几个问题
如果本地事务执行时间非常久,相应的回滚也会非常久,如果发送消息失败而导致业务回滚,那么对性能是极大的浪费,而使用事务消息没有执行顺序的限制,完全可以将发送消息靠前执行
如果发送消息时间非常久,那么本地事务和发送消息两个步骤顺序执行会很费时间,而使用事务消息,两个步骤异步执行也可以保证最后的一致性
GRPC 依赖 对于GRPC,官方client并未提供Spring Bean方式接入的示例,只能使用原生的client,在使用过程中,可以直接使用或者自己嵌入到Spring
1 2 3 4 5 <dependency > <groupId > org.apache.rocketmq</groupId > <artifactId > rocketmq-client-java</artifactId > <version > 5.0.7</version > </dependency >
生产者 由于是原生的client,所以没有Spring相关配置
代码 org.apache.rocketmq.client.java.example包中提供了一些构建生产者,发送消息的示例,下面是其中一个构造生产者和发送消息的一个例子,这里需要注意,使用GRPC的话,必须配置Proxy的GRPC端口,不再是NameServer的地址了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private static Producer buildProducer (TransactionChecker checker, String... topics) throws ClientException { ClientServiceProvider provider = ClientServiceProvider.loadService(); SessionCredentialsProvider sessionCredentialsProvider = new StaticSessionCredentialsProvider ("yourAccessKey" , "yourSecretKey" ); ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder().setEndpoints("foobar.com:8080" ).setCredentialProvider(sessionCredentialsProvider).build(); ProducerBuilder builder = provider.newProducerBuilder().setClientConfiguration(clientConfiguration).setTopics(topics); if (checker != null ) { builder.setTransactionChecker(checker); } return builder.build(); } public static void main (String[] args) throws ClientException { ClientServiceProvider provider = ClientServiceProvider.loadService(); String topic = "yourNormalTopic" ; Producer producer = ProducerSingleton.getInstance(new String []{topic}); byte [] body = "This is a normal message for Apache RocketMQ" .getBytes(StandardCharsets.UTF_8); String tag = "yourMessageTagA" ; Message message = provider.newMessageBuilder().setTopic(topic).setTag(tag).setKeys(new String []{"yourMessageKey-1c151062f96e" }).setBody(body).build(); try { SendReceipt sendReceipt = producer.send(message); log.info("Send message successfully, messageId={}" , sendReceipt.getMessageId()); } catch (Throwable var8) { log.error("Failed to send message" , var8); } }
消费者 由于是原生的client,所以没有Spring相关配置
代码 和生产者一样,org.apache.rocketmq.client.java.example包总也有很多消费者的示例,包括推模式,拉模式,同消费,异步消费等,下面是一个推模式同步消费的一个例子,使用GRPC的话,必须配置Proxy的GRPC端口,不再是NameServer的地址了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static void main (String[] args) throws ClientException, InterruptedException, IOException { ClientServiceProvider provider = ClientServiceProvider.loadService(); String accessKey = "yourAccessKey" ; String secretKey = "yourSecretKey" ; SessionCredentialsProvider sessionCredentialsProvider = new StaticSessionCredentialsProvider (accessKey, secretKey); String endpoints = "foobar.com:8080" ; ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder().setEndpoints(endpoints).setCredentialProvider(sessionCredentialsProvider).build(); String tag = "yourMessageTagA" ; FilterExpression filterExpression = new FilterExpression (tag, FilterExpressionType.TAG); String consumerGroup = "yourConsumerGroup" ; String topic = "yourTopic" ; PushConsumer pushConsumer = provider.newPushConsumerBuilder().setClientConfiguration(clientConfiguration).setConsumerGroup(consumerGroup).setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression)).setMessageListener((messageView) -> { log.info("Consume message={}" , messageView); return ConsumeResult.SUCCESS; }).build(); Thread.sleep(Long.MAX_VALUE); pushConsumer.close(); }
事务消息 代码 下面是org.apache.rocketmq.client.java.example包中对于事务消息的一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void main (String[] args) throws ClientException { ClientServiceProvider provider = ClientServiceProvider.loadService(); String topic = "yourTransactionTopic" ; TransactionChecker checker = (messageView) -> { log.info("Receive transactional message check, message={}" , messageView); return TransactionResolution.COMMIT; }; Producer producer = ProducerSingleton.getTransactionalInstance(checker, new String []{topic}); Transaction transaction = producer.beginTransaction(); byte [] body = "This is a transaction message for Apache RocketMQ" .getBytes(StandardCharsets.UTF_8); String tag = "yourMessageTagA" ; Message message = provider.newMessageBuilder().setTopic(topic).setTag(tag).setKeys(new String []{"yourMessageKey-565ef26f5727" }).setBody(body).build(); try { SendReceipt sendReceipt = producer.send(message, transaction); log.info("Send transaction message successfully, messageId={}" , sendReceipt.getMessageId()); } catch (Throwable var10) { log.error("Failed to send message" , var10); return ; } transaction.commit(); }
这段代码可以看出来,这里的事务消息实现了多消息事务,它的实现原理是每次发送消息后,将消息保存下来,最后commit的时候,将每条事务消息依次提交,通过这种方式实现了类似于RabbitMQ以及数据库的事务机制,实现了多条消息同时提交或者同时回滚,下面是commit方法的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void commit () throws ClientException { if (this .messageSendReceiptMap.isEmpty()) { throw new IllegalStateException ("Transactional message has not been sent yet" ); } else { Iterator var1 = this .messageSendReceiptMap.entrySet().iterator(); while (var1.hasNext()) { Map.Entry<PublishingMessageImpl, SendReceiptImpl> entry = (Map.Entry)var1.next(); PublishingMessageImpl publishingMessage = (PublishingMessageImpl)entry.getKey(); SendReceiptImpl sendReceipt = (SendReceiptImpl)entry.getValue(); this .producerImpl.endTransaction(sendReceipt.getEndpoints(), new GeneralMessageImpl (publishingMessage), sendReceipt.getMessageId(), sendReceipt.getTransactionId(), TransactionResolution.COMMIT, TransactionSource.SOURCE_CLIENT); } } }
测试 这里主要是看一下是否用到了GRPC
可以看出来,确实使用到了GRPC