RabbitMQ解决分布式事务实例

1.产生事务条件如下:

确认生产者将信息投递到MQ服务器中(采用MQ确认机制)
    生产者向MQ发送消息失败,采用重试机制
确认消费者能正确的消费消息,采用手动ACK模式(注意幂等性问题)
    消费者消费消息失败,生产者无需回滚 
生产者和消费者都成功,但是生产者后续步骤出现异常,数据库事务回滚
    生产者同时投递到两个队列,第二个队列判断生产者数据是否插入数据库,未插入则执行数据库插入逻辑


项目背景:umz-merchant-platform 商户平台项目  --用于创建订单 生成订单表
        umz-pay                支付项目     --跟据订单表中的订单号生成对应的流水表

 2.实例项目构建

RabbitMQ解决分布式事务实例_第1张图片

 3.部分代码

umz-merchant-platform 商户平台项目

#########发起订单请求#######################

package com.microservice.soa.controller;

import java.util.Date;

import javax.annotation.Resource;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.microservice.soa.dao.order.EquipOrderDao;
import com.microservice.soa.model.EquipOrder;
import com.microservice.soa.service.order.EquipOrderService;
import com.microservice.soa.service.order.impl.EquipOrderServiceImpl;
import com.microservice.soa.util.Utils;
import com.microservice.soa.vo.EquipOrderRequest;

@RestController
public class EquipOrderController {
	
	@Resource
	private EquipOrderServiceImpl equipOrderService;

	@RequestMapping("createOrder")
	@ResponseBody
	public String createOrder(@RequestBody EquipOrderRequest equipOrder) {
		String result = equipOrderService.createOrder_tx(equipOrder);
		return result;
		
	}
}
##########################生成订单 发送端 消息提供者 ###########################

package com.microservice.soa.service.order.impl;

import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.microservice.soa.dao.order.EquipOrderDao;
import com.microservice.soa.model.EquipOrder;
import com.microservice.soa.util.JsonToBean;
import com.microservice.soa.util.Utils;
import com.microservice.soa.vo.EquipOrderRequest;

import net.sf.json.JSONObject;

/**
 *  订单业务实现
 *  分布式事务思路解决方案
 *  	1.如果消费者消费消息失败,生产者是不需要回滚事务
 *  		解决方法:消费采用手动ack应答方式,采用MQ进行补偿重试机制,注意MQ补偿幂等问题。
 *  	2.如何确认生产者一定要将数据投递到MQ服务器中Confirm机制(确认应答机制)
 *  		如查生产者发送消息到MQ服务器失败
 *  		解决办法:使用生产者重试机制进行发消息。
 * @author jiajie
 *
 */
@Service("equipOrderService")
public class EquipOrderServiceImpl implements RabbitTemplate.ConfirmCallback {
	private static Logger log = LoggerFactory.getLogger(EquipOrderServiceImpl.class);
	@Resource
	private EquipOrderDao equipOrderDao;
	
	@Resource
	RabbitTemplate template;

//	@Override
	@Transactional
	public int insert(EquipOrderRequest equipOrderRequest) {
		// TODO Auto-generated method stub
		EquipOrder equipOrder = new EquipOrder();
		equipOrder.setOrderId(Utils.createUtil());   	 //生成uuid
		equipOrder.setOrderNo(equipOrderRequest.getOrderNo()); 				//生成订单号
		equipOrder.setCreateTime(new Date());
		equipOrder.setDeliveryTime(new Date());
		equipOrder.setOrderName(equipOrderRequest.getOrderName());
		equipOrder.setConsigneeProvince(equipOrderRequest.getConsigneeProvince());
		equipOrder.setConsigneeCity(equipOrderRequest.getConsigneeCity());
		equipOrder.setConsigneeArea(equipOrderRequest.getConsigneeArea());
		equipOrder.setConsigneeSite(equipOrderRequest.getConsigneeSite());
		equipOrder.setConsigneeTel(equipOrderRequest.getConsigneeTel());
		equipOrder.setOrderAmt(Long.parseLong(equipOrderRequest.getOrderAmt()));
		equipOrder.setOrderStatus("1");
		equipOrder.setFreightPrice(new BigDecimal(equipOrderRequest.getFreightPrice()));
		equipOrder.setGoodsPrice(new BigDecimal(equipOrderRequest.getGoodsPrice()));
		equipOrder.setGoodsNum(Integer.parseInt(equipOrderRequest.getGoodsNum()));
		equipOrder.setOrderName(equipOrderRequest.getOrderName());
		//插入订单表
//		equipOrderDao.insert(equipOrder);
		//模拟补尝 
		 int result = 1/0;
		//插入订单表 
		return equipOrderDao.insert(equipOrder);
	}
	
