1. 程式人生 > >CAS和MySql樂觀鎖實現下單

CAS和MySql樂觀鎖實現下單

CAS和MySql樂觀鎖實現下單

準備

建表t_order:

CREATE TABLE `t_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `version` int(255) DEFAULT NULL,
  `stock` int(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

初始化資料:

INSERT INTO `spboot`.`t_order` (`
id`, `version`, `stock`) VALUES ('1', '1', '35'); mysql> select * from t_order; +----+---------+-------+ | id | version | stock | +----+---------+-------+ | 1 | 1 | 35 | +----+---------+-------+

OrderDo.java:

public class OrderDo {
    private Integer id;

    private Integer version;
private Integer stock; //ommited getter & setter }

OrderDoMapper.java:

public interface OrderDoMapper {

    OrderDo selectByPrimaryKey(Integer id);

    int desStockByCas(@Param("orderId") int orderId, 
    			@Param("oldStock") int oldStock, @Param("desStock") int desStock);

    int
desStockByOptimistic(@Param("orderId") int orderId, @Param("oldVersion") int oldVersion, @Param("desStock") int desStock); int desStockByLarge(@Param("orderId") int orderId,@Param("desStock") int desStock); }

OrderManager.java:

@Component(value = "orderManager")
public class OrderManager {


    @Autowired
    private OrderDoMapper orderDoMapper;

    public OrderDoMapper getDao(){
        return this.orderDoMapper;
    }


    public int desStockByCas(int orderId, int oldStock, int desStock){
        return orderDoMapper.desStockByCas(orderId,oldStock, desStock);
    }


    public int desStockByOptimistic(int orderId, int oldVersion, int desStock){
        return orderDoMapper.desStockByOptimistic(orderId, oldVersion, desStock);
    }


    public int desStockByLarge(int orderId, int desStock){
        return orderDoMapper.desStockByLarge(orderId, desStock);
    }

}

模擬業務OrderService.java:

@Service
public class OrderService {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrderService.class);

    @Autowired
    private OrderManager orderManager;

    @Transactional
    public void downOrder(){

        int orderId = 1;
        int desStock = 1;
        OrderDo orderDo = orderManager.getDao().selectByPrimaryKey(orderId);
        if (orderDo.getStock() <=0 ){
            LOGGER.info(Thread.currentThread().getName()+" :無庫存.....");
            return;
        }

          //CAS
//        int result = orderManager.desStockByCas(orderId, orderDo.getStock(), desStock);
          //樂觀鎖
//        int result = orderManager.desStockByOptimistic(orderId, orderDo.getVersion(), desStock);
//      
//      //大於
        int result = orderManager.desStockByLarge(orderId, desStock);
        if (result > 0){
            LOGGER.info(Thread.currentThread().getName()+ " 下單成功...");
        }else {
            LOGGER.info(Thread.currentThread().getName()+ " 下單失敗...");
        }

    }
}

測試案例

用執行緒OrderRunnable.java模擬去發起下單操作:

public class OrderRunnable implements Runnable {

    private CountDownLatch latch;
    private OrderService orderService;

    public OrderRunnable(CountDownLatch latch, OrderService orderService) {
        this.latch = latch;
        this.orderService = orderService;
    }

    @Override
    public void run() {
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        orderService.downOrder();
    }
}

測試案例,用50個執行緒模擬50個使用者去下單:

@Test
public void downOrderTest() throws InterruptedException {

	CountDownLatch latch = new CountDownLatch(1);
	for (int i=0; i < 50; i++){
		Thread t = new Thread(new OrderRunable(latch, orderService));
		t.start();
	}
	latch.countDown();
	Thread.sleep(10000);
}

CAS方式

orderDoMapper.xml:

<update id="desStockByCas">
     UPDATE t_order SET stock=stock-#{desStock} WHERE id=#{orderId} AND stock=#{oldStock}
</update>

有地方說扣減庫存不是冪等的,需要改成設值的方式,之後的方式也一樣,如:

# newStock = oldStock-desStock;
<update id="desStockByCas">
     UPDATE t_order SET stock=#{newStock} WHERE id=#{orderId} AND stock=#{oldStock}
</update>

開啟註解:

int result = orderManager.desStockByCas(orderId, orderDo.getStock(), desStock);

測試結果:

2018-11-26 21:30:33 [Thread-3] INFO  com.xxx.service.OrderService -Thread-3 下單成功...
2018-11-26 21:30:33 [Thread-7] INFO  com.xxx.service.OrderService -Thread-7 下單失敗...
2018-11-26 21:30:33 [Thread-36] INFO  com.xxx.service.OrderService -Thread-36 下單失敗...
2018-11-26 21:30:33 [Thread-5] INFO  com.xxx.service.OrderService -Thread-5 下單失敗...
2018-11-26 21:30:33 [Thread-14] INFO  com.xxx.service.OrderService -Thread-14 下單失敗...
2018-11-26 21:30:33 [Thread-9] INFO  com.xxx.service.OrderService -Thread-9 下單失敗...
2018-11-26 21:30:33 [Thread-43] INFO  com.xxx.service.OrderService -Thread-43 下單失敗...
2018-11-26 21:30:33 [Thread-29] INFO  com.xxx.service.OrderService -Thread-29 下單成功...
2018-11-26 21:30:33 [Thread-42] INFO  com.xxx.service.OrderService -Thread-42 下單失敗...
2018-11-26 21:30:33 [Thread-34] INFO  com.xxx.service.OrderService -Thread-34 下單成功...
2018-11-26 21:30:33 [Thread-21] INFO  com.xxx.service.OrderService -Thread-21 下單失敗...
2018-11-26 21:30:33 [Thread-39] INFO  com.xxx.service.OrderService -Thread-39 下單成功...
2018-11-26 21:30:33 [Thread-22] INFO  com.xxx.service.OrderService -Thread-22 下單失敗...
2018-11-26 21:30:33 [Thread-8] INFO  com.xxx.service.OrderService -Thread-8 下單成功...
2018-11-26 21:30:33 [Thread-20] INFO  com.xxx.service.OrderService -Thread-20 下單失敗...
// ommitted some...

檢視資料庫:

mysql> select * from t_order;
+----+---------+-------+
| id | version | stock |
+----+---------+-------+
|  1 |       1 |    20 |
+----+---------+-------+

可以發現,本來35個庫存,現在變成了20個,也就是說成功了15個

MySql樂觀鎖方式

MySql樂觀鎖是基於CAS原理的一種實現.

<update id="desStockByOptimistic">
    UPDATE t_order SET stock=stock-#{desStock}, version=version+1 
    		WHERE id=#{orderId} AND version=#{oldVersion}
</update>

開啟註解:

int result = orderManager.desStockByOptimistic(orderId, orderDo.getVersion(), desStock);

測試結果:

2018-11-26 21:34:26 [Thread-20] INFO  com.xxx.service.OrderService -Thread-20 下單成功...
2018-11-26 21:34:26 [Thread-25] INFO  com.xxx.service.OrderService -Thread-25 下單失敗...
2018-11-26 21:34:26 [Thread-11] INFO  com.xxx.service.OrderService -Thread-11 下單失敗...
2018-11-26 21:34:26 [Thread-34] INFO  com.xxx.service.OrderService -Thread-34 下單失敗...
2018-11-26 21:34:26 [Thread-26] INFO  com.xxx.service.OrderService -Thread-26 下單失敗...
2018-11-26 21:34:26 [Thread-24] INFO  com.xxx.service.OrderService -Thread-24 下單失敗...
2018-11-26 21:34:26 [Thread-40] INFO  com.xxx.service.OrderService -Thread-40 下單失敗...
2018-11-26 21:34:26 [Thread-14] INFO  com.xxx.service.OrderService -Thread-14 下單成功...
2018-11-26 21:34:26 [Thread-22] INFO  com.xxx.service.OrderService -Thread-22 下單失敗...
2018-11-26 21:34:26 [Thread-21] INFO  com.xxx.service.OrderService -Thread-21 下單成功...
2018-11-26 21:34:26 [Thread-49] INFO  com.xxx.service.OrderService -Thread-49 下單失敗...
2018-11-26 21:34:26 [Thread-4] INFO  com.xxx.service.OrderService -Thread-4 下單成功...
2018-11-26 21:34:26 [Thread-7] INFO  com.xxx.service.OrderService -Thread-7 下單失敗...

檢視資料庫:

mysql> select * from t_order;
+----+---------+-------+
| id | version | stock |
+----+---------+-------+
|  1 |      19 |    17 |
+----+---------+-------+
1 row in set

可以發現,本來35個庫存,現在變成了17個,也就是說成功了18個

大於方式

<update id="desStockByLarge">
   UPDATE t_order SET stock=stock-#{desStock} WHERE id=#{orderId} and stock-#{desStock}>=0
</update>

開啟註解:

int result = orderManager.desStockByLarge(orderId, desStock);

測試結果:

2018-11-26 21:37:32 [Thread-3] INFO  com.xxx.service.OrderService -Thread-3 下單成功...
2018-11-26 21:37:32 [Thread-5] INFO  com.xxx.service.OrderService -Thread-5 下單成功...
2018-11-26 21:37:32 [Thread-47] INFO  com.xxx.service.OrderService -Thread-47 下單成功...
2018-11-26 21:37:32 [Thread-23] INFO  com.xxx.service.OrderService -Thread-23 下單成功...
2018-11-26 21:37:32 [Thread-16] INFO  com.xxx.service.OrderService -Thread-16 下單成功...
2018-11-26 21:37:32 [Thread-45] INFO  com.xxx.service.OrderService -Thread-45 下單成功...
2018-11-26 21:37:32 [Thread-52] INFO  com.xxx.service.OrderService -Thread-52 下單成功...
2018-11-26 21:37:32 [Thread-49] INFO  com.xxx.service.OrderService -Thread-49 下單失敗...
2018-11-26 21:37:32 [Thread-11] INFO  com.xxx.service.OrderService -Thread-11 下單失敗...
2018-11-26 21:37:32 [Thread-31] INFO  com.xxx.service.OrderService -Thread-31 下單失敗...
2018-11-26 21:37:32 [Thread-6] INFO  com.xxx.service.OrderService -Thread-6 :無庫存.....
2018-11-26 21:37:32 [Thread-44] INFO  com.xxx.service.OrderService -Thread-44 :無庫存.....
2018-11-26 21:37:32 [Thread-30] INFO  com.xxx.service.OrderService -Thread-30 :無庫存.....
2018-11-26 21:37:32 [Thread-33] INFO  com.xxx.service.OrderService -Thread-33 :無庫存.....

檢視資料庫:

mysql> select * from t_order;
+----+---------+-------+
| id | version | stock |
+----+---------+-------+
|  1 |       1 |     0 |
+----+---------+-------+
1 row in set

可以發現,本來35個庫存,現在變成了0個,也就是說全部成功

mysql悲觀鎖方式