CAS和MySql樂觀鎖實現下單
阿新 • • 發佈:2018-12-26
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個,也就是說全部成功