基于MQ消息中间件的分布式事务解决方案

1,模拟支付宝转账到余额宝的整个操作流程图如下
                   

 

2,直接上代码

          Alipay---支付宝

       xml配置

                AccountMapper.xml               

<mapper namespace="com.reyco.core.dao.AccountDao">
 	
 	<select id="getAccountById" resultType="Account">
 		select id,name,amount from account where id = #{id}
 	</select>
 	
	<update id="updateAccountById">
 		update account set amount = amount-#{amount},gmtModified=now() where id= #{id}
 	</update> 	
 	
</mapper>

               MessageMapper.xml

 <mapper namespace="com.reyco.core.dao.MessageDao">
 	
 	<!-- 新增 -->
 	<insert id="insertMessage">
 		insert into message(accountId,price,status,gmtCreate,gmtModified) values(#{accountId},#{price},#{status},now(),now())
 		<selectKey resultType="int" keyProperty="id" order="AFTER">
			select @@identity
		</selectKey>
 	</insert>
 	<select id="getMessageById" resultType="MessageMQ">
 		select id,price,accountId,status from message where id = #{id}
 	</select>
 	
 	<update id="updateStatusById">
 		update message set status = #{status},gmtModified=now() where id = #{id}
 	</update>
 	
	<!-- 扫描所有未确认的消息 --> 	
 	<select id="listMessageByStatus" resultType="MessageMQ">
 		select id,price,accountId,status from message where status = 1
 	</select>
 	
 	
</mapper>

  Mapper代码

        AccountDao.java

public interface AccountDao {
	/**
	 * 查询余额
	 * @param accountId
	 * @return
	 */
	Account getAccountById(Integer accountId);
	/**
	 *      转账-->出钱
	 * @param accountId   转账人id
	 * @param amount      转账金额
	 * @param messageId   消息id
	 */
	void updateAccountById(Account account);
}

       MessageDao.java   

public interface MessageDao {
	/**
	  *     新增message
	 * @param message   实体参数
	 */
	void insertMessage(MessageMQ message);
	/**
	 * 查询
	 * @param id
	 * @return
	 */
	MessageMQ getMessageById(Integer id);
	/**
	 * 扫描未确认的消息
	 * @return
	 */
	List<MessageMQ> listMessageByStatus();
	/**
	 * 修改状态
	 * @param id
	 * @param status
	 */
	void updateStatusById(MessageMQ message);
}

Service代码

    AccountService.java           

public interface AccountService {
	
	Account getAccountById(Integer accountId);
	/**
	  *      转账
	 * @param accountId   转账人id
	 * @param amount      转账金额
	 * @param messageId   消息id
	 * @param messageStatus 消息状态
	 */
	void updateAccountById(Account account);
	/**
	 * 
	 * @param accountId
	 * @param amount
	 * @return
	 */
	boolean transfer(Integer accountId, Integer amount);
	
	void testUpdateAccountById(Account account);
}

    messageService.java

public interface MessageService {
	/**
	  *     新增message
	 * @param message   实体参数
	 */
	void insertMessage(MessageMQ message);
	/**
	  *      查询
	 * @param id
	 * @return
	 */
	MessageMQ getMessageById(Integer id);
	/**
	  *      扫描未确认的消息
	 * @return
	 */
	List<MessageMQ> listMessageByStatus();
	/**
	  *      修改状态
	 * @param id
	 * @param status
	 */
	void updateStatusById(String param);
}

      QueueProducerService.java

public interface QueueProducerService {
	/**
	  *  发送Object queue消息
	 * @param message 消息内容
	 * @return
	 */
	public void sendObjectMessage(MessageMQ message);
	/**
	  *  发送text queue消息
	 * @param message 消息内容
	 * @return
	 */
	public void sendTextMessage(String message);
	
}

ServiceImpl代码

          AccountServiceImpl.java          

@Service("accountService")
public class AccountServiceImpl implements AccountService {
	
	protected Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	private AccountDao accountDao;

	@Autowired
	private MessageService messageService;

	@Autowired
	private TransactionTemplate transactionTemplate;

	@Autowired
	private QueueProducerService queueProducerService;

	/**
	 * 转账流程:  
	 * 			 1,修改金额; 
	 *           2,修改金额成功,新增一条未确认消息,修改金额失败,不新增消息; 
	 *           3,新增消息成功,向MQ发送一条消息。
	 *     如果向MQ发送消息失败(消息丢失),用一个定时任务,定时扫描未确认的消息用于消息(发送MQ消息).
	 */
	public boolean transfer(Integer id, Integer amount) {
		// 未提交状态
		Integer stats = 1;
		MessageMQ message = new MessageMQ(id,amount,stats);
		Boolean flag = transactionTemplate.execute(new TransactionCallback<Boolean>() {
			@Override
			public Boolean doInTransaction(TransactionStatus status) {
				try {
					logger.info("###########支付宝方查询余额操作###########id==" + id);
					Account account = accountDao.getAccountById(id);
					Integer oldAmount = account.getAmount();
					logger.info("###########支付宝方查询当前账号余额###########OldAmount==" + oldAmount);
					if (oldAmount < amount) {
						logger.info("###########支付宝方余额不足###########" + account);
						return false;
					}
					// 设置余额
					account.setAmount(amount);
					logger.info("###########支付宝方扣款操作###########" + account);
					accountDao.updateAccountById(account);
					logger.info("###########支付宝方添加消息操作###########" + message);
					// 新增一条消息
					messageService.insertMessage(message);
					return true;
				} catch (Exception e) {
					e.printStackTrace();
					throw new RuntimeException();
				}
			}
		});
		// 失败
		if (!flag) {
			return false;
		}
		// 获取新增message的id
		Integer messageId = message.getId();
		// 向MQ发送一条消息
		logger.info("###########支付宝方向MQ发送消息###########" + message);
		queueProducerService.sendObjectMessage(message);
		return true;
	}

	/**
	 * 转账流程: 
	 *         1,修改金额; 
	 *         2,修改金额成功,新增一条未确认消息,修改金额失败,不新增消息; 
	 *         3,新增消息成功,向MQ发送一条消息。
	 *      如果向MQ发送消息失败(消息丢失),用一个定时任务,定时扫描未确认的消息用于消息(发送MQ消息).
	 */
	@Transactional(rollbackFor = Throwable.class)
	public boolean transfer1(Integer id, Integer amount) {
		logger.info("###########支付宝方查询余额操作###########id==" + id);
		Account account = accountDao.getAccountById(id);
		Integer oldAmount = account.getAmount();
		logger.info("###########支付宝方查询当前账号余额###########OldAmount==" + oldAmount);
		if (oldAmount < amount) {
			logger.info("###########支付宝方余额不足###########" + account);
			return false;
		}
		// 设置余额
		account.setAmount(amount);
		logger.info("###########支付宝方扣款操作###########" + account);
		accountDao.updateAccountById(account);
		// 未确认状态
		Integer stats = 1;
		MessageMQ message = new MessageMQ(id, amount, stats);
		logger.info("###########支付宝方添加消息操作###########" + message);
		// 新增一条消息
		messageService.insertMessage(message);
		// 获取新增message的id
		Integer messageId = message.getId();
		// 向MQ发送一条消息
		if (null != messageId) {
			logger.info("###########支付宝方向MQ发送消息###########" + message);
			queueProducerService.sendObjectMessage(message);
		}
		return true;
	}

	@Override
	public Account getAccountById(Integer accountId) {
		return accountDao.getAccountById(accountId);
	}

	@Override
	public void updateAccountById(Account account) {
		accountDao.updateAccountById(account);
	}

	@Override
	public void testUpdateAccountById(Account account) {
		Boolean flag = (Boolean) transactionTemplate.execute(new TransactionCallback<Object>() {
			@Override
			public Object doInTransaction(TransactionStatus status) {
				try {
					account.setAmount(100);
					System.out.println("第一次" + account);
					accountDao.updateAccountById(account);
					account.setAmount(10000);
					System.out.println("第一次" + account);
					accountDao.updateAccountById(account);
					return true;
				} catch (Exception e) {
					e.printStackTrace();
				}
				return false;
			}
		});
		System.out.println("###############flag###########" + flag);
	}

}

     MessageServiceImpl.java

@Service("messageService")
public class MessageServiceImpl implements MessageService {
	protected Logger logger = LoggerFactory.getLogger(getClass());
	@Autowired
	private MessageDao messageDao;

	@Override
	public void insertMessage(MessageMQ message) {
		messageDao.insertMessage(message);
	}

	@Transactional(rollbackFor = RuntimeException.class)
	@Override
	public void updateStatusById(String param) {
		try {
			// JSONObject jsonObject = JSONObject.parseObject(param);
			// String messageId = jsonObject.getString("messageId");
			// String respCode = jsonObject.getString("respCode");
			logger.info("############支付宝收到回调参数##########"+param);
			Integer id = Integer.parseInt(param);
			MessageMQ message = this.getMessageById(id);
			logger.info("############支付宝根据根据回调参数查询消息##########"+message);
			Integer search_id = message.getId();
			if (id.equals(search_id)) {
				Integer search_status = message.getStatus();
				if(search_status==1) {
					Integer status = 0;
					message.setStatus(status);
					logger.info("############支付宝根据根据回调修改消息状态##########"+message);
					messageDao.updateStatusById(message);
					logger.info("############支付宝消息已更新确认状态##########"+message);
				}
			}
		} catch (Exception e) {
			logger.error("############消息更新失败############。。。"+param);
			throw e;
		}
	}

	@Override
	public MessageMQ getMessageById(Integer id) {
		return messageDao.getMessageById(id);
	}
	
	@Override
	public List<MessageMQ> listMessageByStatus() {
		return messageDao.listMessageByStatus();
	}

}

QueueProducerSerivceImpl.java

@Service("queueProducerService")
public class QueueProducerSerivceImpl implements QueueProducerService {
	
	@Autowired
	private JmsTemplate jmsTemplate;
	@Override
	public void sendObjectMessage(MessageMQ message) {
		//设置发送地址
		this.jmsTemplate.setDefaultDestinationName("distributedTransaction-message");
		jmsTemplate.send(new MessageCreator() {
			@Override
			public Message createMessage(Session session) throws JMSException {
				return session.createObjectMessage(message);
			}
		});
	}
	@Override
	public void sendTextMessage(String message) {
		//设置发送地址
		this.jmsTemplate.setDefaultDestinationName("text-message");
		jmsTemplate.send(new MessageCreator() {
			@Override
			public Message createMessage(Session session) throws JMSException {
				return session.createTextMessage(message);
			}
		});
	}
}

MessageScheduledJob.java

/**
  *         定时扫描消息发送失败的消息
  *         重发失败消息
 * @author reyco
 *
 */
public class MessageScheduledJob extends QuartzJobBean {
	
	protected Logger logger = LoggerFactory.getLogger(getClass());
	/**
	 * messageService
	 */
	private MessageService messageService;
	/**
	 * MQ queueService
	 */
	private QueueProducerService queueProducerService;
	
	public void setMessageService(MessageService messageService) {
		this.messageService = messageService;
	}
	public void setQueueProducerService(QueueProducerService queueProducerService) {
		this.queueProducerService = queueProducerService;
	}
	@Override
	protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
		logger.info("--------------------------------------------------");
		logger.info("###################定时任务被执行啦################");
		List<MessageMQ> messages = messageService.listMessageByStatus();
		logger.info("###################发送失败的消息################"+messages);
		if(messages.size() < 1) {
			logger.info("###################没有发送失败的消息################");
			return;
		}
		// 
		MessageTask task = new MessageTask();
		task.setMessages(messages);
		task.setQueueProducerService(queueProducerService);
		task.run();
	}
	
}

    messageTask.java