	/**
	 * 创建订单
	 * @param equipOrder
	 * @return
	 */
	@Transactional
	public String createOrder_tx(EquipOrderRequest equipOrderRequest) {
		try {
			String orderNo = Utils.getOrderIdByTime();
			equipOrderRequest.setOrderNo(orderNo);
			
			JSONObject json = JSONObject.fromObject(equipOrderRequest);
			String jsonString = json.toString();
			System.out.println("jsonString:" + jsonString);
			
			EquipOrder equipOrder = new EquipOrder();
			equipOrder.setOrderId(Utils.createUtil());   	 //生成uuid
			equipOrder.setOrderNo(equipOrderRequest.getOrderNo()); 				//生成订单号
			equipOrder.setCreateTime(new Date());
			equipOrder.setDeliveryTime(new Date());
			equipOrder.setOrderName(equipOrderRequest.getOrderName());
			equipOrder.setConsigneeProvince(equipOrderRequest.getConsigneeProvince());
			equipOrder.setConsigneeCity(equipOrderRequest.getConsigneeCity());
			equipOrder.setConsigneeArea(equipOrderRequest.getConsigneeArea());
			equipOrder.setConsigneeSite(equipOrderRequest.getConsigneeSite());
			equipOrder.setConsigneeTel(equipOrderRequest.getConsigneeTel());
			equipOrder.setOrderAmt(Long.parseLong(equipOrderRequest.getOrderAmt()));
			equipOrder.setOrderStatus("1");
			equipOrder.setFreightPrice(new BigDecimal(equipOrderRequest.getFreightPrice()));
			equipOrder.setGoodsPrice(new BigDecimal(equipOrderRequest.getGoodsPrice()));
			equipOrder.setGoodsNum(Integer.parseInt(equipOrderRequest.getGoodsNum()));
			equipOrder.setOrderName(equipOrderRequest.getOrderName());
			//插入订单表
//			equipOrderDao.insert(equipOrder);
			
			//插入订单表 
			equipOrderDao.insert(equipOrder);
			
			
			// 生产者发送消息的时候需要设置消息id
			Message message = MessageBuilder.withBody(jsonString.getBytes())
					.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
					.setMessageId(UUID.randomUUID() + "").build();
			//回调
			this.template.setMandatory(true);
			this.template.setConfirmCallback(this);
			//构建回调返回的数据(消息ID)
			CorrelationData correlationData = new CorrelationData(orderNo);
			correlationData.setReturnedMessage(message);
			template.convertAndSend("orderChange","order.bar.test",message,correlationData);
			//模拟补尝 
			 int result = 1/0;
		} catch (AmqpException e) {
			// TODO Auto-generated catch block
//			e.printStackTrace();
			log.info("订单创建失败:原因【"+e.toString()+"】");
			return  "error";
		}
		
		return "success";
	}

	/**
	 * confirm 生产者确认机制 生产者往服务器发送消息的时候,采用应答机制
	 */
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		// TODO Auto-generated method stub
		log.info("消息ID:"+correlationData.getId()+"消息内容:"+correlationData.getReturnedMessage());
		
