RabbitMQ para resolver el problema de las transacciones distribuidas

SpringBoot mecanismo de reintento de mensajes

Mensaje mecanismo de reintento idempotente

¿Cómo elegir el mecanismo de reintento derecha

Caso 1: Los consumidores reciben el mensaje, llame a la interfaz de terceros, pero la interfaz no está disponible temporalmente, es necesaria la necesidad de Volver a intentar?

Caso 2: El acceso de los consumidores a las noticias, la conversión de datos se produce una excepción, la necesidad no tiene por qué Volver a intentar?
Resumen: 2, el código genera una excepción si el consumidor es la necesidad de lanzar una nueva versión de resolver el problema para el caso, entonces no lo hacen necesidad de reintento, reintento no ayudará. En caso de utilizar el trabajo de tareas de cronómetros cheques registro + + de salud lleva a cabo la compensación manual

Si el mensaje es para asegurar que los consumidores idempotencia, para no repetir el consumo

Motivo: retrasos en la transmisión de la red, la causa es MQ reintento, proceso de reintento puede causar la duplicación del gasto.

Solución:
El uso mundial de identificador de mensaje determinar consumidores utilizar el mismo idempotencia resolución.

Identificación para distinguir el mensaje basándose en el mensaje global, para resolver idempotente

productor:

Solicitud conjunto encabezado del mensaje Identificación (messageId)

@Component
public class FanoutProducer {
	@Autowired
	private AmqpTemplate amqpTemplate;

	public void send(String queueName) {
		String msg = "my_fanout_msg:" + System.currentTimeMillis();
		Message message = MessageBuilder.withBody(msg.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)
				.setContentEncoding("utf-8").setMessageId(UUID.randomUUID() + "").build();
		System.out.println(msg + ":" + msg);
		amqpTemplate.convertAndSend(queueName, message);
	}
}

consumidores:

código del núcleo

@Component
public class FanoutEamilConsumer {
	@RabbitListener(queues = "fanout_email_queue")
	public void process(Message message) throws Exception {
		System.out
				.println(Thread.currentThread().getName() + ",邮件消费者获取生产者消息msg:" + new String(message.getBody(), "UTF-8")
						+ ",messageId:" + message.getMessageProperties().getMessageId());
		// int i = 1 / 0;
	}
}

configuración de la aplicación

spring:
  rabbitmq:
  ####连接地址
    host: 127.0.0.1
   ####端口号   
    port: 5672
   ####账号 
    username: guest
   ####密码  
    password: guest
   ### 地址
    virtual-host: /admin_host
    listener:
      simple:
        retry:
        ####开启消费者重试
          enabled: true
         ####最大重试次数
          max-attempts: 5
        ####重试间隔次数
          initial-interval: 3000
             

server:
  port: 8081

Call Interface RabbitMQ reintento del consumidor

//邮件队列
@Component
public class FanoutEamilConsumer {
	@RabbitListener(queues = "fanout_email_queue")
	public void process(String msg) throws Exception {

		System.out.println("邮件消费者获取生产者消息msg:" + msg);
		JSONObject jsonObject = JSONObject.parseObject(msg);
		// 获取email参数
		String email = jsonObject.getString("email");
		// 请求地址
		String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email;
		JSONObject result = HttpClientUtils.httpGet(emailUrl);
		if (result == null) {
			// 因为网络原因,造成无法访问,继续重试
			throw new Exception("调用接口失败!");
		}
		System.out.println("执行结束....");

	}
}



@RabbitListener(queues = "fanout_email_queue")
	public void process(Message message) throws Exception {
		// 获取消息Id
		String messageId = message.getMessageProperties().getMessageId();
		String msg = new String(message.getBody(), "UTF-8");
		System.out.println("邮件消费者获取生产者消息" + "messageId:" + messageId + ",消息内容:" + msg);
		JSONObject jsonObject = JSONObject.parseObject(msg);
		// 获取email参数
		String email = jsonObject.getString("email");
		// 请求地址
		String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email;
		JSONObject result = HttpClientUtils.httpGet(emailUrl);
		if (result == null) {
			// 因为网络原因,造成无法访问,继续重试
			throw new Exception("调用接口失败!");
		}
		System.out.println("执行结束....");

	}

el modo de signo RabbitMQ

//邮件队列
@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());
		// 手动ack
		Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
		// 手动签收
		channel.basicAck(deliveryTag, false);
	}
}

Abra la respuesta manual

