SpringBoot
It is a product born to simplifySpring
a series of problems such as application creation, operation, debugging, and deployment. The feature of automatic assembly allows us to better focus on the business itself instead of external XML configuration. We only need to follow the specifications and introduce relevant Dependency can easily build a WEB project
The full name of MQ (Message Queue), also known as message queue, is a middleware for asynchronous communication . It can be understood as a post office. The sender delivers the message to the post office, and then the post office helps us send it to the specific message recipient (consumer). We don’t need to care about the specific sending process and time, and it will not interfere with me doing other things. . Common MQs include kafka, activemq, zeromq, rabbitmq, etc. The comparison and advantages and disadvantages of major MQs can be Googled by yourself
rabbitmq
RabbitMQ is a high-concurrency-oriented language that follows the AMQP protocolerlanng
. It is used in real-time message delivery that requires high reliability and supports multiple language clients. Support 延迟队列(这是一个非常有用的功能)
....
basic concepts
Broker: Simply put, it is the message queue server entity
Exchange: message exchange, which specifies the rules by which messages are routed to which queue
Queue: message queue carrier, each message will be put into one or more queues
Binding: Binding, its function is to bind exchange
and queue
follow the routing rules
Routing Key: Routing keyword, exchange
based on this keyword for message delivery
vhost: virtual host, broker
multiple can be opened in one vhost
, used for the separation of permissions of different users
producer: The message producer is the program that delivers the message
consumer: message consumer, which is the program that accepts messages
channel: message channel, in each connection of the client, multiple can be established channel
, each channel
representing a session task
For installation based on Centos7.x, please refer to: http://blog.battcn.com/2017/08/20/linux/linux-centos7-ribbitmq/
Common application scenarios
- Mailbox sending: After the user registers, the message is delivered to the
rabbitmq
center, and the message consumer sends the message asynchronously to improve the system response speed. - Traffic peak clipping: It is generally widely used in seckill activities. The seckill will cause the application to hang due to excessive traffic. To solve this problem, a message queue is generally added to the front end of the application. It is used to control the number of active people, and orders that exceed this certain threshold will be discarded directly. Relieve short-term high-flow overwhelming applications.
- Order timeout:
rabbitmq
The delay queue is used to easily implement the function of order timeout . For example, the user cancels the order without paying 30 minutes after placing the order. - There are many more application scenarios that are not listed one by one...
import dependencies
dependencies pom.xml
added in spring-boot-starter-amqp
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
property configuration
application.properties
Configure the relevant content in the file rabbitmq
, it is worth noting that the switch for manual ACK is configured here
spring.rabbitmq.username=battcn
spring.rabbitmq.password=battcn
spring.rabbitmq.host=192.168.0.133
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
# 手动ACK 不开启自动ACK模式,目的是防止报错后未正确处理消息丢失 默认 为 none
spring.rabbitmq.listener.simple.acknowledge-mode=manual
specific code
define queue
If the queue has been created manually or RabbitMQ
already exists in the queue, the following code can also be omitted...
package com.battcn.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置
*
* @author Levin
* @since 2018/4/11 0011
*/
@Configuration
public class RabbitConfig {
public static final String DEFAULT_BOOK_QUEUE = "dev.book.register.default.queue";
public static final String MANUAL_BOOK_QUEUE = "dev.book.register.manual.queue";
@Bean
public Queue defaultBookQueue() {
// 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
return new Queue(DEFAULT_BOOK_QUEUE, true);
}
@Bean
public Queue manualBookQueue() {
// 第一个是 QUEUE 的名字,第二个是消息是否需要持久化处理
return new Queue(MANUAL_BOOK_QUEUE, true);
}
}
entity class
create a Book
class
public class Book implements java.io.Serializable {
private static final long serialVersionUID = -2164058270260403154L;
private String id;
private String name;
// 省略get set ...
}
controller
Write a Controller
class for the message sending job
package com.battcn.controller;
import com.battcn.config.RabbitConfig;
import com.battcn.entity.Book;
import com.battcn.handler.BookHandler;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Levin
* @since 2018/4/2 0002
*/
@RestController
@RequestMapping(value = "/books")
public class BookController {
private final RabbitTemplate rabbitTemplate;
@Autowired
public BookController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
/**
* this.rabbitTemplate.convertAndSend(RabbitConfig.DEFAULT_BOOK_QUEUE, book); 对应 {@link BookHandler#listenerAutoAck}
* this.rabbitTemplate.convertAndSend(RabbitConfig.MANUAL_BOOK_QUEUE, book); 对应 {@link BookHandler#listenerManualAck}
*/
@GetMapping
public void defaultMessage() {
Book book = new Book();
book.setId("1");
book.setName("一起来学Spring Boot");
this.rabbitTemplate.convertAndSend(RabbitConfig.DEFAULT_BOOK_QUEUE, book);
this.rabbitTemplate.convertAndSend(RabbitConfig.MANUAL_BOOK_QUEUE, book);
}
}
message consumer
By default, it spring-boot-data-amqp
is an automatic ACK
mechanism, which means that MQ will automatically help us to ACK after the message is consumed, so there is such a problem in dependencies: if an error is reported, the message will not be lost, and it will be consumed in an infinite loop. It is easy to use the disk. When the space is exhausted, although the number of consumptions can be configured, this approach is also inelegant. At present, it is recommended that we manually ACK and then transfer the message of consumption error to other message queues for compensation processing.
package com.battcn.handler;
import com.battcn.config.RabbitConfig;
import com.battcn.entity.Book;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* BOOK_QUEUE 消费者
*
* @author Levin
* @since 2018/4/11 0011
*/
@Component
public class BookHandler {
private static final Logger log = LoggerFactory.getLogger(BookHandler.class);
/**
* <p>TODO 该方案是 spring-boot-data-amqp 默认的方式,不太推荐。具体推荐使用 listenerManualAck()</p>
* 默认情况下,如果没有配置手动ACK, 那么Spring Data AMQP 会在消息消费完毕后自动帮我们去ACK
* 存在问题:如果报错了,消息不会丢失,但是会无限循环消费,一直报错,如果开启了错误日志很容易就吧磁盘空间耗完
* 解决方案:手动ACK,或者try-catch 然后在 catch 里面讲错误的消息转移到其它的系列中去
* spring.rabbitmq.listener.simple.acknowledge-mode=manual
* <p>
*
* @param book 监听的内容
*/
@RabbitListener(queues = {RabbitConfig.DEFAULT_BOOK_QUEUE})
public void listenerAutoAck(Book book, Message message, Channel channel) {
// TODO 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
final long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.info("[listenerAutoAck 监听的消息] - [{}]", book.toString());
// TODO 通知 MQ 消息已被成功消费,可以ACK了
channel.basicAck(deliveryTag, false);
} catch (IOException e) {
try {
// TODO 处理失败,重新压入MQ
channel.basicRecover();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
@RabbitListener(queues = {RabbitConfig.MANUAL_BOOK_QUEUE})
public void listenerManualAck(Book book, Message message, Channel channel) {
log.info("[listenerManualAck 监听的消息] - [{}]", book.toString());
try {
// TODO 通知 MQ 消息已被成功消费,可以ACK了
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
// TODO 如果报错了,那么我们可以进行容错处理,比如转移当前消息进入其它队列
}
}
}
main function
package com.battcn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Levin
*/
@SpringBootApplication
public class Chapter11Application {
public static void main(String[] args) {
SpringApplication.run(Chapter11Application.class, args);
}
}
test
After completing the preparations, start Chapter11Application
accessing http://localhost:8080/books and you will see the following content, which means everything is normal....
2018-05-22 19:04:26.708 INFO 23752 --- [cTaskExecutor-1] com.battcn.handler.BookHandler : [listenerAutoAck 监听的消息] - [com.battcn.entity.Book@77d8be18]
2018-05-22 19:04:26.709 INFO 23752 --- [cTaskExecutor-1] com.battcn.handler.BookHandler : [listenerManualAck 监听的消息] - [com.battcn.entity.Book@8bb452]