		try {
			String msg = new String(correlationData.getReturnedMessage().getBody());
			JSONObject json = JSONObject.fromObject(msg);
			EquipOrderRequest equipOrderRequest  = JsonToBean.json2Bean(json, EquipOrderRequest.class);
			
			if (ack) {
				log.info("消息发送成功!");
//				insert(equipOrderRequest);
			}else {
				createOrder_tx(equipOrderRequest);
				log.info("消息发送失败!"+cause);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
#############################事物补尝 队列监听 ################################

package com.microservice.soa.listener;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;

import com.alibaba.druid.util.DaemonThreadFactory;
import com.alibaba.druid.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microservice.soa.dao.order.EquipOrderDao;
import com.microservice.soa.model.EquipOrder;
import com.microservice.soa.util.JsonToBean;
import com.microservice.soa.util.Utils;
import com.microservice.soa.vo.EquipOrderRequest;
import com.netflix.ribbon.proxy.annotation.Http.Header;
import com.rabbitmq.client.Channel;

import net.sf.json.JSONObject;



/**
 *  @RabbitListener 底层 使用Aop进行拦截,如果程序没有抛出异常,自动提交事务,
 *  				如果Aop使用异常通知拦截  获取异常信息的话,自动实现补偿机制,该消息会缓存到rabbitmq服务器存放,
 *  				会一直重试到不抛出异常为主。解决方式 修改重试机制策略 默认间隔5秒重试一次
 * 
 * 
 * 
* @ClassName: EmailMessageListener  
* @Description: TODO(消息处理监听类 补尝队列订单)  
* @author MAOJIAJIE  
* @date 2019年4月12日  
*
 */
@Component
public class OderMessageCompensationListener{
	private static Logger log = LoggerFactory.getLogger(OderMessageCompensationListener.class);
	
	@Resource
	private EquipOrderDao equipOrderDao;
	
	@RabbitListener(queues = {"orderCompensationQueue"})
	public void onMessage(Message message,@Headers Map header,Channel channel) throws Exception {
		// TODO Auto-generated method stub
		log.info("*************************开始订单补尝监听消息*************************");
		String messageBody = new String(message.getBody());
		ObjectMapper mapper = new ObjectMapper();
		log.info("message:"+messageBody);
		
		/**
		 * rabbitmq 默认情况下,如果消费者程序出现异常的情况下,会自动实现补偿机制 (重试机制)
		 * 队列服务器发送补尝请求
		 */
		try {
//			int i = 1/0;
			JSONObject json = JSONObject.fromObject(messageBody);
			EquipOrderRequest equipOrderRequest  = JsonToBean.json2Bean(json, EquipOrderRequest.class);
			log.info("接收到邮件消息:【" + equipOrderRequest.toString() + "】");
			if (StringUtils.isEmpty(equipOrderRequest.getOrderNo())) {
				// 手动签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
				return;
			}
			EquipOrder order = equipOrderDao.queryByOrderNoInfo(equipOrderRequest.getOrderNo());
			if (order!=null) {
				//订单已经存在 手动签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
				return;
			}
			log.info("订单号:"+equipOrderRequest.getOrderNo()+"开始补尝操作!");
			EquipOrder equipOrder = new EquipOrder();
			equipOrder.setOrderId(Utils.createUtil());   	 //生成uuid
			equipOrder.setOrderNo(equipOrderRequest.getOrderNo()); 				//生成订单号
			equipOrder.setCreateTime(new Date());
			equipOrder.setDeliveryTime(new Date());
			equipOrder.setOrderName(equipOrderRequest.getOrderName());
			equipOrder.setConsigneeProvince(equipOrderRequest.getConsigneeProvince());
			equipOrder.setConsigneeCity(equipOrderRequest.getConsigneeCity());
			equipOrder.setConsigneeArea(equipOrderRequest.getConsigneeArea());
			equipOrder.setConsigneeSite(equipOrderRequest.getConsigneeSite());
			equipOrder.setConsigneeTel(equipOrderRequest.getConsigneeTel());
			equipOrder.setOrderAmt(Long.parseLong(equipOrderRequest.getOrderAmt()));
			equipOrder.setOrderStatus("1");
			equipOrder.setFreightPrice(new BigDecimal(equipOrderRequest.getFreightPrice()));
			equipOrder.setGoodsPrice(new BigDecimal(equipOrderRequest.getGoodsPrice()));
			equipOrder.setGoodsNum(Integer.parseInt(equipOrderRequest.getGoodsNum()));
			equipOrder.setOrderName(equipOrderRequest.getOrderName());
			//插入订单表
//			equipOrderDao.insert(equipOrder);
			//插入订单表
			int result = equipOrderDao.insert(equipOrder);
			if (result>=0) { //插入成功
				//订单插入成功,手动向通道发送签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
				return;
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
//			log.error("发送邮件失败!:【" + e.toString() + "】");
			//拒绝消费消息(丢失消息) 给死信队列
			channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
		}
	}

}
###########################相关工具类##############################
package com.microservice.soa.util;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;

public class JsonToBean {
	public static  T json2Bean(JSONObject json, Class clazz) throws Exception {
        T t = clazz.newInstance();
        Method[] methods = clazz.getDeclaredMethods();
        if (methods == null || methods.length == 0)
            return null;

        for (Method method : methods) {
            String methodName = method.getName();
            System.out.println(methodName);
            if (methodName.startsWith("set")) {
                StringBuffer fieldName = new StringBuffer();

                // 形参类型:String long Product boolean double short int String List
                Class ParamClazz = method.getParameterTypes()[0];

                // 通过set的method拿到字段名称name time delFlag birth price price2 age
                fieldName.append(methodName.substring(3, 4).toLowerCase())//字段首字母小写
                        .append(methodName.substring(4));

                if (!json.has(fieldName.toString())) {
                    continue;
                }

                // 常用基本类型 不含short,Date(支持String类型的 日期)
                if (ParamClazz == boolean.class || ParamClazz == Boolean.class) {
                    method.invoke(t, new Object[] { json.optBoolean(fieldName.toString(), false) });
                } else if (ParamClazz == int.class || ParamClazz == Integer.class) {
                    method.invoke(t, new Object[] { json.optInt(fieldName.toString(), 0) });
                } else if (ParamClazz == long.class || ParamClazz == Long.class) {
                    method.invoke(t, new Object[] { json.optLong(fieldName.toString(), 0) });
                } else if (ParamClazz == double.class || ParamClazz == Double.class) {
                    method.invoke(t, new Object[] { json.optDouble(fieldName.toString(), 0) });
                } else if (ParamClazz == String.class) {
                		method.invoke(t, new Object[] { json.optString(fieldName.toString(), "") });
                }else if(ParamClazz == Date.class){
                    	method.invoke(t, new Object[] { json.optString(fieldName.toString(),"") });
                }else if (List.class.isAssignableFrom(ParamClazz)) {//解析jsonArray为list
                    JSONArray array = json.getJSONArray(fieldName.toString());
                    List list = new ArrayList<>();
                    for(int i =0;array!=null && i listType = (Class) ((ParameterizedType) clazz   // 获取List的泛型类型
                        	    .getDeclaredField(fieldName.toString()).getGenericType())
                                .getActualTypeArguments()[0];

                        Object obj = json2Bean(jsonobj, listType);
                        list.add(obj);
                    }
                    method.invoke(t,list);

                } else {//jsonObject和无法解析字段的判断
                    try{
                        JSONObject getjsonObj = json.getJSONObject(fieldName.toString());
                        if (getjsonObj != null) {
                        	Object obj = json2Bean(getjsonObj, ParamClazz);
                            method.invoke(t, obj);
						}
                        
                    }catch(JSONException e){
                        e.printStackTrace();
                        continue;
                    }
                }
            }
        }
        return t;
    }
}

4.rabbitmq配置文件

package com.microservice.soa.conf;


import java.util.HashMap;
import java.util.Map;

import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    //为了方便演示我都使用的public
    //队列
    public static final String ORDER_QUEUE = "orderQueue";
    //死信队列
    public static final String ORDER_DEAD_QUEUE = "orderDeadQueue";
    //交换机
    public static final String EXCHANGE_TOPIC_NAME = "orderChange";
    //死信交换机
    public static final String EXCHANGE_DEAD_TOPIC_NAME = "orderDeadChange";
    //#路由规则 匹配 email后面 所有的键
    public static final String QUEUE_BING_ROUTINGKEY="order.#";
    //绑定死信路由(死信队列与死信交换机之前绑定关系)
    public static final String QUEUE_BING_EMAIL_DEAD_ROUTINGKEY="order.queue.dead.#";
    //绑定死信路由(消费队列与死储交换机绑定)
    public static final String QUEUE_BING_DEAD_ROUTING_ROUTINGKEY="order.deal.*";
    
    //补偿队列
    public static final String ORDER_COMPENSATION_QUEUE = "orderCompensationQueue";
    
   
    
    //# 提供者发送消息指定的 路由键(邮件)
    public static final String PRODUCER_ROUTINGKEY="order.bar.test";
    
    
    /**
     * 死信队列 交换机标识符
     */
    private static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange";
    /**
     * 死信队列交换机绑定键标识符
     */
    private static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key";
    

    //声明邮件队列
    @Bean(ORDER_QUEUE)
    public Queue QUEUE_NEWS(){    //邮件队列
    	Map args = new HashMap(2);
//    	//消费队列---------------绑定死信交换机 (x-dead-letter-exchange    声明  死信交换机)
//    	args.put(DEAD_LETTER_QUEUE_KEY,EXCHANGE_DEAD_TOPIC_NAME);
//    	//消费队列---------------绑定死信路由  x-dead-letter-routing-key    声明 死信路由键
//    	args.put(DEAD_LETTER_ROUTING_KEY, QUEUE_BING_DEAD_ROUTING_ROUTINGKEY);
//    
//        return new Queue(ORDER_QUEUE, true, false, false,args);
    	return new Queue(ORDER_QUEUE);
    }
    
    //补尝队列
    @Bean(ORDER_COMPENSATION_QUEUE)
    public Queue ORDER_COMPENSATION_QUEUE() {
    	return new Queue(ORDER_COMPENSATION_QUEUE);
    }

    //声明交换机
    @Bean(EXCHANGE_TOPIC_NAME)
    public Exchange EXCHANGE_TOPIC_INFORM(){
        //声明了一个Topic类型的交换机,durable是持久化(重启rabbitmq这个交换机不会被自动删除)
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_NAME).durable(true).build();
    }

    //声明ORDER_QUEUE队列和交换机绑定关系,并且指定RoutingKey
    @Bean
    public Binding NEWS_BINDING_TOPIC(@Qualifier(ORDER_QUEUE) Queue queue,
                                      @Qualifier(EXCHANGE_TOPIC_NAME) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(QUEUE_BING_ROUTINGKEY).noargs();
    }
    
    //声明ORDER_COMPENSATION_QUEUE补尝队列和交换机绑定关系,并且指定与ORDER_QUEUE队列同一个RoutingKey由路key
    @Bean
    public Binding NEWS_BINDING_COMPENSATION_TOPIC(@Qualifier(ORDER_COMPENSATION_QUEUE) Queue queue,
                                      @Qualifier(EXCHANGE_TOPIC_NAME) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(QUEUE_BING_ROUTINGKEY).noargs();
    }
    
    
    
//    /**
//     * 创建死信队列
//     * @return
//     */
//    @Bean
//	public Queue deadQueue() {
//		Queue queue = new Queue(ORDER_DEAD_QUEUE, true);
//		return queue;
//	}
//
//    /**
//     * 创建死信交换机
//     * @return
//     */
//	@Bean
//	public DirectExchange deadExchange() {
//		return new DirectExchange(EXCHANGE_DEAD_TOPIC_NAME);
//	}
//
//	@Bean
//	public Binding bindingDeadExchange(Queue deadQueue, DirectExchange deadExchange) {
//		return BindingBuilder.bind(deadQueue).to(deadExchange).with(QUEUE_BING_DEAD_ROUTING_ROUTINGKEY);
//	} 
    
    /**
     * rabbit 数据传输方式JSON
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
   
}

5.application.yml文件

# native
#应用端口及应用名称 商户平台项目
server:
  port: 8010
  servlet:
    context-path: /umz-merchant-platform


#数据库配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/umz_db?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 12345678
    type: com.alibaba.druid.pool.DruidDataSource
    tomcat:
      initial-size: 1
      max-wait: 60000
      min-idle: 3
      max-age: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
  mvc:
    view:
      prefix: /WEB-INF/
      suffix: .jsp
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true 
  #邮箱服务器
  mail:
    host: smtp.qq.com
    #发送邮箱用户名
    username: [email protected]
    #此处为客户端授权密码
    password: kmwptnlpeznjebaj
  #编码集
    default-encoding: UTF-8
  #必须有!邮箱授权开启,不然报错
  properties:
    mail:
      smtp:
        auth: true
          #服务器地址校正
        localhost: smtp.qq.com

  
  application:
    name: umz-merchant-platform   #客户端服务名

  #消息队列
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: admin
    ### 地址
    virtual-host: /
    ##开启消息确认机制 confirms
    publisher-confirms: true
    publisher-returns: true



#注册信息
eureka:
  instance:
    hostname: localhost  #eureka客户端主机实例名称
    instance-id: umz-merchant-platform:8763 #客户端实例名称
    prefer-ip-address: true #显示IP
    lease-renewal-interval-in-seconds: 1 #eureka 客户端向服务端发送心跳间隔时间  单位秒
    lease-expiration-duration-in-seconds: 2 #eureka服务端收到最后一次等笔
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
      # 单机 defaultZone: http://localhost:2001/eureka   #把服务注册到eureka注册中心
      #defaultZone: http://eureka2001.java1234.com:2001/eureka/,http://eureka2002.java1234.com:2002/eureka/,http://eureka2003.java1234.com:2003/eureka/ # 集群

mybatis:
  mapper-locations:
  - classpath:com/microservice/soa/dao/*.xml
  config-location: classpath:com/microservice/mybatis-config.xml
  type-aliases-package: com.microservice.soa.model

6.pom文件



	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.2.6.RELEASE
		 
	
	com.microservice
	umz-merchant-platform
	0.0.1-SNAPSHOT
	war
	umz-merchant-platform
	Demo project for Spring Boot

	
		1.8
		0.10
		Hoxton.SR4
	

	
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-tomcat
			provided
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
			
				
					org.junit.vintage
					junit-vintage-engine
				
			
		
		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-eureka-client
		
		
		
		    com.rabbitmq
		    amqp-client
		    5.5.1
		
		
		
            org.springframework.boot
            spring-boot-starter-mail
        
        
         
		    com.alibaba
		    druid
		    1.1.22
		  
		
		
			org.mybatis.spring.boot
			mybatis-spring-boot-starter
			2.1.2
		
		
		
			mysql
			mysql-connector-java
			runtime
		
		
			org.springframework.boot
			spring-boot-starter-tomcat
			provided
		
		
		
			net.sf.json-lib
			json-lib
			2.4
			jdk15
		
		
		
			org.springframework.boot
			spring-boot-starter-amqp
		
		
			org.springframework.boot
			spring-boot-starter-data-redis
		
		
		                                                          
	                                                              
	       org.springframework.boot                        
	       spring-boot-starter-log4j2                
	                   

		
			org.springframework.boot
			spring-boot-starter-test
			test
			
				
					org.junit.vintage
					junit-vintage-engine
				
			
		
		
		
         com.101tec
         zkclient
         ${zkclient.version}
     
	

	
		
			
				org.springframework.cloud
				spring-cloud-dependencies
				${spring-cloud.version}
				pom
				import
			
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	


7.umz-pay 支付项目

##################消费者 跟据订单表中的订单号 生成 支付流水表##########################
package com.microservice.soa.listener;

import java.io.IOException;
import java.util.Map;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;

import com.alibaba.druid.util.DaemonThreadFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microservice.soa.dto.EquipOrderDto;
import com.microservice.soa.model.TradeWater;
import com.microservice.soa.service.trade.TradeWaterService;
import com.microservice.soa.util.JsonToBean;
import com.microservice.soa.util.Utils;
import com.netflix.ribbon.proxy.annotation.Http.Header;
import com.rabbitmq.client.Channel;

import net.sf.json.JSONObject;



/**
 *  @RabbitListener 底层 使用Aop进行拦截,如果程序没有抛出异常,自动提交事务,
 *  				如果Aop使用异常通知拦截  获取异常信息的话,自动实现补偿机制,该消息会缓存到rabbitmq服务器存放,
 *  				会一直重试到不抛出异常为主。解决方式 修改重试机制策略 默认间隔5秒重试一次
 * 
 * 
 * 
* @ClassName: EmailMessageListener  
* @Description: TODO(消息处理监听类 订单)  
* @author MAOJIAJIE  
* @date 2019年4月12日  
*
 */
@Component
public class OderToTradeMessageListener{
	private static Logger log = LoggerFactory.getLogger(OderToTradeMessageListener.class);
	
	@Resource
	private TradeWaterService tradeWaterService;
	
	@RabbitListener(queues = {"orderQueue"})
	public void onMessage(Message message,@Headers Map header,Channel channel) throws Exception {
		// TODO Auto-generated method stub
		log.info("*************************开始订单监听消息*************************");
		String messageBody = new String(message.getBody());
		ObjectMapper mapper = new ObjectMapper();
		System.out.println(messageBody);
		
		/**
		 * rabbitmq 默认情况下,如果消费者程序出现异常的情况下,会自动实现补偿机制 (重试机制)
		 * 队列服务器发送补尝请求
		 */
		try {
//			int i = 1/0;
			JSONObject json = JSONObject.fromObject(messageBody);
			EquipOrderDto orderInfoDto  = JsonToBean.json2Bean(json, EquipOrderDto.class);
//			 Jackson2JsonMessageConverter jackson2JsonMessageConverter =new Jackson2JsonMessageConverter();
//			 EquipOrderDto equipOrderDto = (EquipOrderDto)jackson2JsonMessageConverter.fromMessage(message);
//			Email email = mapper.readValue(messageBody, Email.class);
			log.info("接收到订单消息:【" + orderInfoDto.toString() + "】");
			//调用支付sdk
			
			//插入交易流水表
			TradeWater tradeWater = new TradeWater();
			tradeWater.setMtwId(Utils.createUtil());		   //uuid
			tradeWater.setOrdNo(orderInfoDto.getOrderNo());   //订单号
			tradeWater.setTradeAmt(tradeWater.getTradeAmt());  //交易金额
			tradeWater.setCreateTime(Utils.formatDateStr());   //创建时间 
			tradeWater.setTradeTime(Utils.formatDateStr());    //交易时间
			tradeWater.setTransType("2101");                   //消费类型
			tradeWater.setPayChannel("ALIPAY");                //交易码类型
			int result = tradeWaterService.insertTradeWater(tradeWater);
			if (result>=0) {
				// 手动签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//				return;
			}
			
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
//			log.error("发送邮件失败!:【" + e.toString() + "】");
			//拒绝消费消息(丢失消息) 给死信队列
			channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
		}
	}

}

8.application.yml配置文件

# native
#应用端口及应用名称 支付项目
server:
  port: 8011
  servlet:
    context-path: /umz-pay


#数据库配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/umz_pay_db?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 12345678
    type: com.alibaba.druid.pool.DruidDataSource
    tomcat:
      initial-size: 1
      max-wait: 60000
      min-idle: 3
      max-age: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
  mvc:
    view:
      prefix: /WEB-INF/
      suffix: .jsp
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true 
  #邮箱服务器
  mail:
    host: smtp.qq.com
    #发送邮箱用户名
    username: [email protected]
    #此处为客户端授权密码
    password: kmwptnlpeznjebaj
  #编码集
    default-encoding: UTF-8
  #必须有!邮箱授权开启,不然报错
  properties:
    mail:
      smtp:
        auth: true
          #服务器地址校正
        localhost: smtp.qq.com

  
  application:
    name: umz-pay   #客户端服务名

  #消息队列
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: admin
    ### 地址
    virtual-host: /



#注册信息
eureka:
  instance:
    hostname: localhost  #eureka客户端主机实例名称
    instance-id: umz-pay:8764 #客户端实例名称
    prefer-ip-address: true #显示IP
    lease-renewal-interval-in-seconds: 1 #eureka 客户端向服务端发送心跳间隔时间  单位秒
    lease-expiration-duration-in-seconds: 2 #eureka服务端收到最后一次等笔
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
      # 单机 defaultZone: http://localhost:2001/eureka   #把服务注册到eureka注册中心
      #defaultZone: http://eureka2001.java1234.com:2001/eureka/,http://eureka2002.java1234.com:2002/eureka/,http://eureka2003.java1234.com:2003/eureka/ # 集群

mybatis:
  mapper-locations:
  - classpath:com/microservice/soa/dao/*.xml
  config-location: classpath:com/microservice/mybatis-config.xml
  type-aliases-package: com.microservice.soa.model

9.pom.xml



	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.2.6.RELEASE
		 
	
	com.microservice
	umz-pay
	0.0.1-SNAPSHOT
	war
	umz-pay
	Demo project for Spring Boot

	
		1.8
		0.10
		Hoxton.SR4
	

	
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-tomcat
			provided
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
			
				
					org.junit.vintage
					junit-vintage-engine
				
			
		
		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-eureka-client
		
		
		
		    com.rabbitmq
		    amqp-client
		    5.5.1
		
		
		
			net.sf.json-lib
			json-lib
			2.4
			jdk15
		
		
		
            org.springframework.boot
            spring-boot-starter-mail
        
        
         
		    com.alibaba
		    druid
		    1.1.22
		  
		
		
			org.mybatis.spring.boot
			mybatis-spring-boot-starter
			2.1.2
		
		
		
			mysql
			mysql-connector-java
			runtime
		
		
			org.springframework.boot
			spring-boot-starter-tomcat
			provided
		
		
		
			org.springframework.boot
			spring-boot-starter-amqp
		
		
			org.springframework.boot
			spring-boot-starter-data-redis
		
		
		                                                          
	                                                              
	       org.springframework.boot                        
	       spring-boot-starter-log4j2                
	                   

		
			org.springframework.boot
			spring-boot-starter-test
			test
			
				
					org.junit.vintage
					junit-vintage-engine
				
			
		
		
		
         com.101tec
         zkclient
         ${zkclient.version}
     
	

	
		
			
				org.springframework.cloud
				spring-cloud-dependencies
				${spring-cloud.version}
				pom
				import
			
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	


10.测试

RabbitMQ解决分布式事务实例_第2张图片

 

RabbitMQ解决分布式事务实例_第3张图片

订单插入异常时进入补尝队列 

支付项目执行监听订单信息

数据库

RabbitMQ解决分布式事务实例_第4张图片

订单表

 交易流水表

项目下载 地址:https://download.csdn.net/download/qq_31987649/12394885

你可能感兴趣的:(rabbitmq)