spring:
  rabbitmq:
  ####连接地址
    host: 127.0.0.1
   ####端口号   
    port: 5672
   ####账号 
    username: guest
   ####密码  
    password: guest
   ### 地址
    virtual-host: /admin_host
    listener: 
      simple:
        retry:
        ####开启消费者异常重试
          enabled: true
         ####最大重试次数
          max-attempts: 5
        ####重试间隔次数
          initial-interval: 2000
        ####开启手动ack  
        acknowledge-mode: manual 

RabbitMQ cola de mensajes

Suena como una letra muerta mensajes de cola "muerto" es en realidad una media de bits, cola de mensajes cuando un mensaje en una cola por las siguientes razones:
el mensaje es rechazado (basic.reject / basic.nack) y requeue ya no desviados = false
mensaje extendido (RabbitMQ Time-to-live - > messageProperties.setExpiration ())
cola de sobrecarga
se ha convertido en "letra muerta" después de haber sido desviada (publicar) a otro Exchange es el DLX remitido a la Bolsa de acuerdo con las normas de obligado cumplimiento supervisar la cola puede ser re-consumo que el blanco en la cola correspondiente no es otro lugar de consumir noticias re-consumo
productores -> mensajes -> interruptor -> Trabajo -> se convierten en letra muerta -> interruptor DLX -> Servicios -> consumidores

¿Qué es una letra muerta ella? ¿Qué tipo de mensaje que se convertirá en letra muerta?
Mensaje es rechazada (basic.reject o basic.nack) y requeue = false.
Mensaje TTL expiró
cola alcanza la longitud máxima (cola está llena, capaz de añadir los datos de esta mq)
escenario de aplicación de análisis
en la cola cuando se define el servicio, puede ser considerado para especificar BADMAIL un interruptor, y se unen a una cola de mensajes cuando el mensaje se convierte en letra muerta, el mensaje será enviado a la maldita cola de canal, por lo que nos ayudan a ver la causa del fracaso del mensaje

Cómo utilizar la letra muerta lo cambia?
Cuando definición de cola de tráfico (general) especificar los parámetros
x-letra muerta de intercambio: de ajustar el interruptor para enviar después de la letra muerta
x-muertos-carta-ruta- clave: para establecer la letra routingKey muertos

estructuras ambientales carta cola de
letra muerta de configuración de cola de
productores dispuestos

@Component
public class FanoutConfig {

	/**
	 * 定义死信队列相关信息
	 */
	public final static String deadQueueName = "dead_queue";
	public final static String deadRoutingKey = "dead_routing_key";
	public final static String deadExchangeName = "dead_exchange";
	/**
	 * 死信队列 交换机标识符
	 */
	public static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange";
	/**
	 * 死信队列交换机绑定键标识符
	 */
	public static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key";

	// 邮件队列
	private String FANOUT_EMAIL_QUEUE = "fanout_email_queue";

	// 短信队列
	private String FANOUT_SMS_QUEUE = "fanout_sms_queue";
	// fanout 交换机
	private String EXCHANGE_NAME = "fanoutExchange";

	// 1.定义邮件队列
	@Bean
	public Queue fanOutEamilQueue() {
		// 将普通队列绑定到死信队列交换机上
		Map<String, Object> args = new HashMap<>(2);
		args.put(DEAD_LETTER_QUEUE_KEY, deadExchangeName);
		args.put(DEAD_LETTER_ROUTING_KEY, deadRoutingKey);
		Queue queue = new Queue(FANOUT_EMAIL_QUEUE, true, false, false, args);
		return queue;
	}

	// 2.定义短信队列
	@Bean
	public Queue fanOutSmsQueue() {
		return new Queue(FANOUT_SMS_QUEUE);
	}

	// 2.定义交换机
	@Bean
	FanoutExchange fanoutExchange() {
		return new FanoutExchange(EXCHANGE_NAME);
	}

	// 3.队列与交换机绑定邮件队列
	@Bean
	Binding bindingExchangeEamil(Queue fanOutEamilQueue, FanoutExchange fanoutExchange) {
		return BindingBuilder.bind(fanOutEamilQueue).to(fanoutExchange);
	}

	// 4.队列与交换机绑定短信队列
	@Bean
	Binding bindingExchangeSms(Queue fanOutSmsQueue, FanoutExchange fanoutExchange) {
		return BindingBuilder.bind(fanOutSmsQueue).to(fanoutExchange);
	}

	/**
	 * 配置死信队列
	 * 
	 * @return
	 */
	@Bean
	public Queue deadQueue() {
		Queue queue = new Queue(deadQueueName, true);
		return queue;
	}

	@Bean
	public DirectExchange deadExchange() {
		return new DirectExchange(deadExchangeName);
	}

