基於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);
	}
}

 

 

 

 

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