RabbitMQ Essential 7: Integración con SpringBoot y Spring Cloud Stream

Directorio

Integración con SpringBoot

Lado de producción

2. Consumidor

Configuración del núcleo del consumidor:

Uso de anotaciones de @RabbitListener

3. Ejemplos de código

3.1 archivos pom y entidades de mensaje

3.2 Productor

3.3 Consumidor

Integración con Spring Cloud Stream

1. Introducción a la arquitectura

2. Conceptos básicos:

3. Ejemplos de código

3.1 archivos pom y entidades de mensaje

 3.2 Productor

3.3 Consumidor


Integración con SpringBoot

Lado de producción

Explicación detallada de la integración de SpringBoot configuración-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:

Explicación detallada de la integración de SpringBoot configuración-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 

Explicación detallada de la integración de SpringBoot configuración-consumidor
  • 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

Uso de anotaciones de @RabbitListener
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

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);
        }  
    }  
    
发布了432 篇原创文章 · 获赞 1480 · 访问量 396万+

Supongo que te gusta

Origin blog.csdn.net/fly910905/article/details/105561804
Recomendado
Clasificación