	@Bean
	public Binding bindingDeadExchange(Queue deadQueue, DirectExchange deadExchange) {
		return BindingBuilder.bind(deadQueue).to(deadExchange).with(deadRoutingKey);
	}

}

Configuración de los consumidores

@RabbitListener(queues = "fanout_email_queue")
	public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {
		String messageId = message.getMessageProperties().getMessageId();
		String msg = new String(message.getBody(), "UTF-8");
		System.out.println("邮件消费者获取生产者消息msg:" + msg + ",消息id:" + messageId);
		JSONObject jsonObject = JSONObject.parseObject(msg);
		Integer timestamp = jsonObject.getInteger("timestamp");
		try {
			int result = 1 / timestamp;
			System.out.println("result:" + result);
			// 通知mq服务器删除该消息
			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
		} catch (Exception e) {
			e.printStackTrace();
			// // 丢弃该消息
			channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
		}

	}

@Component
public class DeadConsumer {

	@RabbitListener(queues = "dead_queue")
	public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {
		String messageId = message.getMessageProperties().getMessageId();
		String msg = new String(message.getBody(), "UTF-8");
		System.out.println("死信邮件消费者获取生产者消息msg:" + msg + ",消息id:" + messageId);
		channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

	}

}

MQ resolver tres conceptos importantes de la transacción distribuida

1, aseguran que los productores deben entregar el mensaje al mecanismo de servidor Confirmar MQ
2, para garantizar que los consumidores puedan correctamente noticias del consumidor, ACK manual (Nota idempotente)
3, la forma de garantizar que la primera transacción debe ser creado con éxito (en la creación de una conforman una sola cola, con destino al mismo conmutador, compruebe si los datos del pedido ha sido creado para lograr mecanismo de compensación en la base de datos)

Los productores deben asegurarse de que el mensaje se entrega al servidor MQ (uso)

RabbitMQ para resolver el problema de las transacciones distribuidas

RabbitMQ solucionado Distribuido principio de Transacción: El principio de consistencia eventual. Necesidad de garantizar que los tres elementos 1 siguientes, los datos de confirmación de entrega a un determinado servidor productor MQ (utilizando acuses de recibo de mensaje MQ) 2, MQ mensaje consumidor puede consumir correctamente los mensajes, el modo manual ACK (nota que reintentos idempotent cuestiones) 3, la forma de garantizar que la primera transacción realizada en primer lugar, mediante el mecanismo de compensación, la creación de un solo suplemento consumidores escuchan, si las órdenes no crean éxito, un suplemento individual.

artículo de la orden

productores

@Service
public class OrderService extends BaseApiService implements RabbitTemplate.ConfirmCallback {
	@Autowired
	private OrderMapper orderMapper;
	@Autowired
	private RabbitTemplate rabbitTemplate;

	public ResponseBase addOrderAndDispatch() {
		OrderEntity orderEntity = new OrderEntity();
		orderEntity.setName("蚂蚁课堂永久会员充值");
		orderEntity.setOrderCreatetime(new Date());
		// 价格是300元
		orderEntity.setOrderMoney(300d);
		// 状态为 未支付
		orderEntity.setOrderState(0);
		Long commodityId = 30l;
		// 商品id
		orderEntity.setCommodityId(commodityId);
		String orderId = UUID.randomUUID().toString();
		orderEntity.setOrderId(orderId);
		// ##################################################
		// 1.先下单,创建订单 (往订单数据库中插入一条数据)
		int orderResult = orderMapper.addOrder(orderEntity);
		System.out.println("orderResult:" + orderResult);
		if (orderResult <= 0) {
			return setResultError("下单失败!");
		}
		// 2.使用消息中间件将参数存在派单队列中
		send(orderId);
		return setResultSuccess();
	}

	private void send(String orderId) {
		JSONObject jsonObect = new JSONObject();
		jsonObect.put("orderId", orderId);
		String msg = jsonObect.toJSONString();
		System.out.println("msg:" + msg);
		// 封装消息
		Message message = MessageBuilder.withBody(msg.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)
				.setContentEncoding("utf-8").setMessageId(orderId).build();
		// 构建回调返回的数据
		CorrelationData correlationData = new CorrelationData(orderId);
		// 发送消息
		this.rabbitTemplate.setMandatory(true);
		this.rabbitTemplate.setConfirmCallback(this);
		rabbitTemplate.convertAndSend("order_exchange_name", "orderRoutingKey", message, correlationData);

	}

