
一. 跨库事务解决方案
使用
二阶段提交(try-commit/rollback)的方式:
二. 微服务解决方案
1. TCC(try - confirm/cancel)二阶段提交方案——追求强一致性方案订单类 :
@Service(value = "orderServiceImpl")
public class OrderServiceImpl extends ServersManager<Order, OrderDao> implements
OrderService {
private static final org.slf4j.Logger logger = LoggerFactory
.getLogger(OrderServiceImpl.class);
@Resource(name="orderDaoFront")
@Override
public void setDao(OrderDao orderDao) {
this.dao = orderDao;
}
@Autowired
private OrderdetailDao orderdetailDao;
@Autowired
private OrderpayDao orderpayDao;
@Autowired
private OrdershipDao ordershipDao;
@Autowired
private OrderlogDao orderlogDao;
@Autowired
private ProductService productService;
@Autowired
private AccountService accountService;
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
public void setProductService(ProductService productService) {
this.productService = productService;
}
public void setOrderpayDao(OrderpayDao orderpayDao) {
this.orderpayDao = orderpayDao;
}
public void setOrderlogDao(OrderlogDao orderlogDao) {
this.orderlogDao = orderlogDao;
}
public void setOrdershipDao(OrdershipDao ordershipDao) {
this.ordershipDao = ordershipDao;
}
public void setOrderdetailDao(OrderdetailDao orderdetailDao) {
this.orderdetailDao = orderdetailDao;
}
public Order createOrder(Order order, List<Orderdetail> orderdetailList, Ordership ordership)
throws Exception {
if(order==null || orderdetailList==null || orderdetailList.size()==0 || ordership==null){
throw new NullPointerException("参数不能为空!");
}
//创建订单
int orderID = dao.insert(order);
logger.debug("orderID=" + orderID);
//创建订单项
for (int i = 0; i < orderdetailList.size(); i++) {
Orderdetail orderdetail = orderdetailList.get(i);
orderdetail.setOrderID(orderID);
orderdetailDao.insert(orderdetail);
}
//创建支付记录对象
Orderpay orderpay = new Orderpay();
orderpay.setOrderid(order.getId());
orderpay.setPaystatus(Orderpay.orderpay_paystatus_n);
orderpay.setPayamount(Double.valueOf(order.getAmount()));
orderpay.setPaymethod(Orderpay.orderpay_paymethod_alipayescow);
int orderpayID = orderpayDao.insert(orderpay);
logger.error("orderpayID="+orderpayID);
order.setOrderpayID(String.valueOf(orderpayID));
//记录配送信息
ordership.setOrderid(String.valueOf(orderID));
ordershipDao.insert(ordership);
//记录订单创建日志
Orderlog orderlog = new Orderlog();
orderlog.setOrderid(String.valueOf(orderID));
orderlog.setAccount(order.getAccount());
orderlog.setContent("【创建订单】用户创建订单。订单总金额:"+order.getAmount());
orderlog.setAccountType(Orderlog.orderlog_accountType_w);
orderlogDao.insert(orderlog);
Order orderData=new Order();
orderData.setId(String.valueOf(orderID));
orderData.setOrderpayID(order.getOrderpayID());
return orderData;
}
@Autowired
public MessageService messageService;
public Order createMiaoshaOrder(Order order) {
if(order==null){
throw new RuntimeException("订单参数不能为空!");
}
List<Orderdetail> orderdetailList = order.getOrderdetail();
Ordership ordership = order.getOrdership();
if(orderdetailList==null || orderdetailList.size()==0 || ordership==null){
throw new RuntimeException("订单明细或地址参数不能为空!");
}
//创建订单
int orderID = dao.insert(order);
logger.debug("orderID=" + orderID);
//使用mq的方式解决分布式事务
Message message = new Message(orderID,"",productId,"init");
messageService.receiveMsg(message);
//创建订单明细
for (int i = 0; i < orderdetailList.size(); i++) {
Orderdetail orderdetail = orderdetailList.get(i);
orderdetail.setOrderID(orderID);
orderdetailDao.insert(orderdetail);
}
//创建支付记录对象
Orderpay orderpay = new Orderpay();
orderpay.setOrderid(order.getId());
orderpay.setPaystatus(Orderpay.orderpay_paystatus_n);
orderpay.setPayamount(Double.valueOf(order.getAmount()));
orderpay.setPaymethod(Orderpay.orderpay_paymethod_alipayescow);
int orderpayID = orderpayDao.insert(orderpay);
logger.error("orderpayID="+orderpayID);
order.setOrderpayID(String.valueOf(orderpayID));
//记录配送信息
ordership.setOrderid(String.valueOf(orderID));
ordershipDao.insert(ordership);
//记录订单创建日志
Orderlog orderlog = new Orderlog();
orderlog.setOrderid(String.valueOf(orderID));
orderlog.setAccount(order.getAccount());
orderlog.setContent("【创建订单】用户创建订单。订单总金额:"+order.getAmount());
orderlog.setAccountType(Orderlog.orderlog_accountType_w);
orderlogDao.insert(orderlog);
//TODO 插入日志 log(orderId,productId,deductStockCount,status)
//定时任务 轮询执行程序,从日志里获取该订单信息,往库存库里操作,如果插入成功,定时任务就不需要继续执行,如果不成功,继续执行。
//减库存,使用分布式事务后减库存逻辑需交给消息中心处理,这块逻辑需要注释掉
for (int i = 0; i < orderdetailList.size(); i++) {
String productID = String.valueOf(orderdetailList.get(i).getProductID());
//数据库减库存
Product product = new Product();
product.setId(productID);
//TODO 插入日志 log(orderId,productId,deductStockCount,status)
int updateNums = productService.updateStockAfterMiaoshaSuccess(product);
if (updateNums <= 0) {
throw new ProductSoldOutException("更新库存失败");
}
}
Order orderData=new Order();
orderData.setId(String.valueOf(orderID));
orderData.setOrderpayID(order.getOrderpayID());
//发送确认消息
//使用mq的方式解决分布式事务
Message message = new Message(orderID,"",productId,"sent");
messageService.receiveMsg(message);
return orderData;
}
@Override
public List<Order> selectOrderInfo(Order order) {
return dao.selectOrderInfo(order);
}
// @Override
// public boolean updateOrderStatus(Order order) {
// if(order==null){
// throw new NullPointerException("参数不能为空!");
// }
//
// Orderpay orderpay = orderpayDao.selectById(order.getOrderpayID());
// if(orderpay==null){
// throw new NullPointerException("根据支付记录号查询不到支付记录信息!");
// }
// String orderid = orderpay.getOrderid();//订单ID
//
// //更新支付记录为成功支付
// Orderpay orderpay2 = new Orderpay();
// orderpay2.setId(order.getOrderpayID());
// orderpay2.setTradeNo(order.getTradeNo());
// orderpay2.setPaystatus(Orderpay.orderpay_paystatus_y);
// orderpayDao.update(orderpay2);
//
// //更新订单的支付状态为成功支付
// order.setId(orderid);
// order.setPaystatus(Order.order_paystatus_y);
// dao.update(order);
// return true;
// }
@Override
public boolean alipayNotify(String trade_status,String refund_status,String out_trade_no,String trade_no) {
try {
return alipayNotify0(trade_status, refund_status, out_trade_no, trade_no);
} catch (Exception e) {
logger.error(">>>alipayNotify...Exception..");
e.printStackTrace();
return false;
}
}
private boolean alipayNotify0(String trade_status,String refund_status,String out_trade_no,String trade_no) {
synchronized (FrontContainer.alipay_notify_lock) {
logger.error("trade_status = " + trade_status + ",refund_status = " + refund_status + ",out_trade_no = " + out_trade_no + ",trade_no = " + trade_no);
if ((StringUtils.isBlank(trade_status)
&& StringUtils.isBlank(refund_status)) || (StringUtils.isBlank(out_trade_no)
&& StringUtils.isBlank(trade_no))) {
logger.error("请求非法!");
return false;
}
String orderpayID = null;
if(out_trade_no.startsWith("test")){
//此处做一个说明,localhost或127.0.0.1下的订单的请求发给支付宝的商户订单号都是test开头的,正式的都是非test开头的。
//可以参见OrdersAction.createPayInfo()方法。
orderpayID = out_trade_no.substring(4);
}else{
orderpayID = out_trade_no;
}
logger.error("orderpayID = " + orderpayID);
Orderpay orderpay = orderpayDao.selectById(orderpayID);
if(orderpay==null){
throw new NullPointerException("根据支付记录号查询不到支付记录信息!");
}
String orderid = orderpay.getOrderid();//订单ID
String content = null;
if(StringUtils.isNotBlank(refund_status)){
/**
* 退款流程
*/
if(refund_status.equals("WAIT_SELLER_AGREE")){//等待卖家同意退款 ==>卖家需处理
content = "【支付宝异步通知-->退款流程】等待卖家同意退款(WAIT_SELLER_AGREE)。";
}else if(refund_status.equals("WAIT_BUYER_RETURN_GOODS")){//卖家同意退款,等待买家退货 ==>通知买家退货,此 可以发站内信、短信、或邮件 通知对方
content = "【支付宝异步通知-->退款流程】退款协议达成,等待买家退货(WAIT_BUYER_RETURN_GOODS)。";
}else if(refund_status.equals("WAIT_SELLER_CONFIRM_GOODS")){//买家已退货,等待卖家收到退货 ==>支付宝会通知卖家
content = "【支付宝异步通知-->退款流程】等待卖家收货(WAIT_SELLER_CONFIRM_GOODS)。";
}else if(refund_status.equals("REFUND_SUCCESS")){//卖家收到退货,退款成功,交易关闭 ==>卖家登陆支付宝,确认OK。
//http://club.alipay.com/simple/?t9978565.html
content = "【支付宝异步通知-->退款流程】退款成功(REFUND_SUCCESS)。";
}else if(refund_status.equals("REFUND_CLOSED")){//卖家收到退货,退款成功,交易关闭 ==>卖家登陆支付宝,确认OK。
//http://club.alipay.com/simple/?t9978565.html
content = "【支付宝异步通知-->退款流程】退款关闭(REFUND_CLOSED)。";
}else if(refund_status.equals("SELLER_REFUSE_BUYER")){//卖家收到退货,退款成功,交易关闭 ==>卖家登陆支付宝,确认OK。
//http://club.alipay.com/simple/?t9978565.html
content = "【支付宝异步通知-->退款流程】卖家不同意协议,等待买家修改(SELLER_REFUSE_BUYER)。";
}
else{
//一般不会出现
content = "【支付宝异步通知-->退款流程】未知。refund_status = " + refund_status;
}
updateRefundStatus(orderid, refund_status);
}else if(StringUtils.isNotBlank(trade_status)){
/**
* 交易流程
*/
if(trade_status.equals("WAIT_BUYER_PAY")){//等待买家付款
content = "【支付宝异步通知】等待买家付款(WAIT_BUYER_PAY)。";
}else if(trade_status.equals("WAIT_SELLER_SEND_GOODS")){//已付款,等待卖家发货
if(orderpay.getPaystatus().equals(Orderpay.orderpay_paystatus_y)){
//由于支付宝的同步通知、异步通知,那么WAIT_SELLER_SEND_GOODS的时候会有2次通知,所以需要synchronized处理好,保证订单状态和日志的一致性。
return true;
}
content = "【支付宝异步通知】已付款,等待卖家发货(WAIT_SELLER_SEND_GOODS)。";
//更新支付记录为【成功支付】
Orderpay orderpay2 = new Orderpay();
orderpay2.setId(orderpayID);
orderpay2.setTradeNo(trade_no);
orderpay2.setPaystatus(Orderpay.orderpay_paystatus_y);
orderpayDao.update(orderpay2);
/**
* 真实砍库存,并同步减少内容库存数
*/
logger.error("真实砍库存,并同步减少内容库存数...");
Orderdetail orderdetail = new Orderdetail();
orderdetail.setOrderID(Integer.valueOf(orderid));
List<Orderdetail> orderdetailList = orderdetailDao.selectList(orderdetail);
logger.error("orderdetailList = " + orderdetailList.size());
String lowStocks = null;//订单是否缺货记录
// int score = 0;//商品积分汇总
for (int i = 0; i < orderdetailList.size(); i++) {
Orderdetail detailInfo = orderdetailList.get(i);
String productID = String.valueOf(detailInfo.getProductID());
//内存砍库存呢
ProductStockInfo stockInfo = SystemManager.getInstance().getProductStockMap().get(productID);
if(stockInfo.getStock() >= detailInfo.getNumber()){
stockInfo.setStock(stockInfo.getStock() - detailInfo.getNumber());
stockInfo.setChangeStock(true);
//数据库砍库存
Product product = new Product();
product.setId(productID);
product.setStock(stockInfo.getStock());
product.setAddSellcount(detailInfo.getNumber());//增加销量
productService.updateStockAfterPaySuccess(product);
}else{
lowStocks = Order.order_lowStocks_y;
//记录库存不足
Orderdetail od = new Orderdetail();
od.setId(detailInfo.getId());
od.setLowStocks(Orderdetail.orderdetail_lowstocks_y);
orderdetailDao.update(od);
}
// score += stockInfo.getScore();
logger.error("productID = " + productID + ",stockInfo.getStock() = " + stockInfo.getStock());
// SystemManager.productStockMap.put(product.getId(),stockInfo);
}
//更新订单的支付状态为【已支付】
Order order = new Order();
order.setId(orderid);
if (lowStocks != null) {
order.setLowStocks(Order.order_lowStocks_y);
}
order.setPaystatus(Order.order_paystatus_y);
dao.update(order);
}else if(trade_status.equals("WAIT_BUYER_CONFIRM_GOODS")){//已发货,等待买家确认收货
content = "【支付宝异步通知】已发货,等待买家确认收货(WAIT_BUYER_CONFIRM_GOODS)。";
//更新订单状态为【已发货】
Order order = dao.selectById(orderid);
if(order==null){
throw new NullPointerException("根据订单号查询不到订单信息,orderid="+orderid);
}
logger.error("order.getStatus()"+order.getStatus()+",trade_status=WAIT_BUYER_CONFIRM_GOODS");
Orderlog orderlog = new Orderlog();
orderlog.setContent(content);
orderlog.setAccount(order.getAccount());
//临时解决办法,防止此日志重复记录.
if(orderlogDao.selectCount(orderlog) > 0){
return true;
}
// if(order.getStatus().equals(Order.order_status_send)){
// //当前订单状态已经是这个状态了,无需重复操作。
// return true;
// }
//防止由于支付宝异步消息的先后顺序,导致把订单的状态更新混乱了。
if(order.getStatus().equals(Order.order_status_pass)){
order = new Order();
order.setId(orderid);
order.setStatus(Order.order_status_send);
dao.update(order);
}
}else if(trade_status.equals("TRADE_FINISHED")){//交易完成
content = "【支付宝异步通知】交易完成(TRADE_FINISHED)。";
Order order = dao.selectById(orderid);
if(order==null){
throw new NullPointerException("根据订单号查询不到订单信息,orderid="+orderid);
}
//订单结束后,订单上面赠送的积分都成功转移到用户账户上。
Account acc = new Account();
acc.setAccount(order.getAccount());
acc.setAddScore(order.getScore() - order.getAmountExchangeScore());//支付完成,扣除订单消耗的积分
accountService.updateScore(acc);
//更新订单状态为【已签收】
order = new Order();
order.setId(orderid);
order.setStatus(Order.order_status_sign);
dao.update(order);
}else{
//一般不会出现
content = "【支付宝异步通知】未知。trade_status = " + trade_status;
}
}else{
throw new RuntimeException("运行异常!");
}
/**
* 以上代码,如不可以返回都会走到此处,记录下日志.
*/
insertOrderlog(orderid, content);
return true;
}
}
/**
* 更新订单的退款状态
* @param orderid 订单ID
* @param refundStatus 退款状态
*/
private void updateRefundStatus(String orderid,String refundStatus){
Order order = new Order();
order.setId(orderid);
order.setRefundStatus(refundStatus);
dao.update(order);
}
/**
* 插入订单操作日志
* @param orderid 订单ID
* @param content 日志内容
*/
private void insertOrderlog(String orderid,String content) {
Orderlog orderlog = new Orderlog();
orderlog.setOrderid(orderid);//订单ID
orderlog.setAccount("alipay_notify");//操作人账号
orderlog.setContent(content);//日志内容
orderlog.setAccountType(Orderlog.orderlog_accountType_p);
orderlogDao.insert(orderlog);
}
@Override
public OrderSimpleReport selectOrdersSimpleReport(String account) {
return dao.selectOrdersSimpleReport(account);
}
}
4. 使用mq消息队列的方式做补偿 消息中间件:
/**
* 独立消息服务,目前只实现一个大框架,主要目的是给大家理清分布式事务的控制思路
*
*/
@Service(value = "messageServiceImpl")
public class MessageServiceImpl implements MessageService {
@Override
@Transactional
public void receiveMsg(Message message) {
if (message == null) {
throw new RuntimeException("消息为空");
}
switch (message.getStatus()) {
case MessageStatus.INIT:
// TODO save msg
System.out.println("保存初始消息");
break;
case MessageStatus.SENT:
// TODO update msg status 为sent
// TODO 发送消息到订单减库存队列,这块待同学们自己实现
System.out.println("更新消息状态为sent");
break;
case MessageStatus.END:
// TODO update msg status 为end
break;
default:
throw new RuntimeException("消息状态有误");
}
}
}
MessageService :
public interface MessageService {
void receiveMsg(Message msg);
}
Message :
import java.io.Serializable;
public class Message implements Serializable {
private Integer id;
private String bizId;
private String bizType;
private String bizData;
private int status;
public Message(String bizId, String bizType, String bizData, int status) {
super();
this.bizId = bizId;
this.bizType = bizType;
this.bizData = bizData;
this.status = status;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBizId() {
return bizId;
}
public void setBizId(String bizId) {
this.bizId = bizId;
}
public String getBizType() {
return bizType;
}
public void setBizType(String bizType) {
this.bizType = bizType;
}
public String getBizData() {
return bizData;
}
public void setBizData(String bizData) {
this.bizData = bizData;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
三. 定时任务 Quartz的使用
1. Quartz 概述Quartz 是 OpenSymphony 开源组织在 Job Scheduling 领域又一个开源项目,它可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。Quartz 可以用来创建简单或为运行十个,百个,甚至是好几万个 Jobs 这样复杂的程序。Jobs 可以做成标准的 Java 组件或 EJBs。
2. 为什么使用 Quartz?Quartz 是一个任务调度框架。比如你遇到这样的问题:
- 每天 02:00 发送一份工作邮件给工作组成员并抄送给老板(假装自己很努力的工作到深夜)
- 每月 2 号提醒自己还信用卡或自动还款
- 每秒钟发 N 笔脏数据给竞争对手公司的服务器(!←_←)
这些问题总结起来就是:在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂,复杂到需要一个专门的框架来干这个事。Quartz 就是来干这样的事,你给它一个触发条件的定义,它负责到了时间点,触发相应的 Job 起来干活(睡 NMB 起来嗨!)。
3. 什么是 cron 表达式?一个 cron 表达式具体表现就是一个字符串,这个字符串中包含 6~7 个字段,字段之间是由空格分割的,每个字段可以由任何允许的值以及允许的特殊字符所构成,下面表格列出了每个字段所允许的值和特殊字符
-
字符被用来指定所有的值。如:*
在分钟的字段域里表示“每分钟”。*
-
字符被用来指定一个范围。如:-
在小时域意味着“10点、11点、12点”10-12
-
字符被用来指定另外的值。如:,
在星期域里表示“星期一、星期三、星期五”.MON,WED,FRI
-
字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。?
-
字符指定在月或者星期中的某天(最后一天)。即 “Last” 的缩写。但是在星期和月中 “L” 表示不同的意思,如:在月字段中 “L” 指月份的最后一天 “1月31日,2月28日”,如果在星期字段中则简单的表示为“7”或者“SAT”。如果在星期字段中在某个 value 值得后面,则表示 “某月的最后一个星期 value” ,如 “6L” 表示某月的最后一个星期五。L
-
字符只能用在月份字段中,该字段指定了离指定日期最近的那个星期日。W
-
字符只能用在星期字段,该字段指定了第几个星期 value 在某月中#
每一个元素都可以显式地规定一个值(如 6),一个区间(如 9-12 ),一个列表(如 9,11,13 )或一个通配符(如 *)。“月份中的日期”和“星期中的日期”这两个元素是互斥的,因此应该通过设置一个问号(?)来表明你不想设置的那个字段。
在线Cron表达式生成器
4. Spring Boot 集成 Quartz(1).
创建项目创建一个名为
hello-quartz
的项目
(2).
pom文件<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.funtl</groupId>
<artifactId>hello-quartz</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>hello-quartz</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
主要增加了
org.springframework.boot:spring-boot-starter-quartz
依赖
(3).
Applicationpackage com.duo.hello.quatrz;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@SpringBootApplication
public class HelloQuatrzApplication {
public static void main(String[] args) {
SpringApplication.run(HelloQuatrzApplication.class, args);
}
}
使用
@EnableScheduling
注解来开启计划任务功能
(4).
创建任务我们创建一个每 5 秒钟打印当前时间的任务来测试 Quartz
package com.duo.hello.quatrz.tasks;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
public class PrintCurrentTimeTask {
@Scheduled(cron = "0/5 * * * * ? ")
public void printCurrentTime() {
System.out.println("Current Time is:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
5. 定时任务 @Component
public class AnnotationQuartz {
@Scheduled(cron = "0/30 * * * * ?")
public void processInitMsg() {
//TODO 回调消息发送服务中心接口用消息业务id查找对应的业务数据是否生成,如果生成则直接将消息改成已发送状态并投递消息到mq
}
@Scheduled(cron = "0/60 * * * * ?")
public void processSentMsg() {
//TODO 定时处理未完成的消息,如果已经几次查找消息都还是处于未完成状态,则很可能消息没有被消费者接收成功,由于消费者有幂等性实现,则可重发消息给mq
}
}