0x00 background
A simple purchase procedure looks like there is no problem.
The remaining balance, goods inventory, purchase permissions, and other judgments are all-encompassing, from the beginning to the end of the packaging strictly.
But why do people often miss out? Why?
0x01 Problem Analysis
Take the shopping mall as an example. The Shopping Mall Website consists of web programs and databases. The business process is as follows:
Whether the user amount is greater than the product price-> whether the product stock is sufficient-> purchase operation: generate an order-> deduct the user amount-> reduce the product inventory by 1
Each part of the process deals with databases on the web, and queries or operates databases.
Program example (PHP + MySQL)
$ Goods = $ db-> FirstRow ("SELECT * FROM ". tb ('goods '). "WHERE goods_id = '{$ goods_id}'"); if (empty ($ goods) ShowError ('item does not exist '); /* whether the amount is sufficient */if ($ user-> money <$ goods ['price']) ShowError ('Insufficient amount, please recharge '); /* Item Inventory */if ($ goods ['num'] = 0) ShowError ('inventory insufficiency '); /* purchase operation begin * // generate the order CreateOrder ($ goods, $ user, time ()); $ user-> Update ('money' => $ user-> money-$ goods ['price']); // reduce the user amount by $ db-> Execute ("UPDATE ". tb ('goods '). "SET num = num-1 WHERE goods_id = '{$ goods_id}'"); // item stock-1 ShowSuccess ('purchase successfully');/* purchase operation end */
Normally, there is no problem with this service. What can be the problem caused by the situation where multiple users buy (concurrent requests, such as seckilling) at the same time?
If a user has two purchase requests at the same time, one purchase has been made to the Add order, but the user amount is not deducted, And the other purchase is inaccurate in the first step.
When the inventory of goods is only 1, there are multiple requests at the same time. Currently, no one request is sent to the position where the inventory of goods is reduced. Multiple purchases can be successful, but the mall is unavailable.
To sum up, when a large number of purchase operations are performed simultaneously, ifThe processing speed of the database cannot keep up with the request speed of the programIt may lead to inaccurate judgment, resulting in the user to purchase multiple goods with the amount of a single commodity, some users have paid but cannot get the goods, etc., is a security risk.
0x02 solution:
Core Idea: Take a business processing process (such as a purchase operation) as a minimum operation unit, and only one operation can be performed at a time.
1. Add a memory lock to the entire operation. For example, in memcache, when you start purchasing, set the purchase status to in progress. After the purchase, clear the purchase status. When the program starts, it checks whether there are ongoing purchase operations from memcache, exit if any. 2. Restrict the purchase interval of each user. For example, you can only buy one time within 10 seconds. It is best to put it in the memory. 3. Of course, it is also necessary to optimize databases and programs to speed up processing.
Solution example (PHP + MySQL + Memcached)
/*** Solve the concurrent purchase problem through memcache */$ goods = $ db-> FirstRow ("SELECT * FROM ". tb ('goods '). "WHERE goods_id = '{$ goods_id}'"); if (empty ($ goods) ShowError ('item does not exist'); $ mmc = memcache_init (); $ lastBuyTime = $ mmc-> get ('lastbuytime _'. $ user-> userId); if ($ lastBuyTime> 0 & $ lastBuyTime> time ()-10) ShowError ('only one purchase in 10 seconds '); $ buying = $ mmc-> get ('bucking'); if ($ buying = 1) ShowError ('there are ongoing purchases, please wait '); /* whether the amount is sufficient */if ($ user-> money <$ goods ['price']) ShowError ('Insufficient amount, please recharge '); /* Item Inventory */if ($ goods ['num'] = 0) ShowError ('inventory insufficiency '); /* purchase operation begin * // generate the order CreateOrder ($ goods, $ user, time ()); $ user-> Update ('money' => $ user-> money-$ goods ['price']); // reduce the user amount by $ db-> Execute ("UPDATE ". tb ('goods '). "SET num = num-1 WHERE goods_id = '{$ goods_id}'"); // item stock-1/* purchase operation end */$ mmc-> set ('bucket ', 0); $ mmc-> set ('lastbuytime _'. $ user-> userId, time (); ShowSuccess ('purchase successful ');