/**
  * 消费信息任务
 * 
 * @author reyco
 *
 */
public class MessageTask {
	protected Logger logger = LoggerFactory.getLogger(getClass());
	/**
	 * 信息集合
	 */
	private List<MessageMQ> messages;
	
	private QueueProducerService queueProducerService;

	public void setMessages(List<MessageMQ> messages) {
		this.messages = messages;
	}
	public void setQueueProducerService(QueueProducerService queueProducerService) {
		this.queueProducerService = queueProducerService;
	}
	public void run() {
		Integer size = messages.size();
		Integer countSize = this.getCountSize(size);
		Integer threalPoolSize = size / countSize + 1;
		List<MessageMQ> newMessages = new ArrayList<>();
		for (int i = 0; i < threalPoolSize; i++) {
			if((i+1) == threalPoolSize) {
				int startIndex =  i * countSize;
				int endIndex = size;
				newMessages = messages.subList(startIndex, endIndex);
			}else {
				int startIndex = i * countSize;
				int endIndex = (i+1) * countSize;
				newMessages = messages.subList(startIndex, endIndex);
			}
			logger.info("################newMessages size################"+newMessages.size());
			new Thread(new Runnable() {
				@Override
				public void run() {
					for(MessageMQ message : messages) {
						logger.info("################定时任务向MQ发送消息################"+message);
						queueProducerService.sendObjectMessage(message);
					}				
				}
			}).start();
		}

	}
	/**
	  *    获取每个线程池执行任务数
	 * 
	 * @param messages
	 * @return
	 */
	private Integer getCountSize(int size) {
		Integer countSize = 10;
		if (null == messages) {
			return 0;
		}
		if (size < 5) {
			countSize = 5;
		} else if (size >= 5 && size < 33) {
			countSize = 5;
		} else if (size >= 33 && size < 133) {
			countSize = 10;
		} else if (size >= 133 && size < 533) {
			countSize = 20;
		} else if (size >= 533 && size < 1333) {
			countSize = 30;
		} else if (size >= 1333 && size < 5000) {
			countSize = 60;
		} else {
			countSize = 200;
		}
		return countSize;
	}

}

      AccountController.java            

