You can refer to the blogger's previous article on what is Message Middleware and its benefits (SpringBoot Integrates ActiveMQ), which is not covered here ~
1. Simple integration
1. Add RabbitMQ dependencies
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
2. Configure application.yml
spring: rabbitmq: host: 127.0.0.1 port: 5672 username: test password: 123456 # virtual-host: /
3. New RabbitMQConfig
package com.example.config; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RabbitMQConfig { // Test Queue Name private String testQueueName = "test_queue"; // Test switch name private String testExchangeName = "test_exchange"; // RoutingKey private String testRoutingKey = "test_routing_key"; /** Create Queue */ @Bean public Queue testQueue() { return new Queue(testQueueName); } /** Create Switch */ @Bean public TopicExchange testExchange() { return new TopicExchange(testExchangeName); } /** Bind queues to switches via routingKey */ @Bean public Binding testBinding() { return BindingBuilder.bind(testQueue()).to(testExchange()).with(testRoutingKey); } }
4. New producer s
package com.example.producer; import com.alibaba.fastjson.JSONObject; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageBuilder; import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class TestProducer { @Autowired private RabbitTemplate rabbitTemplate; public void send(String queueName) { JSONObject jsonObject = new JSONObject(); jsonObject.put("email", "756840349@qq.com"); jsonObject.put("timestamp", System.currentTimeMillis()); String jsonString = jsonObject.toJSONString(); // Producer needs to set message id when sending message Message message = MessageBuilder.withBody(jsonString.getBytes()) .setDeliveryMode(MessageDeliveryMode.PERSISTENT) .setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8") .build(); rabbitTemplate.convertAndSend(queueName, message); } }
5. New Consumers
package com.example.listener; import com.alibaba.fastjson.JSONObject; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class FanoutSmsConsumer { @RabbitListener(queues = "test_queue") public void consumeMessage(Message message) throws Exception{ String msg = new String(message.getBody(), "UTF-8"); JSONObject jsonObject = JSONObject.parseObject(msg); System.out.println("Consumer News:" + jsonObject); } }
6. Write controller tests
package com.example.controller; import com.example.producer.FanoutProducer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ProducerController { @Autowired private TestProducer TestProducer; @RequestMapping("/sendMsg") public String sendFanout() { fanoutProducer.send("test_queue"); return "success"; } }
The browser accesses localhost:8080/sendMsg, returns success, and the console listener prints a message:
2. Advanced Scenes
1. When consumers consume messages, what should they do if their business logic is abnormal?
Solution: Use message retry mechanism (built-in feature, no configuration required, automatic retry whenever an exception occurs)
Principle: @RabbitListener uses AOP interception at the bottom, commits transactions automatically if the program does not throw an exception, retries automatically if an exception is thrown, and consumes messages again
Extension 1: If an exception has been reported, it is not possible to retry all the time, at which point you can modify the retry policy (add the following configuration in application.properties):
#Turn on consumer retry spring.rabbitmq.listener.simple.retry.enabled=true #Maximum number of retries (message will be deleted if 5 retries are not possible, default is unlimited, number of retries is recommended to be less than 10) spring.rabbitmq.listener.simple.retry.max-attempts=5 #Retry interval spring.rabbitmq.listener.simple.retry.initial-interval=3000
Expansion 2: How to rationally choose retry mechanism?
(3) When consumers get a message, they call a third-party interface, but the interface is temporarily inaccessible. Do they need to retry? (Retry mechanism is required)
(Do consumers throw data conversion exceptions when they get a message and need to retry? (No retry mechanism required, release version required to resolve)
@Component public class EamilConsumer { @RabbitListener(queues = "femail_queue") public void process(String msg) throws Exception { JSONObject jsonObject = JSONObject.parseObject(msg); String email = jsonObject.getString("email"); String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email; JSONObject result = HttpClientUtils.httpGet(emailUrl); if (result == null) { // Unaccessible due to network reasons, continue retrying throw new Exception("Invoke interface failed!"); } System.out.println("end of execution...."); } }
2. RabbitMQ Message Confirmation Mechanism ack mode (default is manual reply, here is automatic reply)
(1) application.peoperties configuration:
# Manual signing spring.rabbitmq.listener.simple.acknowledge-mode=manual
(2) Modify the listener code (add "@Headers Map<String, Object>headers, Channel channel" to the request parameter):
@Component public class FanoutEamilConsumer { @RabbitListener(queues = "fanout_email_queue") public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception { System.out.println(Thread.currentThread().getName() + ",msg:" + new String(message.getBody(), "UTF-8") + ",messageId:" + message.getMessageProperties().getMessageId()); // Manual ack Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG); // Manual signing channel.basicAck(deliveryTag, false); } }
3. How can consumers ensure message idempotency and not be re-consumed?
(1) Causes: In network delay transmission, abnormal consumption or delayed consumption will cause MQ to make retry compensation, and may cause duplicate consumption in the retry process.
(2) Solutions: 1>Use global MessageId to judge whether consumers use the same, solve idempotency, 2>or use business logic to ensure uniqueness (such as order number)
Producer Code:
public void send(String queueName) { JSONObject jsonObject = new JSONObject(); jsonObject.put("email", "644064779"); jsonObject.put("timestamp", System.currentTimeMillis()); String jsonString = jsonObject.toJSONString(); System.out.println("jsonString:" + jsonString); // Producer needs to set message id when sending message String uuid = UUID.randomUUID() + ""; Message message = MessageBuilder.withBody(jsonString.getBytes()) .setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8") .setMessageId(UUID.randomUUID() + "").build(); //Save messageId in redis (blank on listener to prevent duplicate consumption) stringRedisTemplate.opsForValue().set("uuid ", uuid ); rabbitTemplate.convertAndSend(queueName, message); }
Consumer Code:
@RabbitListener(queues = "fanout_email_queue") public void process(Message message) throws Exception { String messageId = message.getMessageProperties().getMessageId(); String msg = new String(message.getBody(), "UTF-8"); JSONObject jsonObject = JSONObject.parseObject(msg); if(!stringRedisTemplate.hasKey("meaagaeId")){ return;//Has been consumed } String email = jsonObject.getString("email"); String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email; JSONObject result = HttpClientUtils.httpGet(emailUrl); //If the calling third-party mail interface is not accessible, how can I achieve automatic retry?Throw an exception if (result == null) { throw new Exception("Failed to invoke third-party mail server interface!");//If you walk into this line, you will automatically try again } stringRedisTemplate.delete(messageId);//Delete, in actual development can also be set to empty, just need to void above //Or go to this line and write to the database log table, if(StringUtils.equals.) above.To determine if there are records in the log table }