Directorio
Configuración del núcleo del consumidor:
Uso de anotaciones de @RabbitListener
3.1 archivos pom y entidades de mensaje
Integración con Spring Cloud Stream
1. Introducción a la arquitectura
3.1 archivos pom y entidades de mensaje
Integración con SpringBoot
Lado de producción
- confirma el editor, implemente un oyente para supervisar la solicitud de confirmación que nos devuelve el corredor: RabbitTemplate.ConfirmCallback
- publisher-return, para garantizar que el mensaje sea accesible al final del intermediario, si no se puede acceder a la clave de enrutamiento, utilice el escucha para realizar el procesamiento posterior en el mensaje inalcanzable para garantizar el enrutamiento exitoso del mensaje: RabbitTemplate.ReturnCallback
- Tenga en cuenta que, al enviar un mensaje, configure template = obligatorio = true para garantizar que la supervisión sea efectiva
- El lado de producción también puede configurar otros atributos, como el envío de reintentos, tiempo de espera, número de veces, intervalo, etc.
2. Consumidor
Configuración del núcleo del consumidor:
##
Modo de inicio de sesión: inicio de sesión manual spring.rabbitmq.listener.simple.acknowledge-mode = manual
## Establezca el límite de supervisión: máximo 10, predeterminado 5
spring.rabbitmq.listener.simple.concurrency = 5
spring.rabbitmq.listener.simple. concurrencia máxima = 10
- Primero configure el modo de confirmación manual para el procesamiento manual de ACK, de modo que podamos garantizar la entrega confiable del mensaje, o cuando el consumo del consumidor final falle, puede devolverse a la cola (no recomendado), de acuerdo con el registro de registros comerciales y otro procesamiento.
- Puede establecer el número de monitoreo y el número máximo del consumidor, utilizado para monitorear la concurrencia del consumidor
Uso de anotaciones de @RabbitListener
- La anotación del consumidor monitor @RabbitListener, esto es muy útil en el trabajo práctico
- @RabbitListener es una anotación combinada, que se puede configurar con anotaciones
- @QueueBinding, @Queue, @Exchange usan directamente esta combinación de anotaciones para manejar conmutadores, colas, enlaces, enrutamiento y configurar funciones de escucha a la vez.
3. Ejemplos de código
3.1 archivos pom y entidades de mensaje
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
import java.io.Serializable;
public class Order implements Serializable {
private String id;
private String name;
public Order() {
}
public Order(String id, String name) {
super();
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.2 Productor
application.properties
spring.rabbitmq.addresses=192.168.11.76:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=15000
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.template.mandatory=true
- ConejoSender
import java.util.Map;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import com.bfxy.springboot.entity.Order;
@Component
public class RabbitSender {
//自动注入RabbitTemplate模板类
@Autowired
private RabbitTemplate rabbitTemplate;
//回调函数: confirm确认
final ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.err.println("correlationData: " + correlationData);
System.err.println("ack: " + ack);
if(!ack){
System.err.println("异常处理....");
}
}
};
//回调函数: return返回
final ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText,
String exchange, String routingKey) {
System.err.println("return exchange: " + exchange + ", routingKey: "
+ routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText);
}
};
//发送消息方法调用: 构建Message消息
public void send(Object message, Map<String, Object> properties) throws Exception {
MessageHeaders mhs = new MessageHeaders(properties);
Message msg = MessageBuilder.createMessage(message, mhs);
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
//id + 时间戳 全局唯一
CorrelationData correlationData = new CorrelationData("1234567890");
rabbitTemplate.convertAndSend("exchange-1", "springboot.abc", msg, correlationData);
}
//发送消息方法调用: 构建自定义对象消息
public void sendOrder(Order order) throws Exception {
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
//id + 时间戳 全局唯一
CorrelationData correlationData = new CorrelationData("0987654321");
rabbitTemplate.convertAndSend("exchange-2", "springboot.def", order, correlationData);
}
}
- Clase de prueba
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.bfxy.springboot.entity.Order;
import com.bfxy.springboot.producer.RabbitSender;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Test
public void contextLoads() {
}
@Autowired
private RabbitSender rabbitSender;
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@Test
public void testSender1() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("number", "12345");
properties.put("send_time", simpleDateFormat.format(new Date()));
rabbitSender.send("Hello RabbitMQ For Spring Boot!", properties);
}
@Test
public void testSender2() throws Exception {
Order order = new Order("001", "第一个订单");
rabbitSender.sendOrder(order);
}
}
3.3 Consumidor
application.properties
spring.rabbitmq.addresses=192.168.11.76:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=15000
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.concurrency=5
spring.rabbitmq.listener.simple.max-concurrency=10
spring.rabbitmq.listener.order.queue.name=queue-2
spring.rabbitmq.listener.order.queue.durable=true
spring.rabbitmq.listener.order.exchange.name=exchange-2
spring.rabbitmq.listener.order.exchange.durable=true
spring.rabbitmq.listener.order.exchange.type=topic
spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true
spring.rabbitmq.listener.order.key=springboot.*
- ConejoReceptor
package com.bfxy.springboot.conusmer;
import java.util.Map;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import com.rabbitmq.client.Channel;
@Component
public class RabbitReceiver {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "queue-1",
durable="true"),
exchange = @Exchange(value = "exchange-1",
durable="true",
type= "topic",
ignoreDeclarationExceptions = "true"),
key = "springboot.*"
)
)
@RabbitHandler
public void onMessage(Message message, Channel channel) throws Exception {
System.err.println("--------------------------------------");
System.err.println("消费端Payload: " + message.getPayload());
Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
//手工ACK
channel.basicAck(deliveryTag, false);
}
/**
*
* spring.rabbitmq.listener.order.queue.name=queue-2
spring.rabbitmq.listener.order.queue.durable=true
spring.rabbitmq.listener.order.exchange.name=exchange-1
spring.rabbitmq.listener.order.exchange.durable=true
spring.rabbitmq.listener.order.exchange.type=topic
spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true
spring.rabbitmq.listener.order.key=springboot.*
* @param order
* @param channel
* @param headers
* @throws Exception
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "${spring.rabbitmq.listener.order.queue.name}",
durable="${spring.rabbitmq.listener.order.queue.durable}"),
exchange = @Exchange(value = "${spring.rabbitmq.listener.order.exchange.name}",
durable="${spring.rabbitmq.listener.order.exchange.durable}",
type= "${spring.rabbitmq.listener.order.exchange.type}",
ignoreDeclarationExceptions = "${spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions}"),
key = "${spring.rabbitmq.listener.order.key}"
)
)
@RabbitHandler
public void onOrderMessage(@Payload com.bfxy.springboot.entity.Order order,
Channel channel,
@Headers Map<String, Object> headers) throws Exception {
System.err.println("--------------------------------------");
System.err.println("消费端order: " + order.getId());
Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG);
//手工ACK
channel.basicAck(deliveryTag, false);
}
}
Integración con Spring Cloud Stream
Spring Cloud, este marco familiar es extremadamente popular entre las pequeñas y medianas empresas de Internet, por lo que el Spring Cloud Stream correspondiente está siendo valorado gradualmente por todos. Esta lección presenta principalmente cómo Spring Cloud Stream se integra con RabbitMQ.
1. Introducción a la arquitectura
- Carpeta de destino: contiene su propia aplicación Aplicación
- RabbitMQ a la izquierda y Kafka a la derecha. Indica que la producción y el envío de mensajes pueden ser mensajes de middleware diferentes. Esta es una abstracción en el nivel superior de Spring Cloud Stream. Muy lindo lugar.
- Dos lugares más importantes: final de recepción de mensaje de entrada (entrada), final de envío de mensaje de salida (salida)
- Una aplicación Spring Cloud Stream utiliza middleware de mensajes como núcleo, y la aplicación se comunica con el exterior a través de los canales de entrada / salida inyectados por Spring Cloud Stream. Los canales se comunican con el middleware de mensajes externos a través de un Binder específico.
- Amarillo: indica RabbitMQ
- Verde: los complementos, la entrada y salida de mensajes tienen una capa de complementos, los complementos se pueden usar para una variedad de mensajes diferentes, también se pueden usar para reemplazar el middleware de mensajes.
2. Conceptos básicos:
- Barista [bəˈri: stə] Interfaz: la interfaz Barista se define como un parámetro de la última clase, esta interfaz define el tipo de canal y el nombre del canal ,
- El nombre del canal se usa para la configuración,
- El tipo de canal determina si la aplicación usará este canal para enviar o recibir mensajes.
Cómo definir la interfaz del canal:
- @Output: anotación de salida, utilizada para definir la interfaz de envío de mensajes
- @Input: anotación de entrada, utilizada para definir la interfaz de consumidor del mensaje
- @StreamListener: anotación utilizada para definir el método de supervisión
Usar Spring Cloud Stream es muy simple, solo necesita usar estas tres anotaciones, lo cual es muy adecuado para la producción y el consumo de mensajes de alto rendimiento, pero hay un gran problema con el marco Spring CloudStream, es decir, no se puede lograr una entrega confiable Es decir, no se puede garantizar el 100% de confiabilidad del mensaje, y habrá un problema de una pequeña cantidad de pérdida de mensaje.
- En la actualidad, SpringCloudStream integra RabbitMQ y Kafka. Todos sabemos que Kafka no puede entregar la confiabilidad del mensaje. Esto se debe a que el marco SpringCloudStream se usa para Kafka en el trabajo real. ¡El propósito de usarlo en el trabajo real es para la comunicación de mensajes de alto rendimiento! Aquí es donde se posiciona la versión actual de Spring Cloud Stream.
- Por lo tanto, en el trabajo real, se puede usar Spring Cloud Stream. Si se requiere una entrega confiable, RabbitMQ también se puede usar solo.
3. Ejemplos de código
3.1 archivos pom y entidades de mensaje
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--spring-cloud-starter-stream-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
<version>1.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
3.2 Productor
- application.properties
server.port=8001
server.servlet.context-path=/producer
spring.application.name=producer
spring.cloud.stream.bindings.output_channel.destination=exchange-3
spring.cloud.stream.bindings.output_channel.group=queue-3
spring.cloud.stream.bindings.output_channel.binder=rabbit_cluster
spring.cloud.stream.binders.rabbit_cluster.type=rabbit
spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.addresses=192.168.11.76:5672
spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.username=guest
spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.password=guest
spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.virtual-host=/
- Interfaz barista
package com.bfxy.rabbitmq.stream;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
/**
* <B>中文类名:</B><BR>
* <B>概要说明:</B><BR>
* 这里的Barista接口是定义来作为后面类的参数,这一接口定义来通道类型和通道名称。
* 通道名称是作为配置用,通道类型则决定了app会使用这一通道进行发送消息还是从中接收消息。
*/
public interface Barista {
//String INPUT_CHANNEL = "input_channel";
String OUTPUT_CHANNEL = "output_channel";
//注解@Input声明了它是一个输入类型的通道,名字是Barista.INPUT_CHANNEL,也就是position3的input_channel。这一名字与上述配置app2的配置文件中position1应该一致,表明注入了一个名字叫做input_channel的通道,它的类型是input,订阅的主题是position2处声明的mydest这个主题
// @Input(Barista.INPUT_CHANNEL)
// SubscribableChannel loginput();
//注解@Output声明了它是一个输出类型的通道,名字是output_channel。这一名字与app1中通道名一致,表明注入了一个名字为output_channel的通道,类型是output,发布的主题名为mydest。
@Output(Barista.OUTPUT_CHANNEL)
MessageChannel logoutput();
// String INPUT_BASE = "queue-1";
// String OUTPUT_BASE = "queue-1";
// @Input(Barista.INPUT_BASE)
// SubscribableChannel input1();
// MessageChannel output1();
}
- RabbitmqSender
package com.bfxy.rabbitmq.stream;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
@EnableBinding(Barista.class)
@Service
public class RabbitmqSender {
@Autowired
private Barista barista;
// 发送消息
public String sendMessage(Object message, Map<String, Object> properties) throws Exception {
try{
MessageHeaders mhs = new MessageHeaders(properties);
Message msg = MessageBuilder.createMessage(message, mhs);
boolean sendStatus = barista.logoutput().send(msg);
System.err.println("--------------sending -------------------");
System.out.println("发送数据:" + message + ",sendStatus: " + sendStatus);
}catch (Exception e){
System.err.println("-------------error-------------");
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
return null;
}
}
- Clase de prueba
package com.bfxy.rabbitmq;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.client.utils.DateUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.bfxy.rabbitmq.stream.RabbitmqSender;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private RabbitmqSender rabbitmqSender;
@Test
public void sendMessageTest1() {
for(int i = 0; i < 1; i ++){
try {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("SERIAL_NUMBER", "12345");
properties.put("BANK_NUMBER", "abc");
properties.put("PLAT_SEND_TIME", DateUtils.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"));
rabbitmqSender.sendMessage("Hello, I am amqp sender num :" + i, properties);
} catch (Exception e) {
System.out.println("--------error-------");
e.printStackTrace();
}
}
//TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
}
3.3 Consumidor
- application.properties
server.port=8002
server.context-path=/consumer
spring.application.name=consumer
spring.cloud.stream.bindings.input_channel.destination=exchange-3
spring.cloud.stream.bindings.input_channel.group=queue-3
spring.cloud.stream.bindings.input_channel.binder=rabbit_cluster
spring.cloud.stream.bindings.input_channel.consumer.concurrency=1
spring.cloud.stream.rabbit.bindings.input_channel.consumer.requeue-rejected=false
spring.cloud.stream.rabbit.bindings.input_channel.consumer.acknowledge-mode=MANUAL
spring.cloud.stream.rabbit.bindings.input_channel.consumer.recovery-interval=3000
spring.cloud.stream.rabbit.bindings.input_channel.consumer.durable-subscription=true
spring.cloud.stream.rabbit.bindings.input_channel.consumer.max-concurrency=5
spring.cloud.stream.binders.rabbit_cluster.type=rabbit
spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.addresses=192.168.11.76:5672
spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.username=guest
spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.password=guest
spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.virtual-host=/
-
Interfaz barista
package com.bfxy.rabbitmq.stream;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
/**
* <B>中文类名:</B><BR>
* <B>概要说明:</B><BR>
* 这里的Barista接口是定义来作为后面类的参数,这一接口定义来通道类型和通道名称。
* 通道名称是作为配置用,通道类型则决定了app会使用这一通道进行发送消息还是从中接收消息。
* @author ashen(Alienware)
* @since 2016年7月22日
*/
public interface Barista {
String INPUT_CHANNEL = "input_channel";
//注解@Input声明了它是一个输入类型的通道,名字是Barista.INPUT_CHANNEL,也就是position3的input_channel。这一名字与上述配置app2的配置文件中position1应该一致,表明注入了一个名字叫做input_channel的通道,它的类型是input,订阅的主题是position2处声明的mydest这个主题
@Input(Barista.INPUT_CHANNEL)
SubscribableChannel loginput();
}
-
RabbitmqReceiver
-
package com.bfxy.rabbitmq.stream; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.messaging.Message; import org.springframework.stereotype.Service; import com.rabbitmq.client.Channel; @EnableBinding(Barista.class) @Service public class RabbitmqReceiver { @StreamListener(Barista.INPUT_CHANNEL) public void receiver(Message message) throws Exception { Channel channel = (com.rabbitmq.client.Channel) message.getHeaders().get(AmqpHeaders.CHANNEL); Long deliveryTag = (Long) message.getHeaders().get(AmqpHeaders.DELIVERY_TAG); System.out.println("Input Stream 1 接受数据:" + message); System.out.println("消费完毕------------"); channel.basicAck(deliveryTag, false); } }