@RequestMapping("account")
@Controller
public class AccountController {
	protected Logger logger = LoggerFactory.getLogger(getClass());
	@Autowired
	private AccountService accountService;
	@Autowired
	private MessageService messageService;
	
	@ResponseBody
	@RequestMapping("transfer")
	public String transfer(Integer accountId,Integer amount) {
		if(null == accountId || null == amount) {
			return "转账失败。。。";
		}
		logger.info("Parameter:\taccountId==="+accountId+"\tamount==="+amount);
		boolean flag = accountService.transfer(accountId, amount);
		if(flag) {
			logger.info("##############支付宝方转账成功,预计24小时之内到账,请注意查收!##############");
			return "转账成功,预计24小时之内到账,请注意查收!";
		}
		return "转账失败。。。";
	}
	@ResponseBody
	@RequestMapping("callback")
	public String updateStatus(String param) {
		if(null == param || "".equals(param)) {
			return "fail";
		}
		logger.info("################Parameter:\t#################param==="+param.toString());
		try {
			messageService.updateStatusById(param);
			return "success";
		} catch (Exception e) {
			return "fail";
		}
	}
	@ResponseBody
	@RequestMapping("test")
	public String testUpdateStatus() {
		Account account = new Account();
		account.setId(1);
		accountService.testUpdateAccountById(account);
		return "ok";
	}
	
	
}

 

    balance----余额宝 

           xml配置

           accountMapper.xml