	// 生产消息确认机制
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		String orderId = correlationData.getId();
		System.out.println("消息id:" + correlationData.getId());
		if (ack) {
			System.out.println("消息发送确认成功");
		} else {
			send(orderId);
			System.out.println("消息发送确认失败:" + cause);
		}

	}

}

consumidores individuales de suplemento

@Component
public class CreateOrderConsumer {
	@Autowired
	private OrderMapper orderMapper;

	@RabbitListener(queues = "order_create_queue")
	public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {
		String messageId = message.getMessageProperties().getMessageId();
		String msg = new String(message.getBody(), "UTF-8");
		System.out.println("补单消费者" + msg + ",消息id:" + messageId);
		JSONObject jsonObject = JSONObject.parseObject(msg);
		String orderId = jsonObject.getString("orderId");
		// 判断订单是否存在,如果不存在 实现自动补单机制
		OrderEntity orderEntityResult = orderMapper.findOrderId(orderId);
		if (orderEntityResult != null) {
			System.out.println("订单已经存在 无需补单  orderId:" + orderId);
			return;
		}
		// 订单不存在 ,则需要进行补单

		OrderEntity orderEntity = new OrderEntity();
		orderEntity.setName("蚂蚁课堂永久会员充值");
		orderEntity.setOrderCreatetime(new Date());
		// 价格是300元
		orderEntity.setOrderMoney(300d);
		// 状态为 未支付
		orderEntity.setOrderState(0);
		Long commodityId = 30l;
		// 商品id
		orderEntity.setCommodityId(commodityId);
		orderEntity.setOrderId(orderId);
		// ##################################################
		// 1.先下单,创建订单 (往订单数据库中插入一条数据)
		try {
			int orderResult = orderMapper.addOrder(orderEntity);
			System.out.println("orderResult:" + orderResult);
			if (orderResult >= 0) {
				// 手动签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			}
		} catch (Exception e) {
			// 丢弃该消息
			channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
		}

	}
}

RabbitmqConfig

@Component
public class RabbitmqConfig {

	// 下单并且派单存队列
	public static final String ORDER_DIC_QUEUE = "order_dic_queue";
	// 补单队列,判断订单是否已经被创建
	public static final String ORDER_CREATE_QUEUE = "order_create_queue";
	// 下单并且派单交换机
	private static final String ORDER_EXCHANGE_NAME = "order_exchange_name";

	// 1.定义订单队列
	@Bean
	public Queue directOrderDicQueue() {
		return new Queue(ORDER_DIC_QUEUE);
	}

	// 2.定义补订单队列
	@Bean
	public Queue directCreateOrderQueue() {
		return new Queue(ORDER_CREATE_QUEUE);
	}

	// 2.定义交换机
	@Bean
	DirectExchange directOrderExchange() {
		return new DirectExchange(ORDER_EXCHANGE_NAME);
	}

	// 3.订单队列与交换机绑定
	@Bean
	Binding bindingExchangeOrderDicQueue() {
		return BindingBuilder.bind(directOrderDicQueue()).to(directOrderExchange()).with("orderRoutingKey");
	}

	// 3.补单队列与交换机绑定
	@Bean
	Binding bindingExchangeCreateOrder() {
		return BindingBuilder.bind(directCreateOrderQueue()).to(directOrderExchange()).with("orderRoutingKey");
	}

}

Enviar un único servicio

consumidor

@Component
public class DispatchConsumer {
	@Autowired
	private DispatchMapper dispatchMapper;

	@RabbitListener(queues = "order_dic_queue")
	public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {
		String messageId = message.getMessageProperties().getMessageId();
		String msg = new String(message.getBody(), "UTF-8");
		System.out.println("派单服务平台" + msg + ",消息id:" + messageId);
		JSONObject jsonObject = JSONObject.parseObject(msg);
		String orderId = jsonObject.getString("orderId");
		if (StringUtils.isEmpty(orderId)) {
			// 日志记录
			return;
		}
		DispatchEntity dispatchEntity = new DispatchEntity();
		// 订单id
		dispatchEntity.setOrderId(orderId);
		// 外卖员id
		dispatchEntity.setTakeoutUserId(12l);
		// 外卖路线
		dispatchEntity.setDispatchRoute("40,40");
		try {
			int insertDistribute = dispatchMapper.insertDistribute(dispatchEntity);
			if (insertDistribute > 0) {
				// 手动签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			}
		} catch (Exception e) {
			e.printStackTrace();
			// // 丢弃该消息
			channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
		}
	}

}
Publicado 61 artículos originales · ganado elogios 4 · Vistas 7590

Supongo que te gusta

Origin blog.csdn.net/selt791/article/details/104887491
Recomendado
Clasificación