<mapper namespace="com.reyco.core.dao.AccountDao">
 	
 	<select id="getAccountById" resultType="Account">
 		select id,name,amount from account where id = #{id}
 	</select>
 	
 	<update id="updateAccountById">
 		update account set amount = amount + #{amount},gmtModified=now() where id=#{id}
 	</update> 
</mapper>

          messageMapper.xml

<mapper namespace="com.reyco.core.dao.MessageDao">
 	
 	<select id="countMessageById" resultType="int">
 		select count(*) from message where id = #{id}
 	</select>
 	
 	<!-- 新增 -->
 	<insert id="insertMessage">
 		insert into message(id,accountId,price,status,gmtCreate,gmtModified) values(#{id},#{accountId},#{price},#{status},now(),now())
 	</insert>
</mapper>

    Mapper代码

         accountDao.java

public interface AccountDao {
	/**
	 * 查询余额
	 * @param accountId
	 * @return
	 */
	Account getAccountById(Integer id);
	/**
	 *      转账-->出钱
	 * @param accountId   转账人id
	 * @param amount      转账金额
	 * @param messageId   消息id
	 */
	void updateAccountById(Account account);

}

        messageDao.java

public interface MessageDao {
	/**
	 *       查询
	 * @param id
	 * @return
	 */
	Integer countMessageById(Integer id);
	/**
	  *    新增message
	 * @param message   实体参数
	 */
	void insertMessage(MessageMQ message);
	
	
}

   Service代码

       AccountService.java    

public interface AccountService {
	/**
	 * 查询余额
	 * @param accountId
	 * @return
	 */
	Account getAccountById(Integer id);
	/**
	  *      转账---收钱
	 * @param accountId   转账人id
	 * @param amount      转账金额
	 */
	void updateAccountById(Account account);
}

    messageService.java

public interface MessageService {
	/**
	 *       查询
	 * @param id
	 * @return
	 */
	Integer countMessageById(Integer id);
	/**
	  *    新增message
	 * @param message   实体参数
	 */
	void insertMessage(MessageMQ message);
}

ServiceImpl代码

    AccountServiceImpl.java

@Service("accountService")
public class AccountServiceImpl implements AccountService {
	
	@Autowired 
	private AccountDao accountDao;
	
	@Override
	public void updateAccountById(Account account)  {
		Integer accountId = account.getId();
		Account ac = getAccountById(accountId);
		if(null == ac) {
			return;
		}
		// 修改金额
		accountDao.updateAccountById(account);
	}

	@Override
	public Account getAccountById(Integer accountId) {
		return accountDao.getAccountById(accountId);
	}

}

  MessageServiceImpl.java

@Service("messageService")
public class MessageServiceImpl implements MessageService {
	
	@Autowired
	private MessageDao messageDao;
		
	@Override
	public Integer countMessageById(Integer id) {
		return messageDao.countMessageById(id);
	}
	
	@Override
	public void insertMessage(MessageMQ message) {
		messageDao.insertMessage(message);
	}

}

      QueueMessageListener.java

@Component("messageListener")
public class QueueMessageListener implements MessageListener {
	protected Logger logger = LoggerFactory.getLogger(getClass());
	@Autowired
	private AccountService accountService;
	
	@Autowired
	private MessageService messageService;
	
	/**
	  *    收款流程:
	 *          1,监听消息,拿到MQ消息;
	 *          2,到消息表查询该消息是否消费过;
	 *          3,该消息如未消费(没有查到该消息),执行本地事务;
	 *          4,执行本地事务1,首先修改账号金额;
	 *          5,执行本地事务2,向消息表新增消息;
	 *          6,回调
	 */
	@Transactional(rollbackFor = RuntimeException.class)
	@Override
	public void onMessage(Message message) {
		try {
			if (message instanceof ObjectMessage) {
				ObjectMessage om = (ObjectMessage) message;
				Object data = om.getObject();
				if (data instanceof MessageMQ) {
					MessageMQ mcMQ = (MessageMQ) data;
					Integer mcMQId = mcMQ.getId();
					// 先去查询是否消费过
					Integer count = messageService.countMessageById(mcMQId);
					// 没有消费过
					if (0 == count) {
						Integer accountId = mcMQ.getAccountId();
						Integer amount = mcMQ.getPrice();
						Account account = new Account();
						account.setId(accountId);
						account.setAmount(amount);
						// 收钱
						logger.info("###########余额宝方收款操作###########"+amount);
						accountService.updateAccountById(account);
						logger.info("###########余额宝方收款成功###########"+amount);
						mcMQ.setStatus(0);
						logger.info("###########余额宝方新增消息操作###########"+mcMQ);
						// 新增消息
						messageService.insertMessage(mcMQ);
						logger.info("###########余额宝方新增消息成功###########"+mcMQ);
						RestTemplate restTemplate = this.getRestTemplate();
						// 请求参数
						//JSONObject jsonObject = new JSONObject();
						//jsonObject.put("messageId", mc.getId());
						//jsonObject.put("respCode", 200);
						//String url = "http://localhost:80/alipay/account/callback.do?param=" + jsonObject.toString();
						//ResponseEntity<Object> response = restTemplate.getForEntity(url, null);
						String url = "http://localhost:80/alipay/account/callback.do?param=" + mcMQ.getId();
						logger.info("###########余额宝方回调支付宝操作###########"+url);
						String response = restTemplate.getForObject(url, String.class);
						if("fail".equals(response)) {
							logger.error("###########余额宝方回调支付宝失败###########"+message);
							throw new RuntimeException("###########余额宝方回调支付宝失败###########。。。" + message);
						}else {
							logger.info("###########余额宝方回调支付宝成功###########" + message);
						}
					} else {
						logger.info("#################异常转账###########");
					}
				}
			}
		} catch (Exception e) {
			logger.error("###########余额宝方消息消费失败###########"+message);
			throw new RuntimeException("###########余额宝方消息消费失败###########" + message);
		}
	}

	/**
	 *   请求工具
	 * @return
	 */
	public RestTemplate getRestTemplate() {
		SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
		simpleClientHttpRequestFactory.setConnectTimeout(3000);
		simpleClientHttpRequestFactory.setReadTimeout(3000);
		return new RestTemplate(simpleClientHttpRequestFactory);
	}
}

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章